apple/pkl · error · GenericParserError
closingStringDelimiterMustBeginOnNewLine
closingStringDelimiterMustBeginOnNewLine
Error message
The closing delimiter of a multi-line string must begin on a new line.
What it means
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.
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.
Example fix
// before msg = """ hello""" // after msg = """ hello """
Defensive patterns
Strategy: validation
Validate before calling
// Closing """ must be alone on its line (only indentation before it)
function closingDelimiterOnOwnLine(src) {
return !/[^\n]"""\s*(\n|$)/.test(src.replace(/\\"/g, ''));
} Prevention
- 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.
When it happens
Trigger: Writing content and the closing `"""` on the same line, e.g. `"""\n hello"""` instead of `"""\n hello\n """`.
Common situations: Condensing multi-line strings to save lines; auto-formatters or editors joining lines; code generation emitting `value"""` on one line.
Understand the failure class
Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.
Related errors
- stringContentMustBeginOnNewLine
- '" + ch + "'
- closingStringDelimiterMustBeginOnNewLine
- ':'
- ErrorMessages.create(errorKey, args)
AI-assisted analysis of apple/pkl@f3efcbfc9b (2026-09-08).
Data as JSON: /api/errors/510641559338d86f.
Report an issue: GitHub.
Appendix: source
Thrown at pkl-parser/src/main/java/org/pkl/parser/GenericParserImpl.java:1049
}
case EOF -> {
var delimiter = new StringBuilder(start.text(lexer)).reverse().toString();
throw parserError("missingDelimiter", delimiter);
}
}
}
children.add(makeTerminal(next())); // string end
validateStringEndDelimiter(children);
validateStringIndentation(children);
return new Node(NodeType.MULTI_LINE_STRING_LITERAL_EXPR, children);
}
private void validateStringEndDelimiter(List<Node> nodes) {
var beforeLast = nodes.get(nodes.size() - 2);
if (beforeLast.type == NodeType.STRING_NEWLINE) return;
var text = beforeLast.text(lexer.getSource());
if (!text.isBlank()) {
throw parserError(
ErrorMessages.create("closingStringDelimiterMustBeginOnNewLine"), beforeLast.span);
}
}
private void validateStringIndentation(List<Node> nodes) {
var indentNode = nodes.get(nodes.size() - 2);
if (indentNode.type == NodeType.STRING_NEWLINE) return;
var indent = indentNode.text(lexer.getSource());
var previousNewline = false;
for (var i = 1; i < nodes.size() - 2; i++) {
var child = nodes.get(i);
if (child.type != NodeType.STRING_NEWLINE && previousNewline) {
var text = child.text(lexer.getSource());
if (!text.startsWith(indent)) {
throw parserError(ErrorMessages.create("stringIndentationMustMatchLastLine"), child.span);
}
}
previousNewline =View on GitHub (pinned to f3efcbfc9b)