mirror of
https://github.com/voson-wang/toon.git
synced 2026-01-29 15:24: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>
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if two values can be merged (both are plain objects).
|
||||
*/
|
||||
function canMerge(a: JsonValue, b: JsonValue): a is JsonObject {
|
||||
return isJsonObject(a) && isJsonObject(b)
|
||||
}
|
||||
@@ -140,13 +137,13 @@ function insertPathSafe(
|
||||
|
||||
// Walk to the penultimate segment, creating objects as needed
|
||||
for (let i = 0; i < segments.length - 1; i++) {
|
||||
const seg = segments[i]!
|
||||
const segmentValue = currentNode[seg]
|
||||
const currentSegment = segments[i]!
|
||||
const segmentValue = currentNode[currentSegment]
|
||||
|
||||
if (segmentValue === undefined) {
|
||||
// Create new intermediate object
|
||||
const newObj: JsonObject = {}
|
||||
currentNode[seg] = newObj
|
||||
currentNode[currentSegment] = newObj
|
||||
currentNode = newObj
|
||||
}
|
||||
else if (isJsonObject(segmentValue)) {
|
||||
@@ -157,12 +154,12 @@ function insertPathSafe(
|
||||
// Conflict: existing value is not an object
|
||||
if (strict) {
|
||||
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
|
||||
const newObj: JsonObject = {}
|
||||
currentNode[seg] = newObj
|
||||
currentNode[currentSegment] = newObj
|
||||
currentNode = newObj
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,6 +144,15 @@ export function parseBracketSegment(
|
||||
|
||||
// #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[] {
|
||||
const values: string[] = []
|
||||
let valueBuffer = ''
|
||||
@@ -252,22 +261,22 @@ export function parseStringLiteral(token: string): string {
|
||||
}
|
||||
|
||||
export function parseUnquotedKey(content: string, start: number): { key: string, end: number } {
|
||||
let end = start
|
||||
while (end < content.length && content[end] !== COLON) {
|
||||
end++
|
||||
let parsePosition = start
|
||||
while (parsePosition < content.length && content[parsePosition] !== COLON) {
|
||||
parsePosition++
|
||||
}
|
||||
|
||||
// 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')
|
||||
}
|
||||
|
||||
const key = content.slice(start, end).trim()
|
||||
const key = content.slice(start, parsePosition).trim()
|
||||
|
||||
// Skip the colon
|
||||
end++
|
||||
parsePosition++
|
||||
|
||||
return { key, end }
|
||||
return { key, end: parsePosition }
|
||||
}
|
||||
|
||||
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
|
||||
const keyContent = content.slice(start + 1, closingQuoteIndex)
|
||||
const key = unescapeString(keyContent)
|
||||
let end = closingQuoteIndex + 1
|
||||
let parsePosition = closingQuoteIndex + 1
|
||||
|
||||
// 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')
|
||||
}
|
||||
end++
|
||||
parsePosition++
|
||||
|
||||
return { key, end }
|
||||
return { key, end: parsePosition }
|
||||
}
|
||||
|
||||
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
|
||||
if (strict) {
|
||||
// Find the full leading whitespace region (spaces and tabs)
|
||||
let wsEnd = 0
|
||||
while (wsEnd < raw.length && (raw[wsEnd] === SPACE || raw[wsEnd] === TAB)) {
|
||||
wsEnd++
|
||||
let whitespaceEndIndex = 0
|
||||
while (whitespaceEndIndex < raw.length && (raw[whitespaceEndIndex] === SPACE || raw[whitespaceEndIndex] === TAB)) {
|
||||
whitespaceEndIndex++
|
||||
}
|
||||
|
||||
// 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`)
|
||||
}
|
||||
|
||||
|
||||
@@ -4,12 +4,6 @@ import { COLON, LIST_ITEM_PREFIX } from '../constants'
|
||||
|
||||
/**
|
||||
* 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(
|
||||
actual: number,
|
||||
@@ -24,11 +18,6 @@ export function assertExpectedCount(
|
||||
|
||||
/**
|
||||
* 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(
|
||||
cursor: LineCursor,
|
||||
@@ -46,11 +35,6 @@ export function validateNoExtraListItems(
|
||||
|
||||
/**
|
||||
* 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(
|
||||
cursor: LineCursor,
|
||||
@@ -72,17 +56,7 @@ export function validateNoExtraTabularRows(
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates that there are no blank lines within a specific line range and depth.
|
||||
*
|
||||
* @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
|
||||
* Validates that there are no blank lines within a specific line range in strict mode.
|
||||
*/
|
||||
export function validateNoBlankLinesInRange(
|
||||
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.
|
||||
*
|
||||
* @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
|
||||
* Checks if a line is a data row (vs a key-value pair) in a tabular array.
|
||||
*/
|
||||
function isDataRow(content: string, delimiter: Delimiter): boolean {
|
||||
const colonPos = content.indexOf(COLON)
|
||||
|
||||
@@ -138,6 +138,8 @@ function collectSingleKeyChain(
|
||||
const segments: string[] = [startKey]
|
||||
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) {
|
||||
// Must be an object to continue
|
||||
if (!isJsonObject(currentValue)) {
|
||||
@@ -180,12 +182,6 @@ function collectSingleKeyChain(
|
||||
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 {
|
||||
return segments.join(DOT)
|
||||
}
|
||||
|
||||
@@ -20,12 +20,51 @@ export type {
|
||||
ResolvedEncodeOptions,
|
||||
} 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 {
|
||||
const normalizedValue = normalizeValue(input)
|
||||
const resolvedOptions = resolveOptions(options)
|
||||
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 {
|
||||
const resolvedOptions = resolveDecodeOptions(options)
|
||||
const scanResult = toParsedLines(input, resolvedOptions.indent, resolvedOptions.strict)
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
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 {
|
||||
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.
|
||||
*
|
||||
* @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
|
||||
* Finds the index of the closing double quote, accounting for escape sequences.
|
||||
*/
|
||||
export function findClosingQuote(content: string, start: number): number {
|
||||
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.
|
||||
*
|
||||
* @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
|
||||
* Finds the index of a character outside of quoted sections.
|
||||
*/
|
||||
export function findUnquotedChar(content: string, char: string, start = 0): number {
|
||||
let inQuotes = false
|
||||
|
||||
Reference in New Issue
Block a user