{"record":{"id":"b5f6345504b97f19","repo":"toon-format/toon","slug":"unexpected-characters-after-closing-quote","errorCode":null,"errorMessage":"Unexpected characters after closing quote","messagePattern":"Unexpected characters after closing quote","errorType":"exception","errorClass":"SyntaxError","httpStatus":null,"severity":"error","filePath":"packages/toon/src/decode/parser.ts","lineNumber":479,"sourceCode":"    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) {\n    throw new SyntaxError('Missing colon after key')\n  }\n\n  return { key: trimSpaces(content.slice(start, colonIndex)), end: colonIndex + 1 }","sourceCodeStart":461,"sourceCodeEnd":497,"githubUrl":"https://github.com/toon-format/toon/blob/604eac266e35166bed6f5b9e3bd586e9c60a9330/packages/toon/src/decode/parser.ts#L461-L497","documentation":"A quoted string token had a valid closing quote, but characters remained after it (e.g. `\"abc\"xyz`). TOON requires a quoted string token to end exactly at its closing quote, so any trailing characters make the token invalid and parsing stops with a SyntaxError.","triggerScenarios":"Decoding input where a quoted token has trailing text — e.g. `name: \"Alice\" Smith`, array rows like `[\"a\"b,2]`, or a header field `\"col\"1`. Reached via parseStringLiteral when closingQuoteIndex !== trimmedToken.length - 1.","commonSituations":"Concatenating quoted values without delimiters; hand-editing strings and leaving stray characters; generator bugs that append suffixes (like an index) after the quote; copy-paste merging two tokens together.","solutions":["Remove the extra characters after the closing quote so the token is exactly `\"...\"`","Separate the quoted string and the following value with the proper delimiter (comma/semicolon/tab as per the document)","Quote only where necessary — unquoted plain values avoid this class of error","Generate output with toon.encode instead of hand-writing quoted tokens"],"exampleFix":"// before\nname: \"Alice\" Smith\n// after\nname: \"Alice\"","handlingStrategy":"try-catch","validationCode":"function noTrailingAfterQuotes(text) {\n  return !/[\"'][^\"']*\"[^,;\\t\\]}:\\n]/.test(text)\n}","typeGuard":null,"tryCatchPattern":"try {\n  const value = toon.decode(text)\n} catch (err) {\n  if (err instanceof SyntaxError && err.message.includes('after closing quote')) {\n    // strip/fix trailing characters after quoted tokens\n  } else throw err\n}","preventionTips":["End quoted tokens exactly at the closing quote","Separate values with the document's delimiter, not by juxtaposition","Avoid hand-writing quoted tokens; use toon.encode","Review paste operations that may merge adjacent tokens"],"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"}