From a8690f44988d46beb135fe55c9a9c2841091a487 Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Fri, 13 Nov 2026 18:53:11 -0800 Subject: [PATCH] Recover decorated expressions with a missing identifier Malformed decorators can reach expression parsing without a following class. Returning a missing declaration puts a non-expression node in assignment and decorator expression slots. Use the existing missing identifier recovery node while retaining the expression diagnostic. Cover recovery in assignments, asynchronous iteration, and destructuring through compiler baselines. --- tsc/internal/parser/parser.go | 2 +- ...decoratorMissingClassExpression.errors.txt | 33 ++++++++++ .../decoratorMissingClassExpression.js | 63 +++++++++++++++++++ .../decoratorMissingClassExpression.symbols | 25 ++++++++ .../decoratorMissingClassExpression.types | 36 +++++++++++ .../decoratorOnArrowFunction.errors.txt | 6 +- .../conformance/decoratorOnArrowFunction.js | 5 +- .../decoratorOnArrowFunction.symbols | 2 +- .../decoratorOnArrowFunction.types | 6 +- .../decoratorOnFunctionExpression.symbols | 1 - .../decoratorOnFunctionExpression.types | 2 +- .../decoratorMissingClassExpression.ts | 15 +++++ 12 files changed, 183 insertions(+), 13 deletions(-) create mode 100644 tsc/testdata/baselines/reference/compiler/decoratorMissingClassExpression.errors.txt create mode 100644 tsc/testdata/baselines/reference/compiler/decoratorMissingClassExpression.js create mode 100644 tsc/testdata/baselines/reference/compiler/decoratorMissingClassExpression.symbols create mode 100644 tsc/testdata/baselines/reference/compiler/decoratorMissingClassExpression.types create mode 100644 tsc/testdata/tests/cases/compiler/decoratorMissingClassExpression.ts diff --git a/tsc/internal/parser/parser.go b/tsc/internal/parser/parser.go index a58db02e5c70f..e7837e8afffdb 100644 --- a/tsc/internal/parser/parser.go +++ b/tsc/internal/parser/parser.go @@ -5905,7 +5905,7 @@ func (p *Parser) parseDecoratedExpression() *ast.Expression { return p.parseClassDeclarationOrExpression(pos, jsdoc, modifiers, ast.KindClassExpression) } p.parseErrorAt(p.nodePos(), p.nodePos(), diagnostics.Expression_expected) - return p.finishNode(p.factory.NewMissingDeclaration(modifiers), pos) + return p.createMissingIdentifier() } func (p *Parser) unparseExpressionWithTypeArguments(expression *ast.Node, typeArguments *ast.NodeList, result *ast.Node) { diff --git a/tsc/testdata/baselines/reference/compiler/decoratorMissingClassExpression.errors.txt b/tsc/testdata/baselines/reference/compiler/decoratorMissingClassExpression.errors.txt new file mode 100644 index 0000000000000..1758cfc5744e4 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/decoratorMissingClassExpression.errors.txt @@ -0,0 +1,33 @@ +assignment.ts(2,9): error TS1109: Expression expected. +assignment.ts(2,10): error TS1005: ';' expected. +assignment.ts(3,8): error TS1109: Expression expected. +assignment.ts(3,9): error TS1005: ';' expected. +destructuring.ts(1,8): error TS1109: Expression expected. +forAwait.ts(2,18): error TS1109: Expression expected. + + +==== assignment.ts (4 errors) ==== + let x; + x = @dec 1; + +!!! error TS1109: Expression expected. + ~ +!!! error TS1005: ';' expected. + x = @@a 1; + +!!! error TS1109: Expression expected. + ~ +!!! error TS1005: ';' expected. + +==== forAwait.ts (1 errors) ==== + async function f(y: any) { + for await (@d of y) {} + +!!! error TS1109: Expression expected. + } + +==== destructuring.ts (1 errors) ==== + ({...@d} = {}); + +!!! error TS1109: Expression expected. + \ No newline at end of file diff --git a/tsc/testdata/baselines/reference/compiler/decoratorMissingClassExpression.js b/tsc/testdata/baselines/reference/compiler/decoratorMissingClassExpression.js new file mode 100644 index 0000000000000..02e3942892563 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/decoratorMissingClassExpression.js @@ -0,0 +1,63 @@ +//// [tests/cases/compiler/decoratorMissingClassExpression.ts] //// + +//// [assignment.ts] +let x; +x = @dec 1; +x = @@a 1; + +//// [forAwait.ts] +async function f(y: any) { + for await (@d of y) {} +} + +//// [destructuring.ts] +({...@d} = {}); + + +//// [assignment.js] +"use strict"; +let x; +x = ; +1; +x = ; +1; +//// [forAwait.js] +"use strict"; +var __asyncValues = (this && this.__asyncValues) || function (o) { + if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined."); + var m = o[Symbol.asyncIterator], i; + return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i); + function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; } + function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); } +}; +async function f(y) { + var _a, e_1, _b, _c; + try { + for (var _d = true, y_1 = __asyncValues(y), y_1_1; y_1_1 = await y_1.next(), _a = y_1_1.done, !_a; _d = true) { + _c = y_1_1.value; + _d = false; + = _c; + } + } + catch (e_1_1) { e_1 = { error: e_1_1 }; } + finally { + try { + if (!_d && !_a && (_b = y_1.return)) await _b.call(y_1); + } + finally { if (e_1) throw e_1.error; } + } +} +//// [destructuring.js] +"use strict"; +var __rest = (this && this.__rest) || function (s, e) { + var t = {}; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) + t[p] = s[p]; + if (s != null && typeof Object.getOwnPropertySymbols === "function") + for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { + if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) + t[p[i]] = s[p[i]]; + } + return t; +}; +( = __rest({}, [])); diff --git a/tsc/testdata/baselines/reference/compiler/decoratorMissingClassExpression.symbols b/tsc/testdata/baselines/reference/compiler/decoratorMissingClassExpression.symbols new file mode 100644 index 0000000000000..554fe9b5c6c3f --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/decoratorMissingClassExpression.symbols @@ -0,0 +1,25 @@ +//// [tests/cases/compiler/decoratorMissingClassExpression.ts] //// + +=== assignment.ts === +let x; +>x : Symbol(x, Decl(assignment.ts, 0, 3)) + +x = @dec 1; +>x : Symbol(x, Decl(assignment.ts, 0, 3)) + +x = @@a 1; +>x : Symbol(x, Decl(assignment.ts, 0, 3)) + +=== forAwait.ts === +async function f(y: any) { +>f : Symbol(f, Decl(forAwait.ts, 0, 0)) +>y : Symbol(y, Decl(forAwait.ts, 0, 17)) + + for await (@d of y) {} +>y : Symbol(y, Decl(forAwait.ts, 0, 17)) +} + +=== destructuring.ts === + +({...@d} = {}); + diff --git a/tsc/testdata/baselines/reference/compiler/decoratorMissingClassExpression.types b/tsc/testdata/baselines/reference/compiler/decoratorMissingClassExpression.types new file mode 100644 index 0000000000000..8f2395156cbee --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/decoratorMissingClassExpression.types @@ -0,0 +1,36 @@ +//// [tests/cases/compiler/decoratorMissingClassExpression.ts] //// + +=== assignment.ts === +let x; +>x : any + +x = @dec 1; +>x = @dec : any +>x : any +> : any +>1 : 1 + +x = @@a 1; +>x = @@a : any +>x : any +> : any +>1 : 1 + +=== forAwait.ts === +async function f(y: any) { +>f : (y: any) => Promise +>y : any + + for await (@d of y) {} +> : any +>y : any +} + +=== destructuring.ts === +({...@d} = {}); +>({...@d} = {}) : {} +>{...@d} = {} : {} +>{...@d} : any +> : any +>{} : {} + diff --git a/tsc/testdata/baselines/reference/conformance/decoratorOnArrowFunction.errors.txt b/tsc/testdata/baselines/reference/conformance/decoratorOnArrowFunction.errors.txt index 2c6258baa1541..1bede95a586da 100644 --- a/tsc/testdata/baselines/reference/conformance/decoratorOnArrowFunction.errors.txt +++ b/tsc/testdata/baselines/reference/conformance/decoratorOnArrowFunction.errors.txt @@ -1,5 +1,5 @@ decoratorOnArrowFunction.ts(3,16): error TS1109: Expression expected. -decoratorOnArrowFunction.ts(3,17): error TS1005: ';' expected. +decoratorOnArrowFunction.ts(3,16): error TS7006: Parameter '(Missing)' implicitly has an 'any' type. ==== decoratorOnArrowFunction.ts (2 errors) ==== @@ -8,6 +8,6 @@ decoratorOnArrowFunction.ts(3,17): error TS1005: ';' expected. var F = @dec () => { !!! error TS1109: Expression expected. - ~~ -!!! error TS1005: ';' expected. + +!!! error TS7006: Parameter '(Missing)' implicitly has an 'any' type. } \ No newline at end of file diff --git a/tsc/testdata/baselines/reference/conformance/decoratorOnArrowFunction.js b/tsc/testdata/baselines/reference/conformance/decoratorOnArrowFunction.js index 702f03a103a2d..58e3aa46cc15c 100644 --- a/tsc/testdata/baselines/reference/conformance/decoratorOnArrowFunction.js +++ b/tsc/testdata/baselines/reference/conformance/decoratorOnArrowFunction.js @@ -8,6 +8,5 @@ var F = @dec () => { //// [decoratorOnArrowFunction.js] "use strict"; -var F = ; -{ -} +var F = () => { +}; diff --git a/tsc/testdata/baselines/reference/conformance/decoratorOnArrowFunction.symbols b/tsc/testdata/baselines/reference/conformance/decoratorOnArrowFunction.symbols index b8138b7af46a9..78552e82fb68f 100644 --- a/tsc/testdata/baselines/reference/conformance/decoratorOnArrowFunction.symbols +++ b/tsc/testdata/baselines/reference/conformance/decoratorOnArrowFunction.symbols @@ -10,5 +10,5 @@ declare function dec(target: T): T; var F = @dec () => { >F : Symbol(F, Decl(decoratorOnArrowFunction.ts, 2, 3)) ->dec : Symbol(dec, Decl(decoratorOnArrowFunction.ts, 0, 0)) +> : Symbol((Missing), Decl(decoratorOnArrowFunction.ts, 2, 15)) } diff --git a/tsc/testdata/baselines/reference/conformance/decoratorOnArrowFunction.types b/tsc/testdata/baselines/reference/conformance/decoratorOnArrowFunction.types index b4bd9c4968ac0..c7fc3290b340a 100644 --- a/tsc/testdata/baselines/reference/conformance/decoratorOnArrowFunction.types +++ b/tsc/testdata/baselines/reference/conformance/decoratorOnArrowFunction.types @@ -6,7 +6,7 @@ declare function dec(target: T): T; >target : T var F = @dec () => { ->F : any ->dec () : unknown ->dec : (target: T) => T +>F : (: any) => void +>@dec () => {} : (: any) => void +> : any } diff --git a/tsc/testdata/baselines/reference/conformance/decoratorOnFunctionExpression.symbols b/tsc/testdata/baselines/reference/conformance/decoratorOnFunctionExpression.symbols index f504f567f7d22..01ff84d2cb565 100644 --- a/tsc/testdata/baselines/reference/conformance/decoratorOnFunctionExpression.symbols +++ b/tsc/testdata/baselines/reference/conformance/decoratorOnFunctionExpression.symbols @@ -10,6 +10,5 @@ declare function dec(target: T): T; var F = @dec function () { >F : Symbol(F, Decl(decoratorOnFunctionExpression.ts, 2, 3)) ->dec : Symbol(dec, Decl(decoratorOnFunctionExpression.ts, 0, 0)) > : Symbol((Missing), Decl(decoratorOnFunctionExpression.ts, 2, 12)) } diff --git a/tsc/testdata/baselines/reference/conformance/decoratorOnFunctionExpression.types b/tsc/testdata/baselines/reference/conformance/decoratorOnFunctionExpression.types index d732547403070..f549eb1a1edd6 100644 --- a/tsc/testdata/baselines/reference/conformance/decoratorOnFunctionExpression.types +++ b/tsc/testdata/baselines/reference/conformance/decoratorOnFunctionExpression.types @@ -7,6 +7,6 @@ declare function dec(target: T): T; var F = @dec function () { >F : any ->dec : (target: T) => T +> : any > : () => void } diff --git a/tsc/testdata/tests/cases/compiler/decoratorMissingClassExpression.ts b/tsc/testdata/tests/cases/compiler/decoratorMissingClassExpression.ts new file mode 100644 index 0000000000000..a91e7352423c2 --- /dev/null +++ b/tsc/testdata/tests/cases/compiler/decoratorMissingClassExpression.ts @@ -0,0 +1,15 @@ +// @target: es2017 +// @lib: esnext + +// @filename: assignment.ts +let x; +x = @dec 1; +x = @@a 1; + +// @filename: forAwait.ts +async function f(y: any) { + for await (@d of y) {} +} + +// @filename: destructuring.ts +({...@d} = {});