{"record":{"id":"66a4c1207c54628a","repo":"toon-format/toon","slug":"unterminated-string-missing-closing-quote","errorCode":null,"errorMessage":"Unterminated string: missing closing quote","messagePattern":"Unterminated string: missing closing quote","errorType":"exception","errorClass":"SyntaxError","httpStatus":null,"severity":"error","filePath":"packages/toon/src/decode/parser.ts","lineNumber":475,"sourceCode":"      return null\n  }\n\n  if (isNumericLiteral(trimmedToken)) {\n    const parsedNumber = Number.parseFloat(trimmedToken)\n    return Object.is(parsedNumber, -0) ? 0 : parsedNumber\n  }\n\n  return trimmedToken\n}\n\nexport function parseStringLiteral(token: string): string {\n  const trimmedToken = trimSpaces(token)\n\n  if (trimmedToken.startsWith(DOUBLE_QUOTE)) {\n    const closingQuoteIndex = findClosingQuote(trimmedToken, 0)\n\n    if (closingQuoteIndex === -1) {\n      throw new SyntaxError('Unterminated string: missing closing quote')\n    }\n\n    if (closingQuoteIndex !== trimmedToken.length - 1) {\n      throw new SyntaxError('Unexpected characters after closing quote')\n    }\n\n    const content = trimmedToken.slice(1, closingQuoteIndex)\n    return unescapeString(content)\n  }\n\n  return trimmedToken\n}\n\nexport function parseUnquotedKey(content: string, start: number): { key: string, end: number } {\n  // A raw scan would cut `a \"b:c\" d: 1` at the quoted colon and split the key in two.\n  const colonIndex = findUnquotedChar(content, COLON, start)\n\n  if (colonIndex === -1) {","sourceCodeStart":457,"sourceCodeEnd":493,"githubUrl":"https://github.com/toon-format/toon/blob/604eac266e35166bed6f5b9e3bd586e9c60a9330/packages/toon/src/decode/parser.ts#L457-L493","documentation":"A primitive token in the TOON input started with a double quote, indicating a quoted string, but findClosingQuote could not locate an unescaped closing quote before end-of-token. The parser throws SyntaxError rather than treating the rest of the line as string content, since the token boundaries are ambiguous.","triggerScenarios":"Decoding TOON text containing an opening `\"` with no closing `\"` on the same token/line — e.g. `name: \"hello` or an array row cell `\"unterminated,x`. Reached via parseStringLiteral from parsePrimitiveToken, parseArrayHeaderLine, or parseFieldEntries.","commonSituations":"Hand-written TOON where the closing quote was forgotten; text containing characters that were escaped incorrectly so an escaped quote `\\\"` accidentally consumed the real closing quote; files truncated mid-line; content pasted from sources that strip trailing quotes.","solutions":["Add the missing closing quote to the string token, e.g. `name: \"hello\"`","Check for unescaped inner quotes — escape them as `\\\"` so the parser doesn't end the string early","Avoid manual quoting: emit values via toon.encode which quotes/escapes correctly","Check the file wasn't truncated mid-line (inspect the end of the file/line)"],"exampleFix":"// before\ngreeting: \"hello\n// after\ngreeting: \"hello\"","handlingStrategy":"try-catch","validationCode":"function linesHaveClosedQuotes(text) {\n  return text.split('\\n').every(line => {\n    let n = 0, esc = false\n    for (const ch of line) {\n      if (esc) { esc = false; continue }\n      if (ch === '\\\\') { esc = true; continue }\n      if (ch === '\"') n++\n    }\n    return n % 2 === 0\n  })\n}","typeGuard":null,"tryCatchPattern":"try {\n  const value = toon.decode(text)\n} catch (err) {\n  if (err instanceof SyntaxError && err.message === 'Unterminated string: missing closing quote') {\n    // point at the offending line; fix quoting before retry\n  } else throw err\n}","preventionTips":["Always close quoted strings on the same line","Escape inner quotes as \\\" instead of relying on raw quotes","Let toon.encode handle quoting of values containing special characters","Check files for truncation when decode fails on the last line"],"tags":["syntax","toon","string","quoting"],"backgroundTag":"unterminated-string","analyzedSha":"604eac266e35166bed6f5b9e3bd586e9c60a9330","analyzedAt":"2026-08-31T11:09:25.043Z","schemaVersion":2},"datasetVersion":"2026-09-01T08:17:40.651Z"}