mirror of
https://github.com/oxc-project/oxc.git
synced 2026-11-04 10:29:58 +00:00
fix(parser): parse sloppy let expressions and labels (#28438)
Handle sloppy-mode `let` expressions, comma expressions, and labels through the ordinary statement parser while preserving lexical-declaration restrictions. Validated with scoped Rust checks, full conformance, parser bindings, allocation tracking. AI-assisted (Codex).
This commit is contained in:
1 parent
5238839466
commit
5b9a7bc735
12 files changed
+54
-196
No files matched your search
@@ -9,35 +9,22 @@ impl<'a, C: Config> ParserImpl<'a, C> {
|
||||
pub(crate) fn parse_let(&mut self, stmt_ctx: StatementContext) -> Statement<'a> {
|
||||
let start = self.cur_start();
|
||||
|
||||
let peeked = self.lexer.peek_token().kind();
|
||||
let next = self.lexer.peek_token();
|
||||
let peeked = next.kind();
|
||||
|
||||
// Fast path: avoid rewind.
|
||||
if !stmt_ctx.is_single_statement() && peeked.is_after_let() {
|
||||
// `let [` cannot start an expression statement, even in a single-statement
|
||||
// position. Otherwise, a declaration needs a binding after `let`.
|
||||
if peeked == Kind::LBrack
|
||||
|| (!stmt_ctx.is_single_statement()
|
||||
&& (peeked == Kind::LCurly
|
||||
|| peeked.is_binding_identifier()
|
||||
|| (!next.is_on_new_line() && peeked.is_after_let())))
|
||||
{
|
||||
self.bump_any(); // bump `let`
|
||||
return self.parse_variable_statement(start, VariableDeclarationKind::Let, stmt_ctx);
|
||||
}
|
||||
|
||||
// let = foo, let instanceof x, let + 1
|
||||
if peeked.is_assignment_operator() || peeked.is_binary_operator() {
|
||||
let expr = self.parse_assignment_expression_or_higher();
|
||||
self.parse_expression_statement(start, expr)
|
||||
// let.a = 1, let?.a = 1, let()[a] = 1, let++, let--
|
||||
} else if matches!(
|
||||
peeked,
|
||||
Kind::Dot | Kind::QuestionDot | Kind::LParen | Kind::Plus2 | Kind::Minus2
|
||||
) {
|
||||
let expr = self.parse_expr();
|
||||
self.parse_expression_statement(start, expr)
|
||||
// single statement let declaration: while (0) let
|
||||
} else if (stmt_ctx.is_single_statement() && peeked != Kind::LBrack)
|
||||
|| peeked == Kind::Semicolon
|
||||
{
|
||||
let expr = self.parse_identifier_expression();
|
||||
self.parse_expression_statement(start, expr)
|
||||
} else {
|
||||
self.bump_any();
|
||||
self.parse_variable_statement(start, VariableDeclarationKind::Let, stmt_ctx)
|
||||
}
|
||||
self.parse_expression_or_labeled_statement()
|
||||
}
|
||||
|
||||
pub(crate) fn is_using_statement(&mut self) -> bool {
|
||||
|
||||
@@ -263,7 +263,7 @@ impl<'a, C: Config> ParserImpl<'a, C> {
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_expression_or_labeled_statement(&mut self) -> Statement<'a> {
|
||||
pub(super) fn parse_expression_or_labeled_statement(&mut self) -> Statement<'a> {
|
||||
let start = self.cur_start();
|
||||
let expr = self.parse_expr();
|
||||
if let Expression::Identifier(ident) = &expr {
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
let, other;
|
||||
let ? yes : no;
|
||||
let: { break let; }
|
||||
let
|
||||
42;
|
||||
let
|
||||
const value = 1;
|
||||
let
|
||||
@@ -1,3 +1,3 @@
|
||||
codegen_misc Summary:
|
||||
AST Parsed : 128/128 (100.00%)
|
||||
Positive Passed: 128/128 (100.00%)
|
||||
AST Parsed : 129/129 (100.00%)
|
||||
Positive Passed: 129/129 (100.00%)
|
||||
@@ -1,3 +1,3 @@
|
||||
formatter_misc Summary:
|
||||
AST Parsed : 128/128 (100.00%)
|
||||
Positive Passed: 128/128 (100.00%)
|
||||
AST Parsed : 129/129 (100.00%)
|
||||
Positive Passed: 129/129 (100.00%)
|
||||
@@ -1,3 +1,3 @@
|
||||
lexer_misc Summary:
|
||||
AST Parsed : 391/391 (100.00%)
|
||||
Positive Passed: 391/391 (100.00%)
|
||||
AST Parsed : 392/392 (100.00%)
|
||||
Positive Passed: 392/392 (100.00%)
|
||||
@@ -1,8 +1,8 @@
|
||||
commit: b835f0c6
|
||||
|
||||
parser_babel Summary:
|
||||
AST Parsed : 2292/2304 (99.48%)
|
||||
Positive Passed: 2271/2304 (98.57%)
|
||||
AST Parsed : 2304/2304 (100.00%)
|
||||
Positive Passed: 2283/2304 (99.09%)
|
||||
Negative Passed: 1828/1843 (99.19%)
|
||||
Expect Syntax Error: tasks/coverage/babel/packages/babel-parser/test/fixtures/typescript/cast/unparenthesized-assert-and-assign/input.ts
|
||||
|
||||
@@ -34,15 +34,6 @@ Expect Syntax Error: tasks/coverage/babel/packages/babel-parser/test/fixtures/ty
|
||||
|
||||
Expect Syntax Error: tasks/coverage/babel/packages/babel-parser/test/fixtures/typescript/module-namespace/module-identifier-invalid/input.ts
|
||||
|
||||
Fatal error: tasks/coverage/babel/packages/babel-parser/test/fixtures/annex-b/enabled/1.1-html-comments-close-let-keyword/input.js
|
||||
|
||||
× Identifier expected. 'if' is a reserved word that cannot be used here.
|
||||
╭─[babel/packages/babel-parser/test/fixtures/annex-b/enabled/1.1-html-comments-close-let-keyword/input.js:3:1]
|
||||
2 │ */ --> c
|
||||
3 │ if (x) {}
|
||||
· ──
|
||||
╰────
|
||||
|
||||
Expect to Parse: tasks/coverage/babel/packages/babel-parser/test/fixtures/annex-b/enabled/1.1-html-comments-open-export-default-from/input.js
|
||||
|
||||
× Cannot use export statement outside a module
|
||||
@@ -52,15 +43,6 @@ Expect to Parse: tasks/coverage/babel/packages/babel-parser/test/fixtures/annex-
|
||||
2 │ "x";
|
||||
╰────
|
||||
|
||||
Fatal error: tasks/coverage/babel/packages/babel-parser/test/fixtures/annex-b/enabled/1.1-html-comments-open-let-keyword/input.js
|
||||
|
||||
× Identifier expected. 'if' is a reserved word that cannot be used here.
|
||||
╭─[babel/packages/babel-parser/test/fixtures/annex-b/enabled/1.1-html-comments-open-let-keyword/input.js:2:1]
|
||||
1 │ let <!-- c
|
||||
2 │ if (x) {}
|
||||
· ──
|
||||
╰────
|
||||
|
||||
Expect to Parse: tasks/coverage/babel/packages/babel-parser/test/fixtures/annex-b/enabled/3.3-function-in-if-body-lexical-redeclaration/input.js
|
||||
|
||||
× Identifier `f` has already been declared
|
||||
@@ -90,87 +72,6 @@ Expect to Parse: tasks/coverage/babel/packages/babel-parser/test/fixtures/es2015
|
||||
2 │ if (x) {}
|
||||
╰────
|
||||
|
||||
Fatal error: tasks/coverage/babel/packages/babel-parser/test/fixtures/es2015/let/let-with-linebreak-and-keyword/input.js
|
||||
|
||||
× Identifier expected. 'if' is a reserved word that cannot be used here.
|
||||
╭─[babel/packages/babel-parser/test/fixtures/es2015/let/let-with-linebreak-and-keyword/input.js:2:1]
|
||||
1 │ let
|
||||
2 │ if (x) {}
|
||||
· ──
|
||||
╰────
|
||||
|
||||
Fatal error: tasks/coverage/babel/packages/babel-parser/test/fixtures/es2015/let/let-with-linebreak-and-keyword-cr/input.js
|
||||
|
||||
× Identifier expected. 'if' is a reserved word that cannot be used here.
|
||||
╭─[babel/packages/babel-parser/test/fixtures/es2015/let/let-with-linebreak-and-keyword-cr/input.js:2:1]
|
||||
1 │ let
if (x) {}
|
||||
· ──
|
||||
╰────
|
||||
|
||||
Fatal error: tasks/coverage/babel/packages/babel-parser/test/fixtures/es2015/let/let-with-linebreak-and-keyword-declaration/input.js
|
||||
|
||||
× Identifier expected. 'function' is a reserved word that cannot be used here.
|
||||
╭─[babel/packages/babel-parser/test/fixtures/es2015/let/let-with-linebreak-and-keyword-declaration/input.js:2:1]
|
||||
1 │ let
|
||||
2 │ function f() {}
|
||||
· ────────
|
||||
3 │ let
|
||||
╰────
|
||||
|
||||
Fatal error: tasks/coverage/babel/packages/babel-parser/test/fixtures/es2015/let/let-with-linebreak-and-keyword-expression/input.js
|
||||
|
||||
× Identifier expected. 'this' is a reserved word that cannot be used here.
|
||||
╭─[babel/packages/babel-parser/test/fixtures/es2015/let/let-with-linebreak-and-keyword-expression/input.js:2:1]
|
||||
1 │ let // comment
|
||||
2 │ this.x = 1;
|
||||
· ────
|
||||
3 │ let /*
|
||||
╰────
|
||||
|
||||
Fatal error: tasks/coverage/babel/packages/babel-parser/test/fixtures/es2015/let/let-with-linebreak-and-keyword-line-separator/input.js
|
||||
|
||||
× Identifier expected. 'if' is a reserved word that cannot be used here.
|
||||
╭─[babel/packages/babel-parser/test/fixtures/es2015/let/let-with-linebreak-and-keyword-line-separator/input.js:1:7]
|
||||
1 │ let
if (x) {}
|
||||
· ──
|
||||
╰────
|
||||
|
||||
Fatal error: tasks/coverage/babel/packages/babel-parser/test/fixtures/es2015/let/let-with-linebreak-and-keyword-nested/input.js
|
||||
|
||||
× Identifier expected. 'if' is a reserved word that cannot be used here.
|
||||
╭─[babel/packages/babel-parser/test/fixtures/es2015/let/let-with-linebreak-and-keyword-nested/input.js:3:3]
|
||||
2 │ let
|
||||
3 │ if (x) {}
|
||||
· ──
|
||||
4 │ }
|
||||
╰────
|
||||
|
||||
Fatal error: tasks/coverage/babel/packages/babel-parser/test/fixtures/es2015/let/let-with-linebreak-and-keyword-paragraph-separator/input.js
|
||||
|
||||
× Identifier expected. 'if' is a reserved word that cannot be used here.
|
||||
╭─[babel/packages/babel-parser/test/fixtures/es2015/let/let-with-linebreak-and-keyword-paragraph-separator/input.js:1:7]
|
||||
1 │ let
if (x) {}
|
||||
· ──
|
||||
╰────
|
||||
|
||||
Fatal error: tasks/coverage/babel/packages/babel-parser/test/fixtures/es2015/let/let-with-linebreak-and-void/input.js
|
||||
|
||||
× Identifier expected. 'void' is a reserved word that cannot be used here.
|
||||
╭─[babel/packages/babel-parser/test/fixtures/es2015/let/let-with-linebreak-and-void/input.js:2:1]
|
||||
1 │ let
|
||||
2 │ void 0;
|
||||
· ────
|
||||
╰────
|
||||
|
||||
Fatal error: tasks/coverage/babel/packages/babel-parser/test/fixtures/es2015/let/let-with-multiline-comment-and-keyword/input.js
|
||||
|
||||
× Identifier expected. 'if' is a reserved word that cannot be used here.
|
||||
╭─[babel/packages/babel-parser/test/fixtures/es2015/let/let-with-multiline-comment-and-keyword/input.js:2:4]
|
||||
1 │ let /*
|
||||
2 │ */ if (x) {}
|
||||
· ──
|
||||
╰────
|
||||
|
||||
Expect to Parse: tasks/coverage/babel/packages/babel-parser/test/fixtures/estree/class-private-method/typescript-invalid-abstract/input.ts
|
||||
|
||||
× TS(18019): 'abstract' modifier cannot be used with a private identifier.
|
||||
@@ -424,15 +325,6 @@ Expect to Parse: tasks/coverage/babel/packages/babel-parser/test/fixtures/typesc
|
||||
6 │ }
|
||||
╰────
|
||||
|
||||
Fatal error: tasks/coverage/babel/packages/babel-parser/test/fixtures/typescript/enum/let-with-linebreak/input.js
|
||||
|
||||
× Identifier expected. 'enum' is a reserved word that cannot be used here.
|
||||
╭─[babel/packages/babel-parser/test/fixtures/typescript/enum/let-with-linebreak/input.js:2:1]
|
||||
1 │ let
|
||||
2 │ enum E {}
|
||||
· ────
|
||||
╰────
|
||||
|
||||
Expect to Parse: tasks/coverage/babel/packages/babel-parser/test/fixtures/typescript/function/declare-pattern-parameters/input.ts
|
||||
|
||||
× TS(1016): A required parameter cannot follow an optional parameter.
|
||||
@@ -3771,33 +3663,36 @@ Expect to Parse: tasks/coverage/babel/packages/babel-parser/test/fixtures/typesc
|
||||
╰────
|
||||
help: Try inserting a semicolon here
|
||||
|
||||
× Identifier expected. 'enum' is a reserved word that cannot be used here.
|
||||
× Unexpected token
|
||||
╭─[babel/packages/babel-parser/test/fixtures/es2015/let/let-with-linebreak-and-enum/input.js:2:1]
|
||||
1 │ let
|
||||
2 │ enum;
|
||||
· ────
|
||||
╰────
|
||||
|
||||
× Identifier expected. 'if' is a reserved word that cannot be used here.
|
||||
× Keywords cannot contain escape characters
|
||||
╭─[babel/packages/babel-parser/test/fixtures/es2015/let/let-with-linebreak-and-escaped-keyword/input.js:2:1]
|
||||
1 │ let
|
||||
2 │ i\u0066 (x) {}
|
||||
· ───────
|
||||
╰────
|
||||
|
||||
× Identifier expected. 'if' is a reserved word that cannot be used here.
|
||||
╭─[babel/packages/babel-parser/test/fixtures/es2015/let/let-with-linebreak-and-keyword-module/input.js:2:1]
|
||||
× The keyword 'let' is reserved
|
||||
╭─[babel/packages/babel-parser/test/fixtures/es2015/let/let-with-linebreak-and-keyword-module/input.js:1:1]
|
||||
1 │ let
|
||||
· ───
|
||||
2 │ if (x) {}
|
||||
· ──
|
||||
╰────
|
||||
note: Modules are always strict mode code
|
||||
|
||||
× Identifier expected. 'if' is a reserved word that cannot be used here.
|
||||
╭─[babel/packages/babel-parser/test/fixtures/es2015/let/let-with-linebreak-and-keyword-strict/input.js:3:1]
|
||||
× The keyword 'let' is reserved
|
||||
╭─[babel/packages/babel-parser/test/fixtures/es2015/let/let-with-linebreak-and-keyword-strict/input.js:2:1]
|
||||
1 │ "use strict";
|
||||
2 │ let
|
||||
· ───
|
||||
3 │ if (x) {}
|
||||
· ──
|
||||
╰────
|
||||
note: This identifier is reserved in strict mode code
|
||||
|
||||
× Expected a semicolon or an implicit semicolon after a statement, but found none
|
||||
╭─[babel/packages/babel-parser/test/fixtures/es2015/let/let-with-linebreak-and-yield/input.js:3:8]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
parser_misc Summary:
|
||||
AST Parsed : 128/128 (100.00%)
|
||||
Positive Passed: 128/128 (100.00%)
|
||||
AST Parsed : 129/129 (100.00%)
|
||||
Positive Passed: 129/129 (100.00%)
|
||||
Negative Passed: 265/265 (100.00%)
|
||||
|
||||
× TS(1275): 'accessor' modifier can only appear on a property declaration.
|
||||
|
||||
@@ -6544,11 +6544,13 @@ Expect to Parse: tasks/coverage/typescript/tsc/testdata/tests/cases/conformance/
|
||||
1 │ const
|
||||
╰────
|
||||
|
||||
× Unexpected token
|
||||
╭─[typescript/tsc/testdata/tests/cases/compiler/downlevelLetConst11.ts:2:4]
|
||||
× The keyword 'let' is reserved
|
||||
╭─[typescript/tsc/testdata/tests/cases/compiler/downlevelLetConst11.ts:2:1]
|
||||
1 │ "use strict";
|
||||
2 │ let
|
||||
· ───
|
||||
╰────
|
||||
note: This identifier is reserved in strict mode code
|
||||
|
||||
× Missing initializer in const declaration
|
||||
╭─[typescript/tsc/testdata/tests/cases/compiler/downlevelLetConst2.ts:1:7]
|
||||
@@ -21757,11 +21759,13 @@ Expect to Parse: tasks/coverage/typescript/tsc/testdata/tests/cases/conformance/
|
||||
· ────
|
||||
╰────
|
||||
|
||||
× Unexpected token
|
||||
╭─[typescript/tsc/testdata/tests/cases/conformance/es6/variableDeclarations/VariableDeclaration11_es6.ts:2:4]
|
||||
× The keyword 'let' is reserved
|
||||
╭─[typescript/tsc/testdata/tests/cases/conformance/es6/variableDeclarations/VariableDeclaration11_es6.ts:2:1]
|
||||
1 │ "use strict";
|
||||
2 │ let
|
||||
· ───
|
||||
╰────
|
||||
note: This identifier is reserved in strict mode code
|
||||
|
||||
× Unexpected token
|
||||
╭─[typescript/tsc/testdata/tests/cases/conformance/es6/variableDeclarations/VariableDeclaration13_es6.ts:4:5]
|
||||
|
||||
@@ -2,16 +2,10 @@ commit: b835f0c6
|
||||
|
||||
semantic_babel Summary:
|
||||
AST Parsed : 2304/2304 (100.00%)
|
||||
Positive Passed: 2197/2304 (95.36%)
|
||||
semantic Error: tasks/coverage/babel/packages/babel-parser/test/fixtures/annex-b/enabled/1.1-html-comments-close-let-keyword/input.js
|
||||
Identifier expected. 'if' is a reserved word that cannot be used here.
|
||||
|
||||
Positive Passed: 2209/2304 (95.88%)
|
||||
semantic Error: tasks/coverage/babel/packages/babel-parser/test/fixtures/annex-b/enabled/1.1-html-comments-open-export-default-from/input.js
|
||||
Cannot use export statement outside a module
|
||||
|
||||
semantic Error: tasks/coverage/babel/packages/babel-parser/test/fixtures/annex-b/enabled/1.1-html-comments-open-let-keyword/input.js
|
||||
Identifier expected. 'if' is a reserved word that cannot be used here.
|
||||
|
||||
semantic Error: tasks/coverage/babel/packages/babel-parser/test/fixtures/annex-b/enabled/3.3-function-in-if-body-lexical-redeclaration/input.js
|
||||
Identifier `f` has already been declared
|
||||
Identifier `g` has already been declared
|
||||
@@ -29,33 +23,6 @@ rebuilt : SymbolId(3): SymbolFlags(BlockScopedVariable)
|
||||
semantic Error: tasks/coverage/babel/packages/babel-parser/test/fixtures/es2015/let/export-default-let-with-linebreak-and-keyword/input.js
|
||||
Cannot use export statement outside a module
|
||||
|
||||
semantic Error: tasks/coverage/babel/packages/babel-parser/test/fixtures/es2015/let/let-with-linebreak-and-keyword/input.js
|
||||
Identifier expected. 'if' is a reserved word that cannot be used here.
|
||||
|
||||
semantic Error: tasks/coverage/babel/packages/babel-parser/test/fixtures/es2015/let/let-with-linebreak-and-keyword-cr/input.js
|
||||
Identifier expected. 'if' is a reserved word that cannot be used here.
|
||||
|
||||
semantic Error: tasks/coverage/babel/packages/babel-parser/test/fixtures/es2015/let/let-with-linebreak-and-keyword-declaration/input.js
|
||||
Identifier expected. 'function' is a reserved word that cannot be used here.
|
||||
|
||||
semantic Error: tasks/coverage/babel/packages/babel-parser/test/fixtures/es2015/let/let-with-linebreak-and-keyword-expression/input.js
|
||||
Identifier expected. 'this' is a reserved word that cannot be used here.
|
||||
|
||||
semantic Error: tasks/coverage/babel/packages/babel-parser/test/fixtures/es2015/let/let-with-linebreak-and-keyword-line-separator/input.js
|
||||
Identifier expected. 'if' is a reserved word that cannot be used here.
|
||||
|
||||
semantic Error: tasks/coverage/babel/packages/babel-parser/test/fixtures/es2015/let/let-with-linebreak-and-keyword-nested/input.js
|
||||
Identifier expected. 'if' is a reserved word that cannot be used here.
|
||||
|
||||
semantic Error: tasks/coverage/babel/packages/babel-parser/test/fixtures/es2015/let/let-with-linebreak-and-keyword-paragraph-separator/input.js
|
||||
Identifier expected. 'if' is a reserved word that cannot be used here.
|
||||
|
||||
semantic Error: tasks/coverage/babel/packages/babel-parser/test/fixtures/es2015/let/let-with-linebreak-and-void/input.js
|
||||
Identifier expected. 'void' is a reserved word that cannot be used here.
|
||||
|
||||
semantic Error: tasks/coverage/babel/packages/babel-parser/test/fixtures/es2015/let/let-with-multiline-comment-and-keyword/input.js
|
||||
Identifier expected. 'if' is a reserved word that cannot be used here.
|
||||
|
||||
semantic Error: tasks/coverage/babel/packages/babel-parser/test/fixtures/es2022/class-private-properties/await-in-private-property-in-params-of-async-arrow/input.js
|
||||
Bindings mismatch:
|
||||
after transform: ScopeId(0): ["_asyncToGenerator", "_classPrivateFieldInitSpec"]
|
||||
@@ -249,9 +216,6 @@ Bindings mismatch:
|
||||
after transform: ScopeId(0): ["E"]
|
||||
rebuilt : ScopeId(0): []
|
||||
|
||||
semantic Error: tasks/coverage/babel/packages/babel-parser/test/fixtures/typescript/enum/let-with-linebreak/input.js
|
||||
Identifier expected. 'enum' is a reserved word that cannot be used here.
|
||||
|
||||
semantic Error: tasks/coverage/babel/packages/babel-parser/test/fixtures/typescript/export/declare/input.ts
|
||||
Bindings mismatch:
|
||||
after transform: ScopeId(0): ["C", "N", "_bar", "bar", "f", "foo", "x"]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
semantic_misc Summary:
|
||||
AST Parsed : 128/128 (100.00%)
|
||||
Positive Passed: 122/128 (95.31%)
|
||||
AST Parsed : 129/129 (100.00%)
|
||||
Positive Passed: 123/129 (95.35%)
|
||||
semantic Error: tasks/coverage/misc/pass/declare-let-private.ts
|
||||
Bindings mismatch:
|
||||
after transform: ScopeId(0): ["private"]
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
transformer_misc Summary:
|
||||
AST Parsed : 128/128 (100.00%)
|
||||
Positive Passed: 128/128 (100.00%)
|
||||
AST Parsed : 129/129 (100.00%)
|
||||
Positive Passed: 129/129 (100.00%)
|
||||
Reference in new issue
Block a user