mirror of
https://github.com/voson-wang/toon.git
synced 2026-01-29 23:34:10 +08:00
refactor: misc. clean ups (removing unnecessary comments, improving variable names)
This commit is contained in:
@@ -18,9 +18,6 @@ export interface ObjectWithQuotedKeys extends JsonObject {
|
|||||||
[QUOTED_KEY_MARKER]?: Set<string>
|
[QUOTED_KEY_MARKER]?: Set<string>
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if two values can be merged (both are plain objects).
|
|
||||||
*/
|
|
||||||
function canMerge(a: JsonValue, b: JsonValue): a is JsonObject {
|
function canMerge(a: JsonValue, b: JsonValue): a is JsonObject {
|
||||||
return isJsonObject(a) && isJsonObject(b)
|
return isJsonObject(a) && isJsonObject(b)
|
||||||
}
|
}
|
||||||
@@ -140,13 +137,13 @@ function insertPathSafe(
|
|||||||
|
|
||||||
// Walk to the penultimate segment, creating objects as needed
|
// Walk to the penultimate segment, creating objects as needed
|
||||||
for (let i = 0; i < segments.length - 1; i++) {
|
for (let i = 0; i < segments.length - 1; i++) {
|
||||||
const seg = segments[i]!
|
const currentSegment = segments[i]!
|
||||||
const segmentValue = currentNode[seg]
|
const segmentValue = currentNode[currentSegment]
|
||||||
|
|
||||||
if (segmentValue === undefined) {
|
if (segmentValue === undefined) {
|
||||||
// Create new intermediate object
|
// Create new intermediate object
|
||||||
const newObj: JsonObject = {}
|
const newObj: JsonObject = {}
|
||||||
currentNode[seg] = newObj
|
currentNode[currentSegment] = newObj
|
||||||
currentNode = newObj
|
currentNode = newObj
|
||||||
}
|
}
|
||||||
else if (isJsonObject(segmentValue)) {
|
else if (isJsonObject(segmentValue)) {
|
||||||
@@ -157,12 +154,12 @@ function insertPathSafe(
|
|||||||
// Conflict: existing value is not an object
|
// Conflict: existing value is not an object
|
||||||
if (strict) {
|
if (strict) {
|
||||||
throw new TypeError(
|
throw new TypeError(
|
||||||
`Path expansion conflict at segment "${seg}": expected object but found ${typeof segmentValue}`,
|
`Path expansion conflict at segment "${currentSegment}": expected object but found ${typeof segmentValue}`,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
// Non-strict: overwrite with new object
|
// Non-strict: overwrite with new object
|
||||||
const newObj: JsonObject = {}
|
const newObj: JsonObject = {}
|
||||||
currentNode[seg] = newObj
|
currentNode[currentSegment] = newObj
|
||||||
currentNode = newObj
|
currentNode = newObj
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -144,6 +144,15 @@ export function parseBracketSegment(
|
|||||||
|
|
||||||
// #region Delimited value parsing
|
// #region Delimited value parsing
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses a delimited string into values, respecting quoted strings and escape sequences.
|
||||||
|
*
|
||||||
|
* @remarks
|
||||||
|
* Uses a state machine that tracks:
|
||||||
|
* - `inQuotes`: Whether we're inside a quoted string (to ignore delimiters)
|
||||||
|
* - `valueBuffer`: Accumulates characters for the current value
|
||||||
|
* - Escape sequences: Handled within quoted strings
|
||||||
|
*/
|
||||||
export function parseDelimitedValues(input: string, delimiter: Delimiter): string[] {
|
export function parseDelimitedValues(input: string, delimiter: Delimiter): string[] {
|
||||||
const values: string[] = []
|
const values: string[] = []
|
||||||
let valueBuffer = ''
|
let valueBuffer = ''
|
||||||
@@ -252,22 +261,22 @@ export function parseStringLiteral(token: string): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function parseUnquotedKey(content: string, start: number): { key: string, end: number } {
|
export function parseUnquotedKey(content: string, start: number): { key: string, end: number } {
|
||||||
let end = start
|
let parsePosition = start
|
||||||
while (end < content.length && content[end] !== COLON) {
|
while (parsePosition < content.length && content[parsePosition] !== COLON) {
|
||||||
end++
|
parsePosition++
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate that a colon was found
|
// Validate that a colon was found
|
||||||
if (end >= content.length || content[end] !== COLON) {
|
if (parsePosition >= content.length || content[parsePosition] !== COLON) {
|
||||||
throw new SyntaxError('Missing colon after key')
|
throw new SyntaxError('Missing colon after key')
|
||||||
}
|
}
|
||||||
|
|
||||||
const key = content.slice(start, end).trim()
|
const key = content.slice(start, parsePosition).trim()
|
||||||
|
|
||||||
// Skip the colon
|
// Skip the colon
|
||||||
end++
|
parsePosition++
|
||||||
|
|
||||||
return { key, end }
|
return { key, end: parsePosition }
|
||||||
}
|
}
|
||||||
|
|
||||||
export function parseQuotedKey(content: string, start: number): { key: string, end: number } {
|
export function parseQuotedKey(content: string, start: number): { key: string, end: number } {
|
||||||
@@ -281,15 +290,15 @@ export function parseQuotedKey(content: string, start: number): { key: string, e
|
|||||||
// Extract and unescape the key content
|
// Extract and unescape the key content
|
||||||
const keyContent = content.slice(start + 1, closingQuoteIndex)
|
const keyContent = content.slice(start + 1, closingQuoteIndex)
|
||||||
const key = unescapeString(keyContent)
|
const key = unescapeString(keyContent)
|
||||||
let end = closingQuoteIndex + 1
|
let parsePosition = closingQuoteIndex + 1
|
||||||
|
|
||||||
// Validate and skip colon after quoted key
|
// Validate and skip colon after quoted key
|
||||||
if (end >= content.length || content[end] !== COLON) {
|
if (parsePosition >= content.length || content[parsePosition] !== COLON) {
|
||||||
throw new SyntaxError('Missing colon after key')
|
throw new SyntaxError('Missing colon after key')
|
||||||
}
|
}
|
||||||
end++
|
parsePosition++
|
||||||
|
|
||||||
return { key, end }
|
return { key, end: parsePosition }
|
||||||
}
|
}
|
||||||
|
|
||||||
export function parseKeyToken(content: string, start: number): { key: string, end: number, isQuoted: boolean } {
|
export function parseKeyToken(content: string, start: number): { key: string, end: number, isQuoted: boolean } {
|
||||||
|
|||||||
@@ -92,13 +92,13 @@ export function toParsedLines(source: string, indentSize: number, strict: boolea
|
|||||||
// Strict mode validation
|
// Strict mode validation
|
||||||
if (strict) {
|
if (strict) {
|
||||||
// Find the full leading whitespace region (spaces and tabs)
|
// Find the full leading whitespace region (spaces and tabs)
|
||||||
let wsEnd = 0
|
let whitespaceEndIndex = 0
|
||||||
while (wsEnd < raw.length && (raw[wsEnd] === SPACE || raw[wsEnd] === TAB)) {
|
while (whitespaceEndIndex < raw.length && (raw[whitespaceEndIndex] === SPACE || raw[whitespaceEndIndex] === TAB)) {
|
||||||
wsEnd++
|
whitespaceEndIndex++
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for tabs in leading whitespace (before actual content)
|
// Check for tabs in leading whitespace (before actual content)
|
||||||
if (raw.slice(0, wsEnd).includes(TAB)) {
|
if (raw.slice(0, whitespaceEndIndex).includes(TAB)) {
|
||||||
throw new SyntaxError(`Line ${lineNumber}: Tabs are not allowed in indentation in strict mode`)
|
throw new SyntaxError(`Line ${lineNumber}: Tabs are not allowed in indentation in strict mode`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,12 +4,6 @@ import { COLON, LIST_ITEM_PREFIX } from '../constants'
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Asserts that the actual count matches the expected count in strict mode.
|
* Asserts that the actual count matches the expected count in strict mode.
|
||||||
*
|
|
||||||
* @param actual The actual count
|
|
||||||
* @param expected The expected count
|
|
||||||
* @param itemType The type of items being counted (e.g., `list array items`, `tabular rows`)
|
|
||||||
* @param options Decode options
|
|
||||||
* @throws RangeError if counts don't match in strict mode
|
|
||||||
*/
|
*/
|
||||||
export function assertExpectedCount(
|
export function assertExpectedCount(
|
||||||
actual: number,
|
actual: number,
|
||||||
@@ -24,11 +18,6 @@ export function assertExpectedCount(
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Validates that there are no extra list items beyond the expected count.
|
* Validates that there are no extra list items beyond the expected count.
|
||||||
*
|
|
||||||
* @param cursor The line cursor
|
|
||||||
* @param itemDepth The expected depth of items
|
|
||||||
* @param expectedCount The expected number of items
|
|
||||||
* @throws RangeError if extra items are found
|
|
||||||
*/
|
*/
|
||||||
export function validateNoExtraListItems(
|
export function validateNoExtraListItems(
|
||||||
cursor: LineCursor,
|
cursor: LineCursor,
|
||||||
@@ -46,11 +35,6 @@ export function validateNoExtraListItems(
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Validates that there are no extra tabular rows beyond the expected count.
|
* Validates that there are no extra tabular rows beyond the expected count.
|
||||||
*
|
|
||||||
* @param cursor The line cursor
|
|
||||||
* @param rowDepth The expected depth of rows
|
|
||||||
* @param header The array header info containing length and delimiter
|
|
||||||
* @throws RangeError if extra rows are found
|
|
||||||
*/
|
*/
|
||||||
export function validateNoExtraTabularRows(
|
export function validateNoExtraTabularRows(
|
||||||
cursor: LineCursor,
|
cursor: LineCursor,
|
||||||
@@ -72,17 +56,7 @@ export function validateNoExtraTabularRows(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validates that there are no blank lines within a specific line range and depth.
|
* Validates that there are no blank lines within a specific line range in strict mode.
|
||||||
*
|
|
||||||
* @remarks
|
|
||||||
* In strict mode, blank lines inside arrays/tabular rows are not allowed.
|
|
||||||
*
|
|
||||||
* @param startLine The starting line number (inclusive)
|
|
||||||
* @param endLine The ending line number (inclusive)
|
|
||||||
* @param blankLines Array of blank line information
|
|
||||||
* @param strict Whether strict mode is enabled
|
|
||||||
* @param context Description of the context (e.g., "list array", "tabular array")
|
|
||||||
* @throws SyntaxError if blank lines are found in strict mode
|
|
||||||
*/
|
*/
|
||||||
export function validateNoBlankLinesInRange(
|
export function validateNoBlankLinesInRange(
|
||||||
startLine: number,
|
startLine: number,
|
||||||
@@ -110,11 +84,7 @@ export function validateNoBlankLinesInRange(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if a line represents a data row (as opposed to a key-value pair) in a tabular array.
|
* Checks if a line is a data row (vs a key-value pair) in a tabular array.
|
||||||
*
|
|
||||||
* @param content The line content
|
|
||||||
* @param delimiter The delimiter used in the table
|
|
||||||
* @returns true if the line is a data row, false if it's a key-value pair
|
|
||||||
*/
|
*/
|
||||||
function isDataRow(content: string, delimiter: Delimiter): boolean {
|
function isDataRow(content: string, delimiter: Delimiter): boolean {
|
||||||
const colonPos = content.indexOf(COLON)
|
const colonPos = content.indexOf(COLON)
|
||||||
|
|||||||
@@ -138,6 +138,8 @@ function collectSingleKeyChain(
|
|||||||
const segments: string[] = [startKey]
|
const segments: string[] = [startKey]
|
||||||
let currentValue = startValue
|
let currentValue = startValue
|
||||||
|
|
||||||
|
// Traverse nested single-key objects, collecting each key into segments array
|
||||||
|
// Stop when we encounter: multi-key object, array, primitive, or depth limit
|
||||||
while (segments.length < maxDepth) {
|
while (segments.length < maxDepth) {
|
||||||
// Must be an object to continue
|
// Must be an object to continue
|
||||||
if (!isJsonObject(currentValue)) {
|
if (!isJsonObject(currentValue)) {
|
||||||
@@ -180,12 +182,6 @@ function collectSingleKeyChain(
|
|||||||
return { segments, tail: currentValue, leafValue: currentValue }
|
return { segments, tail: currentValue, leafValue: currentValue }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Builds a folded key from segments.
|
|
||||||
*
|
|
||||||
* @param segments - Array of key segments
|
|
||||||
* @returns Dot-separated key string
|
|
||||||
*/
|
|
||||||
function buildFoldedKey(segments: readonly string[]): string {
|
function buildFoldedKey(segments: readonly string[]): string {
|
||||||
return segments.join(DOT)
|
return segments.join(DOT)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,12 +20,51 @@ export type {
|
|||||||
ResolvedEncodeOptions,
|
ResolvedEncodeOptions,
|
||||||
} from './types'
|
} from './types'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encodes a JavaScript value into TOON format string.
|
||||||
|
*
|
||||||
|
* @param input - Any JavaScript value (objects, arrays, primitives)
|
||||||
|
* @param options - Optional encoding configuration
|
||||||
|
* @returns TOON formatted string
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* encode({ name: 'Alice', age: 30 })
|
||||||
|
* // name: Alice
|
||||||
|
* // age: 30
|
||||||
|
*
|
||||||
|
* encode({ users: [{ id: 1 }, { id: 2 }] })
|
||||||
|
* // users[]:
|
||||||
|
* // - id: 1
|
||||||
|
* // - id: 2
|
||||||
|
*
|
||||||
|
* encode(data, { indent: 4, keyFolding: 'safe' })
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
export function encode(input: unknown, options?: EncodeOptions): string {
|
export function encode(input: unknown, options?: EncodeOptions): string {
|
||||||
const normalizedValue = normalizeValue(input)
|
const normalizedValue = normalizeValue(input)
|
||||||
const resolvedOptions = resolveOptions(options)
|
const resolvedOptions = resolveOptions(options)
|
||||||
return encodeValue(normalizedValue, resolvedOptions)
|
return encodeValue(normalizedValue, resolvedOptions)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decodes a TOON format string into a JavaScript value.
|
||||||
|
*
|
||||||
|
* @param input - TOON formatted string
|
||||||
|
* @param options - Optional decoding configuration
|
||||||
|
* @returns Parsed JavaScript value (object, array, or primitive)
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* decode('name: Alice\nage: 30')
|
||||||
|
* // { name: 'Alice', age: 30 }
|
||||||
|
*
|
||||||
|
* decode('users[]:\n - id: 1\n - id: 2')
|
||||||
|
* // { users: [{ id: 1 }, { id: 2 }] }
|
||||||
|
*
|
||||||
|
* decode(toonString, { strict: false, expandPaths: 'safe' })
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
export function decode(input: string, options?: DecodeOptions): JsonValue {
|
export function decode(input: string, options?: DecodeOptions): JsonValue {
|
||||||
const resolvedOptions = resolveDecodeOptions(options)
|
const resolvedOptions = resolveDecodeOptions(options)
|
||||||
const scanResult = toParsedLines(input, resolvedOptions.indent, resolvedOptions.strict)
|
const scanResult = toParsedLines(input, resolvedOptions.indent, resolvedOptions.strict)
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
import { FALSE_LITERAL, NULL_LITERAL, TRUE_LITERAL } from '../constants'
|
import { FALSE_LITERAL, NULL_LITERAL, TRUE_LITERAL } from '../constants'
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if a token is a boolean or null literal (`true`, `false`, `null`).
|
|
||||||
*/
|
|
||||||
export function isBooleanOrNullLiteral(token: string): boolean {
|
export function isBooleanOrNullLiteral(token: string): boolean {
|
||||||
return token === TRUE_LITERAL || token === FALSE_LITERAL || token === NULL_LITERAL
|
return token === TRUE_LITERAL || token === FALSE_LITERAL || token === NULL_LITERAL
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -69,11 +69,7 @@ export function unescapeString(value: string): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Finds the index of the closing double quote in a string, accounting for escape sequences.
|
* Finds the index of the closing double quote, accounting for escape sequences.
|
||||||
*
|
|
||||||
* @param content The string to search in
|
|
||||||
* @param start The index of the opening quote
|
|
||||||
* @returns The index of the closing quote, or -1 if not found
|
|
||||||
*/
|
*/
|
||||||
export function findClosingQuote(content: string, start: number): number {
|
export function findClosingQuote(content: string, start: number): number {
|
||||||
let i = start + 1
|
let i = start + 1
|
||||||
@@ -92,12 +88,7 @@ export function findClosingQuote(content: string, start: number): number {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Finds the index of a specific character outside of quoted sections.
|
* Finds the index of a character outside of quoted sections.
|
||||||
*
|
|
||||||
* @param content The string to search in
|
|
||||||
* @param char The character to look for
|
|
||||||
* @param start Optional starting index (defaults to 0)
|
|
||||||
* @returns The index of the character, or -1 if not found outside quotes
|
|
||||||
*/
|
*/
|
||||||
export function findUnquotedChar(content: string, char: string, start = 0): number {
|
export function findUnquotedChar(content: string, char: string, start = 0): number {
|
||||||
let inQuotes = false
|
let inQuotes = false
|
||||||
|
|||||||
Reference in New Issue
Block a user