diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index bcb84a4814..dce1ca3f16 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -13048,7 +13048,7 @@ namespace ts { // must instead be rewritten to point to a temporary variable to avoid issues with the double-bind // behavior of class names in ES6. if (declaration.kind === SyntaxKind.ClassDeclaration - && nodeIsDecorated(declaration)) { + && nodeIsDecorated(declaration as ClassDeclaration)) { let container = getContainingClass(node); while (container !== undefined) { if (container === declaration && container.name !== node) { @@ -20697,7 +20697,7 @@ namespace ts { // skip this check for nodes that cannot have decorators. These should have already had an error reported by // checkGrammarDecorators. - if (!nodeCanBeDecorated(node)) { + if (!nodeCanBeDecorated(node, node.parent, node.parent.parent)) { return; } @@ -25203,7 +25203,7 @@ namespace ts { if (!node.decorators) { return false; } - if (!nodeCanBeDecorated(node)) { + if (!nodeCanBeDecorated(node, node.parent, node.parent.parent)) { if (node.kind === SyntaxKind.MethodDeclaration && !nodeIsPresent((node).body)) { return grammarErrorOnFirstToken(node, Diagnostics.A_decorator_can_only_decorate_a_method_implementation_not_an_overload); } diff --git a/src/compiler/transformers/ts.ts b/src/compiler/transformers/ts.ts index 39a4cc83bd..3c1bba280f 100644 --- a/src/compiler/transformers/ts.ts +++ b/src/compiler/transformers/ts.ts @@ -1261,7 +1261,7 @@ namespace ts { * the class. */ function getDecoratedClassElements(node: ClassExpression | ClassDeclaration, isStatic: boolean): ReadonlyArray { - return filter(node.members, isStatic ? isStaticDecoratedClassElement : isInstanceDecoratedClassElement); + return filter(node.members, isStatic ? m => isStaticDecoratedClassElement(m, node) : m => isInstanceDecoratedClassElement(m, node)); } /** @@ -1270,8 +1270,8 @@ namespace ts { * * @param member The class member. */ - function isStaticDecoratedClassElement(member: ClassElement) { - return isDecoratedClassElement(member, /*isStatic*/ true); + function isStaticDecoratedClassElement(member: ClassElement, parent: ClassLikeDeclaration) { + return isDecoratedClassElement(member, /*isStatic*/ true, parent); } /** @@ -1280,8 +1280,8 @@ namespace ts { * * @param member The class member. */ - function isInstanceDecoratedClassElement(member: ClassElement) { - return isDecoratedClassElement(member, /*isStatic*/ false); + function isInstanceDecoratedClassElement(member: ClassElement, parent: ClassLikeDeclaration) { + return isDecoratedClassElement(member, /*isStatic*/ false, parent); } /** @@ -1290,8 +1290,8 @@ namespace ts { * * @param member The class member. */ - function isDecoratedClassElement(member: ClassElement, isStatic: boolean) { - return nodeOrChildIsDecorated(member) + function isDecoratedClassElement(member: ClassElement, isStatic: boolean, parent: ClassLikeDeclaration) { + return nodeOrChildIsDecorated(member, parent) && isStatic === hasModifier(member, ModifierFlags.Static); } diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 366c4bc229..9426cd7783 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -1205,7 +1205,10 @@ namespace ts { return (node).expression; } - export function nodeCanBeDecorated(node: Node): boolean { + export function nodeCanBeDecorated(node: ClassDeclaration): true; + export function nodeCanBeDecorated(node: ClassElement, parent: Node): boolean; + export function nodeCanBeDecorated(node: Node, parent: Node, grandparent: Node): boolean; + export function nodeCanBeDecorated(node: Node, parent?: Node, grandparent?: Node): boolean { switch (node.kind) { case SyntaxKind.ClassDeclaration: // classes are valid targets @@ -1213,43 +1216,51 @@ namespace ts { case SyntaxKind.PropertyDeclaration: // property declarations are valid if their parent is a class declaration. - return node.parent.kind === SyntaxKind.ClassDeclaration; + return parent.kind === SyntaxKind.ClassDeclaration; case SyntaxKind.GetAccessor: case SyntaxKind.SetAccessor: case SyntaxKind.MethodDeclaration: // if this method has a body and its parent is a class declaration, this is a valid target. return (node).body !== undefined - && node.parent.kind === SyntaxKind.ClassDeclaration; + && parent.kind === SyntaxKind.ClassDeclaration; case SyntaxKind.Parameter: // if the parameter's parent has a body and its grandparent is a class declaration, this is a valid target; - return (node.parent).body !== undefined - && (node.parent.kind === SyntaxKind.Constructor - || node.parent.kind === SyntaxKind.MethodDeclaration - || node.parent.kind === SyntaxKind.SetAccessor) - && node.parent.parent.kind === SyntaxKind.ClassDeclaration; + return (parent).body !== undefined + && (parent.kind === SyntaxKind.Constructor + || parent.kind === SyntaxKind.MethodDeclaration + || parent.kind === SyntaxKind.SetAccessor) + && grandparent.kind === SyntaxKind.ClassDeclaration; } return false; } - export function nodeIsDecorated(node: Node): boolean { + export function nodeIsDecorated(node: ClassDeclaration): boolean; + export function nodeIsDecorated(node: ClassElement, parent: Node): boolean; + export function nodeIsDecorated(node: Node, parent: Node, grandparent: Node): boolean; + export function nodeIsDecorated(node: Node, parent?: Node, grandparent?: Node): boolean { return node.decorators !== undefined - && nodeCanBeDecorated(node); + && nodeCanBeDecorated(node, parent, grandparent); } - export function nodeOrChildIsDecorated(node: Node): boolean { - return nodeIsDecorated(node) || childIsDecorated(node); + export function nodeOrChildIsDecorated(node: ClassDeclaration): boolean; + export function nodeOrChildIsDecorated(node: ClassElement, parent: Node): boolean; + export function nodeOrChildIsDecorated(node: Node, parent: Node, grandparent: Node): boolean; + export function nodeOrChildIsDecorated(node: Node, parent?: Node, grandparent?: Node): boolean { + return nodeIsDecorated(node, parent, grandparent) || childIsDecorated(node, parent); } - export function childIsDecorated(node: Node): boolean { + export function childIsDecorated(node: ClassDeclaration): boolean; + export function childIsDecorated(node: Node, parent: Node): boolean; + export function childIsDecorated(node: Node, parent?: Node): boolean { switch (node.kind) { case SyntaxKind.ClassDeclaration: - return forEach((node).members, nodeOrChildIsDecorated); + return forEach((node).members, m => nodeOrChildIsDecorated(m, node, parent)); case SyntaxKind.MethodDeclaration: case SyntaxKind.SetAccessor: - return forEach((node).parameters, nodeIsDecorated); + return forEach((node).parameters, p => nodeIsDecorated(p, node, parent)); } } diff --git a/src/harness/unittests/transform.ts b/src/harness/unittests/transform.ts index a39249bb22..64f0a19b32 100644 --- a/src/harness/unittests/transform.ts +++ b/src/harness/unittests/transform.ts @@ -224,6 +224,32 @@ namespace ts { } } }); + + // https://github.com/Microsoft/TypeScript/issues/17384 + testBaseline("transformAddDecoratedNode", () => { + return ts.transpileModule("", { + transformers: { + before: [transformAddDecoratedNode], + }, + compilerOptions: { + target: ts.ScriptTarget.ES5, + newLine: NewLineKind.CarriageReturnLineFeed, + } + }).outputText; + + function transformAddDecoratedNode(_context: ts.TransformationContext) { + return (sourceFile: ts.SourceFile): ts.SourceFile => { + return visitNode(sourceFile); + }; + function visitNode(sf: ts.SourceFile) { + // produce `class Foo { @Bar baz() {} }`; + const classDecl = ts.createClassDeclaration([], [], "Foo", /*typeParameters*/ undefined, /*heritageClauses*/ undefined, [ + ts.createMethod([ts.createDecorator(ts.createIdentifier("Bar"))], [], /**/ undefined, "baz", /**/ undefined, /**/ undefined, [], /**/ undefined, ts.createBlock([])) + ]); + return ts.updateSourceFileNode(sf, [classDecl]); + } + } + }); }); } diff --git a/tests/baselines/reference/transformApi/transformsCorrectly.transformAddDecoratedNode.js b/tests/baselines/reference/transformApi/transformsCorrectly.transformAddDecoratedNode.js new file mode 100644 index 0000000000..de841226c0 --- /dev/null +++ b/tests/baselines/reference/transformApi/transformsCorrectly.transformAddDecoratedNode.js @@ -0,0 +1,16 @@ +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +var Foo = /** @class */ (function () { + function Foo() { + } + Foo.prototype.baz = function () { }; + __decorate([ + Bar + ], Foo.prototype, "baz", null); + Foo = __decorate([], Foo); + return Foo; +}());