fix(parser): let with binding list on the next line is one declaration (#59) - #68
Merged
Conversation
#59) At statement start in non-strict JS, `let` followed by a newline then an identifier or `[` was mis-parsed: ASI fired after `let`, producing a bare `Identifier` ExpressionStatement plus a separate assignment, instead of a single LexicalDeclaration. There is no `[no LineTerminator here]` restriction between `let` and its BindingList — an identifier / `[` pattern / `\u`-escaped binding name continues the declaration, so ASI must not fire (the ExpressionStatement lookahead forbids `let [`, and `let <id>` has no valid expression parse). Treat those next-tokens as declarations even across a newline, matching espree / @typescript-eslint / Node. (`let\n {` already worked; reserved operator words like `instanceof` correctly remain expressions.) Fixes scope analysis (the binding is now a `let`, not an undeclared global assignment) and every rule keyed on VariableDeclaration (indent, no-var, prefer-const, one-var, …). Validated: full suite green incl. test262 3966/3966 must-parse · 1389/1389 must-reject (the ASI authority); babel 1928/1928 · 1548/1548; TS conformance 17910/17913 · 1210/1223; semantic sweep byte-identical, 0 crashes.
…stmt contexts The statement-list `let\n <id>` → declaration change leaked into single-statement bodies via delegation: `if (a) let\n x = 1`, `while`/`for`/`do` bodies, and labeled items (`lbl: let\n x`) wrongly became lexical declarations (forbidden there) instead of the `let` identifier expression. The single-statement handlers (parseNonDeclStatement/parseIfBody) and parseLabeledStatement delegated those cases to parseStatement, relying on the old expression contract — now broken. Parse the `let` expression directly (parseExprOrLabeledStatement) for the newline/non-binding cases in those contexts instead of re-dispatching through parseStatement. Verified across if/while/for/do bodies + single and nested labels: `let\n x` is an expression there, while top-level/block stays a declaration; same-line `let x` in those positions still errors as before. Tests: add the single-statement-context guard; tighten the binding ref count to exactly 2 and pin that the array-destructuring (`let\n [a] = b`) form binds `a`. Conformance unchanged (test262 3966/3966 · 1389/1389, babel 1928/1928, TS 17910/17913).
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.
Fixes #59.
Problem
At statement start in non-strict JS,
letfollowed by a newline then a binding was mis-parsed — ASI fired afterlet:es-parser produced
ExpressionStatement(Identifier "let")+ExpressionStatement(x = 1)(assignment to an undeclared global) — noVariableDeclaration. espree /@typescript-eslint/ Node all parse this as onelet x = 1.The
.kw_lethandler only treated{/let/yield/awaitas declarations across a newline; a plainidentifieror[fell through to the expression path.Fix
There is no
[no LineTerminator here]restriction betweenletand itsBindingList, so anidentifier/[-pattern /\u-escaped binding name continues the LexicalDeclaration — ASI must not fire. (TheExpressionStatementlookahead forbidslet [, andlet <id>has no valid expression parse.) Treat those next-tokens as declarations even across a newline.let\n {…}already worked; reserved operator words (instanceof,in, …) correctly remain expressions.Validation
let\n x = 1, multi-declaratorlet\n x = {}, y = {}, array/object destructuring on the next line all parse as one declaration with declarators;let\n instanceof xstays an expression; andxis now aletbinding (not an undeclared global) that usages resolve to.