refactor: misc. clean ups (removing unnecessary comments, improving variable names)

This commit is contained in:
Johann Schopplich
2025-11-10 13:19:33 +01:00
parent 5873c5828f
commit ac17a8d260
8 changed files with 74 additions and 75 deletions

View File

@@ -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
} }
} }

View File

@@ -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 } {

View File

@@ -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`)
} }

View File

@@ -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)

View File

@@ -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)
} }

View File

@@ -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)

View File

@@ -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
} }

View File

@@ -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