Skip to content

Commit 5e8f900

Browse files
authored
Report grammar errors for invalid decorator grammar (#57749)
1 parent c1f0f7c commit 5e8f900

File tree

34 files changed

+1550
-47
lines changed

34 files changed

+1550
-47
lines changed

src/compiler/checker.ts

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -651,6 +651,7 @@ import {
651651
isNewExpression,
652652
isNodeDescendantOf,
653653
isNonNullAccess,
654+
isNonNullExpression,
654655
isNullishCoalesce,
655656
isNumericLiteral,
656657
isNumericLiteralName,
@@ -41572,8 +41573,82 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
4157241573
}
4157341574
}
4157441575

41576+
function checkGrammarDecorator(decorator: Decorator): boolean {
41577+
const sourceFile = getSourceFileOfNode(decorator);
41578+
if (!hasParseDiagnostics(sourceFile)) {
41579+
let node: Expression = decorator.expression;
41580+
41581+
// DecoratorParenthesizedExpression :
41582+
// `(` Expression `)`
41583+
41584+
if (isParenthesizedExpression(node)) {
41585+
return false;
41586+
}
41587+
41588+
let canHaveCallExpression = true;
41589+
let errorNode: Node | undefined;
41590+
while (true) {
41591+
// Allow TS syntax such as non-null assertions and instantiation expressions
41592+
if (isExpressionWithTypeArguments(node) || isNonNullExpression(node)) {
41593+
node = node.expression;
41594+
continue;
41595+
}
41596+
41597+
// DecoratorCallExpression :
41598+
// DecoratorMemberExpression Arguments
41599+
41600+
if (isCallExpression(node)) {
41601+
if (!canHaveCallExpression) {
41602+
errorNode = node;
41603+
}
41604+
if (node.questionDotToken) {
41605+
// Even if we already have an error node, error at the `?.` token since it appears earlier.
41606+
errorNode = node.questionDotToken;
41607+
}
41608+
node = node.expression;
41609+
canHaveCallExpression = false;
41610+
continue;
41611+
}
41612+
41613+
// DecoratorMemberExpression :
41614+
// IdentifierReference
41615+
// DecoratorMemberExpression `.` IdentifierName
41616+
// DecoratorMemberExpression `.` PrivateIdentifier
41617+
41618+
if (isPropertyAccessExpression(node)) {
41619+
if (node.questionDotToken) {
41620+
// Even if we already have an error node, error at the `?.` token since it appears earlier.
41621+
errorNode = node.questionDotToken;
41622+
}
41623+
node = node.expression;
41624+
canHaveCallExpression = false;
41625+
continue;
41626+
}
41627+
41628+
if (!isIdentifier(node)) {
41629+
// Even if we already have an error node, error at this node since it appears earlier.
41630+
errorNode = node;
41631+
}
41632+
41633+
break;
41634+
}
41635+
41636+
if (errorNode) {
41637+
addRelatedInfo(
41638+
error(decorator.expression, Diagnostics.Expression_must_be_enclosed_in_parentheses_to_be_used_as_a_decorator),
41639+
createDiagnosticForNode(errorNode, Diagnostics.Invalid_syntax_in_decorator),
41640+
);
41641+
return true;
41642+
}
41643+
}
41644+
41645+
return false;
41646+
}
41647+
4157541648
/** Check a decorator */
4157641649
function checkDecorator(node: Decorator): void {
41650+
checkGrammarDecorator(node);
41651+
4157741652
const signature = getResolvedSignature(node);
4157841653
checkDeprecatedSignature(signature, node);
4157941654
const returnType = getReturnTypeOfSignature(signature);

src/compiler/diagnosticMessages.json

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1637,6 +1637,14 @@
16371637
"category": "Error",
16381638
"code": 1496
16391639
},
1640+
"Expression must be enclosed in parentheses to be used as a decorator.": {
1641+
"category": "Error",
1642+
"code": 1497
1643+
},
1644+
"Invalid syntax in decorator.": {
1645+
"category": "Error",
1646+
"code": 1498
1647+
},
16401648

16411649
"The types of '{0}' are incompatible between these types.": {
16421650
"category": "Error",
@@ -7792,6 +7800,14 @@
77927800
"category": "Message",
77937801
"code": 95193
77947802
},
7803+
"Wrap in parentheses": {
7804+
"category": "Message",
7805+
"code": 95194
7806+
},
7807+
"Wrap all invalid decorator expressions in parentheses": {
7808+
"category": "Message",
7809+
"code": 95195
7810+
},
77957811

