{"record":{"id":"680318ad4a4d6388","repo":"apple/pkl","slug":"closingstringdelimitermustbeginonnewline","errorCode":"closingStringDelimiterMustBeginOnNewLine","errorMessage":"The closing delimiter of a multi-line string must begin on a new line.","messagePattern":"The closing delimiter of a multi-line string must begin on a new line\\.","errorType":"error_code","errorClass":"ParserError","httpStatus":null,"severity":"error","filePath":"pkl-parser/src/main/java/org/pkl/parser/ParserImpl.java","lineNumber":1673,"sourceCode":"\n  private String parseUnicodeEscape(FullToken tk) {\n    var text = tk.text(lexer);\n    var lastIndex = text.length() - 1;\n    var startIndex = text.indexOf('{', 2);\n    try {\n      var codepoint = Integer.parseInt(text.substring(startIndex + 1, lastIndex), 16);\n      return Character.toString(codepoint);\n    } catch (NumberFormatException e) {\n      throw new ParserError(\n          ErrorMessages.create(\"invalidUnicodeEscapeSequence\", text, text.substring(0, startIndex)),\n          tk.span);\n    }\n  }\n\n  private String getCommonIndent(List<TempNode> nodes, Span span) {\n    var lastNode = nodes.get(nodes.size() - 1);\n    if (lastNode.token == null) {\n      throw new ParserError(\n          ErrorMessages.create(\"closingStringDelimiterMustBeginOnNewLine\"), lastNode.span());\n    }\n    if (lastNode.token.token == Token.STRING_NEWLINE) return \"\";\n    var beforeLast = nodes.get(nodes.size() - 2);\n    if (beforeLast.token != null && beforeLast.token.token == Token.STRING_NEWLINE) {\n      var indent = getTrailingIndent(lastNode);\n      if (indent != null) {\n        return indent;\n      }\n    }\n    throw new ParserError(ErrorMessages.create(\"closingStringDelimiterMustBeginOnNewLine\"), span);\n  }\n\n  private @Nullable String getTrailingIndent(TempNode node) {\n    var token = node.token;\n    if (token == null || token.token != Token.STRING_PART) return null;\n    var text = token.text(lexer);\n    for (var i = 0; i < text.length(); i++) {","sourceCodeStart":1655,"sourceCodeEnd":1691,"githubUrl":"https://github.com/apple/pkl/blob/f3efcbfc9b60d30053b0536d664948d7aa1b8673/pkl-parser/src/main/java/org/pkl/parser/ParserImpl.java#L1655-L1691","documentation":"In a Pkl multi-line string, the closing `\"\"\"` delimiter must sit on its own line. getCommonIndent inspects the last node of the string's content; if that node is a raw text part with no token boundary (meaning the closer shares a line with content, leaving no trailing-newline token), it throws closingStringDelimiterMustBeginOnNewLine at the last node's span.","triggerScenarios":"Parsing a multi-line string whose closing delimiter appears on the same line as text, e.g. `\"\"\"\n  hello\"\"\"` — the last content node lacks a token so the parser cannot compute the string's common indent.","commonSituations":"Writing Kotlin/Scala-style raw strings where the closer trails content; collapsing the final newline during copy-paste or minification; template engines trimming the trailing newline before the closer.","solutions":["Place the closing `\"\"\"` on its own new line after the last content line","Disable trimming of the final newline if a build/template step strips it before parsing","Reformat the string block so a line break precedes the closing delimiter"],"exampleFix":"// before\nmsg: \"\"\"\n  hello\"\"\"\n// after\nmsg: \"\"\"\n  hello\n  \"\"\"","handlingStrategy":"validation","validationCode":"function closingDelimiterOnOwnLine(src) {\n  return [...src.matchAll(/\"\"\"/g)].length % 2 === 0 &&\n    !/[^\\n]\"\"\"\\s*$|[^\\n]\"\"\"/m.test(src.split('\"\"\"')[1] ?? '');\n}\n// simpler: for each multi-line string, assert the closing '\"\"\"' is the first non-space token on its line","typeGuard":"null","tryCatchPattern":"try {\n  const result = parser.parse(source);\n} catch (e) {\n  if (e.errorId === 'closingStringDelimiterMustBeginOnNewLine') {\n    // insert a newline before the closing triple quote at e.span\n  } else { throw e; }\n}","preventionTips":["Always end multi-line string content with a newline before the closing \"\"\"","Turn off editor/template trimming that removes trailing newlines","Adopt the standard indented \"\"\" block style in your codebase"],"tags":["parser","pkl","multi-line-string","syntax-error"],"backgroundTag":"multiline-string-format-error","analyzedSha":"f3efcbfc9b60d30053b0536d664948d7aa1b8673","analyzedAt":"2026-09-08T13:10:45.570Z","contentChangedAt":"2026-09-08T13:10:45.570Z","schemaVersion":2},"datasetVersion":"2026-09-14T16:17:12.679Z"}