{"record":{"id":"510641559338d86f","repo":"apple/pkl","slug":"closingstringdelimitermustbeginonnewline-510641","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":"exception","errorClass":"GenericParserError","httpStatus":null,"severity":"error","filePath":"pkl-parser/src/main/java/org/pkl/parser/GenericParserImpl.java","lineNumber":1049,"sourceCode":"        }\n        case EOF -> {\n          var delimiter = new StringBuilder(start.text(lexer)).reverse().toString();\n          throw parserError(\"missingDelimiter\", delimiter);\n        }\n      }\n    }\n    children.add(makeTerminal(next())); // string end\n    validateStringEndDelimiter(children);\n    validateStringIndentation(children);\n    return new Node(NodeType.MULTI_LINE_STRING_LITERAL_EXPR, children);\n  }\n\n  private void validateStringEndDelimiter(List<Node> nodes) {\n    var beforeLast = nodes.get(nodes.size() - 2);\n    if (beforeLast.type == NodeType.STRING_NEWLINE) return;\n    var text = beforeLast.text(lexer.getSource());\n    if (!text.isBlank()) {\n      throw parserError(\n          ErrorMessages.create(\"closingStringDelimiterMustBeginOnNewLine\"), beforeLast.span);\n    }\n  }\n\n  private void validateStringIndentation(List<Node> nodes) {\n    var indentNode = nodes.get(nodes.size() - 2);\n    if (indentNode.type == NodeType.STRING_NEWLINE) return;\n    var indent = indentNode.text(lexer.getSource());\n    var previousNewline = false;\n    for (var i = 1; i < nodes.size() - 2; i++) {\n      var child = nodes.get(i);\n      if (child.type != NodeType.STRING_NEWLINE && previousNewline) {\n        var text = child.text(lexer.getSource());\n        if (!text.startsWith(indent)) {\n          throw parserError(ErrorMessages.create(\"stringIndentationMustMatchLastLine\"), child.span);\n        }\n      }\n      previousNewline =","sourceCodeStart":1031,"sourceCodeEnd":1067,"githubUrl":"https://github.com/apple/pkl/blob/f3efcbfc9b60d30053b0536d664948d7aa1b8673/pkl-parser/src/main/java/org/pkl/parser/GenericParserImpl.java#L1031-L1067","documentation":"Pkl requires the closing `\"\"\"` of a multi-line string to start on its own line: the token immediately before the closer must be a STRING_NEWLINE. validateStringEndDelimiter inspects the second-to-last node; if it is not a newline and its text is not blank, the error is thrown at that node's span.","triggerScenarios":"Writing content and the closing `\"\"\"` on the same line, e.g. `\"\"\"\\n  hello\"\"\"` instead of `\"\"\"\\n  hello\\n  \"\"\"`.","commonSituations":"Condensing multi-line strings to save lines; auto-formatters or editors joining lines; code generation emitting `value\"\"\"` on one line.","solutions":["Move the closing `\"\"\"` onto its own new line.","If a single-line string suffices, switch to `\"...\"` syntax.","Fix templates/generators to always emit a newline before the closing delimiter."],"exampleFix":"// before\nmsg = \"\"\"\n  hello\"\"\"\n\n// after\nmsg = \"\"\"\n  hello\n  \"\"\"","handlingStrategy":"validation","validationCode":"// Closing \"\"\" must be alone on its line (only indentation before it)\nfunction closingDelimiterOnOwnLine(src) {\n  return !/[^\\n]\"\"\"\\s*(\\n|$)/.test(src.replace(/\\\\\"/g, ''));\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Keep the closing `\"\"\"` on its own line, aligned with the block's indentation.","Disable editor settings that join lines when saving .pkl files.","Add a lint rule forbidding non-whitespace before the closing delimiter."],"tags":["pkl","parser","syntax-error","multiline-string"],"backgroundTag":"invalid-argument-format","analyzedSha":"f3efcbfc9b60d30053b0536d664948d7aa1b8673","analyzedAt":"2026-09-08T13:10:45.570Z","contentChangedAt":"2026-09-08T13:10:45.570Z","schemaVersion":2},"datasetVersion":"2026-09-17T15:17:12.973Z"}