77967812
"No value exists in scope for the shorthand property '{0}'. Either declare one or provide an initializer.": {
77977813
"category": "Error",

src/services/_namespaces/ts.codefix.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ export * from "../codefixes/useDefaultImport";
6464
export * from "../codefixes/useBigintLiteral";
6565
export * from "../codefixes/fixAddModuleReferTypeMissingTypeof";
6666
export * from "../codefixes/wrapJsxInFragment";
67+
export * from "../codefixes/wrapDecoratorInParentheses";
6768
export * from "../codefixes/convertToMappedObjectType";
6869
export * from "../codefixes/removeAccidentalCallParentheses";
6970
export * from "../codefixes/removeUnnecessaryAwait";
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import {
2+
Debug,
3+
Diagnostics,
4+
factory,
5+
findAncestor,
6+
getTokenAtPosition,
7+
isDecorator,
8+
SourceFile,
9+
textChanges,
10+
} from "../_namespaces/ts";
11+
import {
12+
codeFixAll,
13+
createCodeFixAction,
14+
registerCodeFix,
15+
} from "../_namespaces/ts.codefix";
16+
17+
const fixId = "wrapDecoratorInParentheses";
18+
const errorCodes = [Diagnostics.Expression_must_be_enclosed_in_parentheses_to_be_used_as_a_decorator.code];
19+
registerCodeFix({
20+
errorCodes,
21+
getCodeActions: function getCodeActionsToWrapDecoratorExpressionInParentheses(context) {
22+
const changes = textChanges.ChangeTracker.with(context, t => makeChange(t, context.sourceFile, context.span.start));
23+
return [createCodeFixAction(fixId, changes, Diagnostics.Wrap_in_parentheses, fixId, Diagnostics.Wrap_all_invalid_decorator_expressions_in_parentheses)];
24+
},
25+
fixIds: [fixId],
26+
getAllCodeActions: context => codeFixAll(context, errorCodes, (changes, diag) => makeChange(changes, diag.file, diag.start)),
27+
});
28+
29+
function makeChange(changeTracker: textChanges.ChangeTracker, sourceFile: SourceFile, pos: number) {
30+
const token = getTokenAtPosition(sourceFile, pos);
31+
const decorator = findAncestor(token, isDecorator)!;
32+
Debug.assert(!!decorator, "Expected position to be owned by a decorator.");
33+
const replacement = factory.createParenthesizedExpression(decorator.expression);
34+
changeTracker.replaceNode(sourceFile, decorator.expression, replacement);
35+
}

src/testRunner/compilerRunner.ts

Lines changed: 27 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -128,42 +128,43 @@ export class CompilerBaselineRunner extends RunnerBase {
128128

129129
class CompilerTest {
130130
private static varyBy: readonly string[] = [
131-
"module",
132-
"moduleResolution",
133-
"moduleDetection",
131+
"allowArbitraryExtensions",
134132
"allowImportingTsExtensions",
135-
"target",
136-
"jsx",
137-
"noEmit",
138-
"removeComments",
133+
"allowSyntheticDefaultImports",
134+
"alwaysStrict",
135+
"downlevelIteration",
136+
"experimentalDecorators",
137+
"emitDecoratorMetadata",
138+
"esModuleInterop",
139+
"exactOptionalPropertyTypes",
139140
"importHelpers",
140141
"importHelpers",
141-
"downlevelIteration",
142142
"isolatedModules",
143-
"verbatimModuleSyntax",
144-
"strict",
143+
"jsx",
144+
"module",
145+
"moduleDetection",
146+
"moduleResolution",
147+
"noEmit",
145148
"noImplicitAny",
146-
"strictNullChecks",
147-
"strictFunctionTypes",
148-
"strictBindCallApply",
149-
"strictPropertyInitialization",
150149
"noImplicitThis",
151-
"alwaysStrict",
152-
"allowSyntheticDefaultImports",
153-
"esModuleInterop",
154-
"emitDecoratorMetadata",
155-
"skipDefaultLibCheck",
150+
"noPropertyAccessFromIndexSignature",
151+
"noUncheckedIndexedAccess",
156152
"preserveConstEnums",
153+
"removeComments",
154+
"resolveJsonModule",
155+
"resolvePackageJsonExports",
156+
"resolvePackageJsonImports",
157+
"skipDefaultLibCheck",
157158
"skipLibCheck",
158-
"exactOptionalPropertyTypes",
159+
"strict",
160+
"strictBindCallApply",
161+
"strictFunctionTypes",
162+
"strictNullChecks",
163+
"strictPropertyInitialization",
164+
"target",
159165
"useDefineForClassFields",
160166
"useUnknownInCatchVariables",
161-
"noUncheckedIndexedAccess",
162-
"noPropertyAccessFromIndexSignature",
163-
"resolvePackageJsonExports",
164-
"resolvePackageJsonImports",
165-
"resolveJsonModule",
166-
"allowArbitraryExtensions",
167+
"verbatimModuleSyntax",
167168
];
168169
private fileName: string;
169170
private justName: string;

tests/baselines/reference/decoratorOnClassMethod11.errors.txt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
1-
decoratorOnClassMethod11.ts(5,10): error TS2331: 'this' cannot be referenced in a module or namespace body.
1+
decoratorOnClassMethod11.ts(5,11): error TS2331: 'this' cannot be referenced in a module or namespace body.
22

33

44
==== decoratorOnClassMethod11.ts (1 errors) ====
55
module M {
66
class C {
77
decorator(target: Object, key: string): void { }
88

9-
@this.decorator
10-
~~~~
9+
@(this.decorator)
10+
~~~~
1111
!!! error TS2331: 'this' cannot be referenced in a module or namespace body.
1212
method() { }
1313
}

tests/baselines/reference/decoratorOnClassMethod11.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ module M {
55
class C {
66
decorator(target: Object, key: string): void { }
77

8-
@this.decorator
8+
@(this.decorator)
99
method() { }
1010
}
1111
}
@@ -25,7 +25,7 @@ var M;
2525
C.prototype.decorator = function (target, key) { };
2626
C.prototype.method = function () { };
2727
__decorate([
28-
this.decorator
28+
(this.decorator)
2929
], C.prototype, "method", null);
3030
return C;
3131
}());

tests/baselines/reference/decoratorOnClassMethod11.symbols

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ module M {
1313
>Object : Symbol(Object, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
1414
>key : Symbol(key, Decl(decoratorOnClassMethod11.ts, 2, 33))
1515

16-
@this.decorator
16+
@(this.decorator)
1717
method() { }
1818
>method : Symbol(C.method, Decl(decoratorOnClassMethod11.ts, 2, 56))
1919
}

tests/baselines/reference/decoratorOnClassMethod11.types

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ module M {
1212
>target : Object
1313
>key : string
1414

15-
@this.decorator
15+
@(this.decorator)
16+
>(this.decorator) : any
1617
>this.decorator : any
1718
>this : any
1819
>decorator : any

tests/baselines/reference/decoratorOnClassMethod12.errors.txt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
decoratorOnClassMethod12.ts(6,10): error TS2660: 'super' can only be referenced in members of derived classes or object literal expressions.
1+
decoratorOnClassMethod12.ts(6,11): error TS2660: 'super' can only be referenced in members of derived classes or object literal expressions.
22

33

44
==== decoratorOnClassMethod12.ts (1 errors) ====
@@ -7,8 +7,8 @@ decoratorOnClassMethod12.ts(6,10): error TS2660: 'super' can only be referenced
77
decorator(target: Object, key: string): void { }
88
}
99
class C extends S {
10-
@super.decorator
11-
~~~~~
10+
@(super.decorator)
11+
~~~~~
1212
!!! error TS2660: 'super' can only be referenced in members of derived classes or object literal expressions.
1313
method() { }
1414
}

tests/baselines/reference/decoratorOnClassMethod12.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ module M {
66
decorator(target: Object, key: string): void { }
77
}
88
class C extends S {
9-
@super.decorator
9+
@(super.decorator)
1010
method() { }
1111
}
1212
}
@@ -48,7 +48,7 @@ var M;
4848
}
4949
C.prototype.method = function () { };
5050
__decorate([
51-
_super.decorator
51+
(_super.decorator)
5252
], C.prototype, "method", null);
5353
return C;
5454
}(S));

tests/baselines/reference/decoratorOnClassMethod12.symbols

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ module M {
1717
>C : Symbol(C, Decl(decoratorOnClassMethod12.ts, 3, 5))
1818
>S : Symbol(S, Decl(decoratorOnClassMethod12.ts, 0, 10))
1919

20-
@super.decorator
20+
@(super.decorator)
2121
method() { }
2222
>method : Symbol(C.method, Decl(decoratorOnClassMethod12.ts, 4, 23))
2323
}

tests/baselines/reference/decoratorOnClassMethod12.types

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ module M {
1616
>C : C
1717
>S : S
1818

19-
@super.decorator
19+
@(super.decorator)
20+
>(super.decorator) : any
2021
>super.decorator : any
2122
>super : any
2223
>decorator : any

0 commit comments

Comments
 (0)