Repository navigation
fix!: keep syntax errors local to the mistake - #93
Merged
Merged
Conversation
Lex string fragments in the external scanner and refuse them during error recovery, which no longer swallows ordinary code such as closing braces. Share one closing-brace token between blocks and string interpolations so that a missing semicolon before `}` yields a MISSING node, skip raw whitespace as padding so that such a node sits right after the preceding token, and stop a single-line string literal at a line terminator with a token that no rule accepts. Co-authored-by: WillBooster (Claude Code) <agent@willbooster.com>
Co-authored-by: WillBooster (Claude Code) <agent@willbooster.com>
This was referenced Nov 3, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
A downstream app reports syntax errors to programming beginners from this parser's ERROR/MISSING nodes. On
main, many ordinary mistakes become one ERROR spanning the whole program, so the error is reported at line 1. For a missing semicolon, for example:yields
(program (ERROR ... (multiline_string_fragment [3, 3] - [6, 0]))). Three causes, confirmed withtree-sitter parse -d:[^"\\]{1,256}, …) match}and long spans of code, so recovery swallows the rest of the file.}token, distinct from the block}. After1, the lexer returned the interpolation}, so recovery could not insert a MISSING;before it.Requirements
Customer Summary
Syntax errors are now located at the mistake. On the downstream app's corpus, the first ERROR/MISSING node is on the mistake's line for every syntactic example except an unterminated block comment (see Notes):
;line 3;line 3;line 3)line 3)line 3]line 3]line 3}line 1}line 1Technical Summary
src/scanner.clexesstring_fragment,_non_u_string_fragment,_multiline_string_fragment, and_non_u_multiline_string_fragmentwith the same character sets as the former patterns and, like them, read at most 256 characters after any leading quotes. In text blocks, one or two leading quotes (raw or Unicode-escaped) belong to a fragment and three are left to the generated lexer as the closing delimiter. When_error_sentinelis valid (error recovery), no fragment or comment end is read.string_interpolationcloses with the block}token; its opening became the hidden token_string_interpolation_open, aliased to\{as before.extrasand skipped by the scanner before its own tokens. During recovery the scanner still returns it as_java_whitespace, which lets recovery resume after an unrecognized character such as a fullwidth;. Unicode-escaped whitespace stays a_java_whitespacetoken._error_sentinel. Otherwise the fallback lexing in error mode would read the line terminator as whitespace, which is valid between any tokens, and continue the literal on the next line.What to check hardest: that the scanner's fragment rules match the former token patterns exactly, especially quotes in text blocks. The corpus, string/text-block unit tests, and the pinned real-world examples (
script/parse-examples --check) cover this.Testing
bun run verifypassed.bun wb testpassed: all unit tests, including the native corpus and the real-world example check, and the e2e tests.test/unit/errorLocations.test.ts, which checks that the first ERROR/MISSING node is on the mistake's line for a missing semicolon, an unterminated string literal (raw and encoded line feed), a fullwidth semicolon, a two-character character literal, anelseafter an empty statement, and atrywithoutcatch.test/unit/performance.test.ts.Notes
/* unfinishedinsidemain) still yields an ERROR from line 1. The comment runs to EOF and hides both closing braces. At EOF, recovery can insert only one MISSING token, and a MISSING comment end does not let EOF be shifted, so recovery wraps the program from its start. Ending the comment early and re-lexing its body as code would rescan unterminated comments repeatedly and break the linear-time guarantee.1 + 2;(the downstream corpus'sstatementexample) parses without error because the grammar accepts any expression as a statement; it is not part of this change.