From faee7b3621fca258c71bd48f2193a66bfc78067b Mon Sep 17 00:00:00 2001 From: Martin Probst Date: Tue, 11 Jan 2022 21:19:36 +0100 Subject: [PATCH] Avoid printing comments on static fields twice. (#47363) * Avoid printing comments on static fields twice. In TS4.4, when a transformer adds a static field with a synthetic comment to a decorated class, TS prints the synthetic comment twice: @Decorator class MyClass { /* comment */ staticField = 'x'; // field and comment added by transformer } Becomes: var MyClass = class MyClass {} /*comment*/ /*comment*/ MyClass.newField = "x"; __decorate(MyClass, Decorator, ...) This is because the classFields transformer calls `setOriginalNode(n, propertyDeclaration)` on both the expression statement , and the assignment expression contained in it, leading to the synthetic comment appearing twice. This change avoids the problem by explicitly deleting any synthetic comments from the assignment expression created for static fields when creating the expression statement containing the assignment. This allows us to retain the information of the original node without printing the synthetic comment twice. * Update src/testRunner/unittests/transform.ts Co-authored-by: Daniel Rosenwasser --- src/compiler/transformers/classFields.ts | 5 ++ src/testRunner/unittests/transform.ts | 57 +++++++++++++++++++ ...cCommentOnStaticFieldInClassDeclaration.js | 13 +++++ ...icCommentOnStaticFieldInClassExpression.js | 6 ++ 4 files changed, 81 insertions(+) create mode 100644 tests/baselines/reference/transformApi/transformsCorrectly.transformSyntheticCommentOnStaticFieldInClassDeclaration.js create mode 100644 tests/baselines/reference/transformApi/transformsCorrectly.transformSyntheticCommentOnStaticFieldInClassExpression.js diff --git a/src/compiler/transformers/classFields.ts b/src/compiler/transformers/classFields.ts index 3bd3d1b1cbb..e898f9199f7 100644 --- a/src/compiler/transformers/classFields.ts +++ b/src/compiler/transformers/classFields.ts @@ -1335,6 +1335,11 @@ namespace ts { setSourceMapRange(statement, moveRangePastModifiers(property)); setCommentRange(statement, property); setOriginalNode(statement, property); + // `setOriginalNode` *copies* the `emitNode` from `property`, so now both + // `statement` and `expression` have a copy of the synthesized comments. + // Drop the comments from expression to avoid printing them twice. + setSyntheticLeadingComments(expression, undefined); + setSyntheticTrailingComments(expression, undefined); statements.push(statement); } } diff --git a/src/testRunner/unittests/transform.ts b/src/testRunner/unittests/transform.ts index 476dc6540d5..256a1db2e30 100644 --- a/src/testRunner/unittests/transform.ts +++ b/src/testRunner/unittests/transform.ts @@ -599,6 +599,63 @@ module MyModule { }, exports => { assert.equal(exports.stringLength, 5); }); + + function addStaticFieldWithComment(context: TransformationContext) { + return (sourceFile: SourceFile): SourceFile => { + return visitNode(sourceFile, rootTransform, isSourceFile); + }; + function rootTransform(node: T): Node { + if (isClassLike(node)) { + const newMembers = [factory.createPropertyDeclaration(/* decorators */ undefined, [factory.createModifier(SyntaxKind.StaticKeyword)], "newField", /* questionOrExclamationToken */ undefined, /* type */ undefined, factory.createStringLiteral("x"))]; + setSyntheticLeadingComments(newMembers[0], [{ kind: SyntaxKind.MultiLineCommentTrivia, text: "comment", pos: -1, end: -1, hasTrailingNewLine: true }]); + return isClassDeclaration(node) ? + factory.updateClassDeclaration( + node, node.decorators, + /* modifierFlags */ undefined, node.name, + node.typeParameters, node.heritageClauses, + newMembers) : + factory.updateClassExpression( + node, node.decorators, + /* modifierFlags */ undefined, node.name, + node.typeParameters, node.heritageClauses, + newMembers); + } + return visitEachChild(node, rootTransform, context); + } + } + + testBaseline("transformSyntheticCommentOnStaticFieldInClassDeclaration", () => { + return transpileModule(` +declare const Decorator: any; +@Decorator +class MyClass { +} +`, { + transformers: { + before: [addStaticFieldWithComment], + }, + compilerOptions: { + target: ScriptTarget.ES2015, + newLine: NewLineKind.CarriageReturnLineFeed, + } + }).outputText; + }); + + testBaseline("transformSyntheticCommentOnStaticFieldInClassExpression", () => { + return transpileModule(` +const MyClass = class { +}; +`, { + transformers: { + before: [addStaticFieldWithComment], + }, + compilerOptions: { + target: ScriptTarget.ES2015, + newLine: NewLineKind.CarriageReturnLineFeed, + } + }).outputText; + }); + }); } diff --git a/tests/baselines/reference/transformApi/transformsCorrectly.transformSyntheticCommentOnStaticFieldInClassDeclaration.js b/tests/baselines/reference/transformApi/transformsCorrectly.transformSyntheticCommentOnStaticFieldInClassDeclaration.js new file mode 100644 index 00000000000..df471270ae2 --- /dev/null +++ b/tests/baselines/reference/transformApi/transformsCorrectly.transformSyntheticCommentOnStaticFieldInClassDeclaration.js @@ -0,0 +1,13 @@ +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; +}; +let MyClass = class MyClass { +}; +/*comment*/ +MyClass.newField = "x"; +MyClass = __decorate([ + Decorator +], MyClass); diff --git a/tests/baselines/reference/transformApi/transformsCorrectly.transformSyntheticCommentOnStaticFieldInClassExpression.js b/tests/baselines/reference/transformApi/transformsCorrectly.transformSyntheticCommentOnStaticFieldInClassExpression.js new file mode 100644 index 00000000000..e6ca9fc6ac3 --- /dev/null +++ b/tests/baselines/reference/transformApi/transformsCorrectly.transformSyntheticCommentOnStaticFieldInClassExpression.js @@ -0,0 +1,6 @@ +var _a; +const MyClass = (_a = class { + }, + /*comment*/ + _a.newField = "x", + _a);