toon-format/toon · error · ToonDecodeError

Unexpected bare token line outside root primitive position

Error message

Unexpected bare token line outside root primitive position

What it means

A line that is neither a list item (`- ...`) nor contains an unquoted `key:` colon is a bare token; outside the document's single root-primitive position such lines have no valid meaning. assertNotScalarLine throws this to reject stray scalar lines inside objects/arrays. It is called from decodeDocument and decodeObjectFields.

Source

Thrown at packages/toon/src/decode/decoders.ts:139

  }
}

function overIndentedLineError(line: ParsedLine, expectedDepth: Depth): ToonDecodeError {
  return new ToonDecodeError(
    `Over-indented line: expected depth ${expectedDepth}, but found ${line.depth}`,
    { line: line.lineNumber, source: line.raw },
  )
}

// Both modes reject a bare token outside root primitive position, so it must not reach
// the non-strict paths that drop an over-indented line.
function assertNotScalarLine(line: ParsedLine): void {
  const isListItem = line.content.startsWith(LIST_ITEM_PREFIX) || line.content === LIST_ITEM_MARKER
  if (isListItem || findUnquotedChar(line.content, COLON) !== -1) {
    return
  }

  throw new ToonDecodeError(
    'Unexpected bare token line outside root primitive position',
    { line: line.lineNumber, source: line.raw },
  )
}

function keylessKeyedError(line: ParsedLine): ToonDecodeError {
  return new ToonDecodeError(
    'Keyless keyed header is only valid at the document root',
    { line: line.lineNumber, source: line.raw },
  )
}

function keylessHeaderError(line: ParsedLine): ToonDecodeError {
  return new ToonDecodeError(
    'Keyless array header is only valid at the document root or as a list item',
    { line: line.lineNumber, source: line.raw },
  )
}

View on GitHub (pinned to 604eac266e)

Solutions

  1. Restore the missing `key: ` prefix on the offending line
  2. Quote the text so its colon is preserved and it parses as a value of a key
  3. Remove the stray line if it is leftover prose/debris
  4. If it was meant to be a list entry, prefix it with `- `

Example fix

// before
user:
  Ada Lovelace

// after
user:
  name: Ada Lovelace
Defensive patterns

Strategy: validation

Validate before calling

const bareLines = input.split('\n')
  .map((raw, i) => ({ raw: raw.trim(), i: i + 1 }))
  .filter(l => l.raw && !l.raw.startsWith('- ') && !/^[^:]+:/.test(l.raw))
if (bareLines.length) throw new Error(`Lines without key: ${bareLines.map(l => l.i).join(',')}`)

Type guard

function lineIsKeyedOrListItem(line: string): boolean {
  const t = line.trim()
  return t.startsWith('- ') || /:/.test(t)
}

Try / catch

try {
  return decode(input)
} catch (e) {
  if (e instanceof ToonDecodeError && e.message.includes('bare token')) {
    const ln = e.line ?? 0
    throw new Error(`Line ${ln} is missing its key; every non-list line needs 'key: value'`)
  }
  throw e
}

Prevention

When it happens

Trigger: decodeDocument or decodeObjectFields encounters a line whose content has no unquoted colon and no list-item prefix, e.g. a value missing its key, or an unquoted string containing no colon in a nested block.

Common situations: Deleting a key but leaving the value on its own line; multi-line strings split without proper quoting; markdown or prose accidentally pasted into the file; a line where the colon got quoted away or removed.

Related errors


AI-assisted analysis of toon-format/toon@604eac266e (2026-08-31). Data as JSON: /api/errors/9d33f643fdcd5c89. Report an issue: GitHub.