fix(parser): handle escaped let in for loops (#26583)

Escaped `let` is an identifier in non-strict code, but `for` headers treated it as the declaration keyword or applied the restriction on expressions beginning with unescaped `let` in `for…of`. Check for escapes in both paths so valid loops such as `for (l\u0065t[0];;);` and `for (l\u0065t of []);` parse successfully.

Adds coverage for classic, `for…in`, `for…of`, and `for await…of` loops, while retaining rejection of escaped declaration keywords.
This commit is contained in:
camc314 committed 2026-09-13 06:00:15 +00:00
1 parent 2d1fad2c9f
commit 1c42008540
11 files changed
+48 -15

No files matched your search

+2 -2
View File
@@ -403,7 +403,7 @@ impl<'a, C: Config> ParserImpl<'a, C> {
r#await,
);
}
Kind::Let => {
Kind::Let if !self.cur_token().escaped() => {
// `for (let`
let decl_start = self.cur_start();
// disallow `for (let in ...`
@@ -460,7 +460,7 @@ impl<'a, C: Config> ParserImpl<'a, C> {
return self.parse_for_loop(for_start, parenthesis_opening_span, None, r#await);
}
let is_let = self.at(Kind::Let);
let is_let = self.at(Kind::Let) && !self.cur_token().escaped();
// `async` is allowed as `for (async of ...)` if `async` is escaped
let is_async = self.at(Kind::Async) && !self.cur_token().escaped();
let expr_start = self.cur_start();
@@ -0,0 +1 @@
for (l\u0065t x = 0;;);
@@ -0,0 +1 @@
for (l\u0065t x in {});
@@ -0,0 +1 @@
for (l\u0065t x of []);
@@ -0,0 +1,9 @@
for (l\u0065t[0];;);
for (l\u0065t[0] in {});
for (l\u0065t of []);
for (l\u0065t[0] of []);
for (l\u0065t.x of []);
async function f() {
for await (l\u0065t of []);
for await (l\u0065t[0] of []);
}
+2 -2
View File
@@ -1,3 +1,3 @@
codegen_misc Summary:
AST Parsed : 107/107 (100.00%)
Positive Passed: 107/107 (100.00%)
AST Parsed : 108/108 (100.00%)
Positive Passed: 108/108 (100.00%)
+2 -2
View File
@@ -1,3 +1,3 @@
formatter_misc Summary:
AST Parsed : 107/107 (100.00%)
Positive Passed: 107/107 (100.00%)
AST Parsed : 108/108 (100.00%)
Positive Passed: 108/108 (100.00%)
+2 -2
View File
@@ -1,3 +1,3 @@
lexer_misc Summary:
AST Parsed : 322/322 (100.00%)
Positive Passed: 322/322 (100.00%)
AST Parsed : 326/326 (100.00%)
Positive Passed: 326/326 (100.00%)
+24 -3
View File
@@ -1,7 +1,7 @@
parser_misc Summary:
AST Parsed : 107/107 (100.00%)
Positive Passed: 107/107 (100.00%)
Negative Passed: 215/215 (100.00%)
AST Parsed : 108/108 (100.00%)
Positive Passed: 108/108 (100.00%)
Negative Passed: 218/218 (100.00%)
× Cannot assign to 'arguments' in strict mode
╭─[misc/fail/arguments-eval-ambient.ts:2:13]
@@ -705,6 +705,27 @@ Negative Passed: 215/215 (100.00%)
3 │ }
╰────
× Expected `;` but found `Identifier`
╭─[misc/fail/for-escaped-let-declaration.cjs:1:15]
1 │ for (l\u0065t x = 0;;);
· ┬
· ╰── `;` expected
╰────
× Expected `;` but found `Identifier`
╭─[misc/fail/for-in-escaped-let-declaration.cjs:1:15]
1 │ for (l\u0065t x in {});
· ┬
· ╰── `;` expected
╰────
× Expected `;` but found `Identifier`
╭─[misc/fail/for-of-escaped-let-declaration.cjs:1:15]
1 │ for (l\u0065t x of []);
· ┬
· ╰── `;` expected
╰────
× The left-hand side of a for...in statement cannot be an using declaration.
╭─[misc/fail/for-using-in-initializer.js:1:6]
1 │ for (using x = a in b;;);
+2 -2
View File
@@ -1,6 +1,6 @@
semantic_misc Summary:
AST Parsed : 107/107 (100.00%)
Positive Passed: 103/107 (96.26%)
AST Parsed : 108/108 (100.00%)
Positive Passed: 104/108 (96.30%)
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 : 107/107 (100.00%)
Positive Passed: 107/107 (100.00%)
AST Parsed : 108/108 (100.00%)
Positive Passed: 108/108 (100.00%)