From 3e76fb5ca20a474c73a84fef183f49b610d0f38f Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Mon, 10 Apr 2023 17:05:38 -0400 Subject: [PATCH] Fix CJS local binding emit for ES decorators (#53725) --- src/compiler/transformers/esDecorators.ts | 11 ++- ...ervation(module=commonjs,target=es2015).js | 4 +- ...ervation(module=commonjs,target=es2022).js | 4 +- ...eclaration-commonjs-classNamespaceMerge.js | 71 +++++++++++++++++++ ...ation-commonjs-classNamespaceMerge.symbols | 26 +++++++ ...aration-commonjs-classNamespaceMerge.types | 28 ++++++++ .../esDecorators-classDeclaration-commonjs.js | 64 +++++++++++++++++ ...corators-classDeclaration-commonjs.symbols | 19 +++++ ...Decorators-classDeclaration-commonjs.types | 20 ++++++ ...eclaration-commonjs-classNamespaceMerge.ts | 15 ++++ .../esDecorators-classDeclaration-commonjs.ts | 11 +++ 11 files changed, 268 insertions(+), 5 deletions(-) create mode 100644 tests/baselines/reference/esDecorators-classDeclaration-commonjs-classNamespaceMerge.js create mode 100644 tests/baselines/reference/esDecorators-classDeclaration-commonjs-classNamespaceMerge.symbols create mode 100644 tests/baselines/reference/esDecorators-classDeclaration-commonjs-classNamespaceMerge.types create mode 100644 tests/baselines/reference/esDecorators-classDeclaration-commonjs.js create mode 100644 tests/baselines/reference/esDecorators-classDeclaration-commonjs.symbols create mode 100644 tests/baselines/reference/esDecorators-classDeclaration-commonjs.types create mode 100644 tests/cases/conformance/esDecorators/classDeclaration/esDecorators-classDeclaration-commonjs-classNamespaceMerge.ts create mode 100644 tests/cases/conformance/esDecorators/classDeclaration/esDecorators-classDeclaration-commonjs.ts diff --git a/src/compiler/transformers/esDecorators.ts b/src/compiler/transformers/esDecorators.ts index fdbab9c0a5a..36f23fbe3b8 100644 --- a/src/compiler/transformers/esDecorators.ts +++ b/src/compiler/transformers/esDecorators.ts @@ -46,6 +46,7 @@ import { getAllDecoratorsOfClassElement, getCommentRange, getEffectiveBaseTypeNode, + getEmitScriptTarget, getFirstConstructorWithBody, getHeritageClause, getNonAssignmentOperatorForCompoundAssignment, @@ -279,6 +280,9 @@ export function transformESDecorators(context: TransformationContext): (x: Sourc hoistVariableDeclaration, } = context; + const compilerOptions = context.getCompilerOptions(); + const languageVersion = getEmitScriptTarget(compilerOptions); + let top: LexicalEnvironmentStackEntry | undefined; let classInfo: ClassInfo | undefined; let classThis: Identifier | undefined; @@ -1003,7 +1007,12 @@ export function transformESDecorators(context: TransformationContext): (x: Sourc Debug.assertIsDefined(node.name, "A class declaration that is not a default export must have a name."); const iife = transformClassLike(node, factory.createStringLiteralFromNode(node.name)); const modifiers = visitNodes(node.modifiers, modifierVisitor, isModifier); - const varDecl = factory.createVariableDeclaration(node.name, /*exclamationToken*/ undefined, /*type*/ undefined, iife); + // When we transform to ES5/3 this will be moved inside an IIFE and should reference the name + // without any block-scoped variable collision handling + const declName = languageVersion <= ScriptTarget.ES2015 ? + factory.getInternalName(node, /*allowComments*/ false, /*allowSourceMaps*/ true) : + factory.getLocalName(node, /*allowComments*/ false, /*allowSourceMaps*/ true); + const varDecl = factory.createVariableDeclaration(declName, /*exclamationToken*/ undefined, /*type*/ undefined, iife); const varDecls = factory.createVariableDeclarationList([varDecl], NodeFlags.Let); const statement = factory.createVariableStatement(modifiers, varDecls); setOriginalNode(statement, node); diff --git a/tests/baselines/reference/esDecorators-classDeclaration-commentPreservation(module=commonjs,target=es2015).js b/tests/baselines/reference/esDecorators-classDeclaration-commentPreservation(module=commonjs,target=es2015).js index de6a0df751c..bc92f15ecde 100644 --- a/tests/baselines/reference/esDecorators-classDeclaration-commentPreservation(module=commonjs,target=es2015).js +++ b/tests/baselines/reference/esDecorators-classDeclaration-commentPreservation(module=commonjs,target=es2015).js @@ -208,7 +208,7 @@ let C = (() => { Object.defineProperty(exports, "__esModule", { value: true }); exports.D = void 0; /*34*/ -exports.D = (() => { +let D = exports.D = (() => { let _classDecorators = [dec, dec]; let _classDescriptor; let _classExtraInitializers = []; @@ -243,7 +243,7 @@ exports.default = (() => { Object.defineProperty(exports, "__esModule", { value: true }); exports.F = void 0; /*40*/ -exports.F = (() => { +let F = exports.F = (() => { let _classDecorators = [dec, dec]; let _classDescriptor; let _classExtraInitializers = []; diff --git a/tests/baselines/reference/esDecorators-classDeclaration-commentPreservation(module=commonjs,target=es2022).js b/tests/baselines/reference/esDecorators-classDeclaration-commentPreservation(module=commonjs,target=es2022).js index 997866ea6d0..812dcb28249 100644 --- a/tests/baselines/reference/esDecorators-classDeclaration-commentPreservation(module=commonjs,target=es2022).js +++ b/tests/baselines/reference/esDecorators-classDeclaration-commentPreservation(module=commonjs,target=es2022).js @@ -205,7 +205,7 @@ let C = (() => { Object.defineProperty(exports, "__esModule", { value: true }); exports.D = void 0; /*34*/ -exports.D = (() => { +let D = exports.D = (() => { let _classDecorators = [dec, dec]; let _classDescriptor; let _classExtraInitializers = []; @@ -238,7 +238,7 @@ exports.default = (() => { Object.defineProperty(exports, "__esModule", { value: true }); exports.F = void 0; /*40*/ -exports.F = (() => { +let F = exports.F = (() => { let _classDecorators = [dec, dec]; let _classDescriptor; let _classExtraInitializers = []; diff --git a/tests/baselines/reference/esDecorators-classDeclaration-commonjs-classNamespaceMerge.js b/tests/baselines/reference/esDecorators-classDeclaration-commonjs-classNamespaceMerge.js new file mode 100644 index 00000000000..2d6ec7e79b5 --- /dev/null +++ b/tests/baselines/reference/esDecorators-classDeclaration-commonjs-classNamespaceMerge.js @@ -0,0 +1,71 @@ +//// [esDecorators-classDeclaration-commonjs-classNamespaceMerge.ts] +declare var deco: any; + +@deco +export class Example { + static foo() {} +} + +export namespace Example { + export const x = 1; +} + +Example.foo(); + +//// [esDecorators-classDeclaration-commonjs-classNamespaceMerge.js] +"use strict"; +var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) { + function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; } + var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value"; + var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null; + var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {}); + var _, done = false; + for (var i = decorators.length - 1; i >= 0; i--) { + var context = {}; + for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p]; + for (var p in contextIn.access) context.access[p] = contextIn.access[p]; + context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); }; + var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context); + if (kind === "accessor") { + if (result === void 0) continue; + if (result === null || typeof result !== "object") throw new TypeError("Object expected"); + if (_ = accept(result.get)) descriptor.get = _; + if (_ = accept(result.set)) descriptor.set = _; + if (_ = accept(result.init)) initializers.push(_); + } + else if (_ = accept(result)) { + if (kind === "field") initializers.push(_); + else descriptor[key] = _; + } + } + if (target) Object.defineProperty(target, contextIn.name, descriptor); + done = true; +}; +var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) { + var useValue = arguments.length > 2; + for (var i = 0; i < initializers.length; i++) { + value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg); + } + return useValue ? value : void 0; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.Example = void 0; +let Example = exports.Example = (() => { + let _classDecorators = [deco]; + let _classDescriptor; + let _classExtraInitializers = []; + let _classThis; + var Example = class { + static { + __esDecorate(null, _classDescriptor = { value: this }, _classDecorators, { kind: "class", name: this.name }, null, _classExtraInitializers); + Example = _classThis = _classDescriptor.value; + __runInitializers(_classThis, _classExtraInitializers); + } + static foo() { } + }; + return Example = _classThis; +})(); +(function (Example) { + Example.x = 1; +})(Example = exports.Example || (exports.Example = {})); +Example.foo(); diff --git a/tests/baselines/reference/esDecorators-classDeclaration-commonjs-classNamespaceMerge.symbols b/tests/baselines/reference/esDecorators-classDeclaration-commonjs-classNamespaceMerge.symbols new file mode 100644 index 00000000000..2216449c5ae --- /dev/null +++ b/tests/baselines/reference/esDecorators-classDeclaration-commonjs-classNamespaceMerge.symbols @@ -0,0 +1,26 @@ +=== tests/cases/conformance/esDecorators/classDeclaration/esDecorators-classDeclaration-commonjs-classNamespaceMerge.ts === +declare var deco: any; +>deco : Symbol(deco, Decl(esDecorators-classDeclaration-commonjs-classNamespaceMerge.ts, 0, 11)) + +@deco +>deco : Symbol(deco, Decl(esDecorators-classDeclaration-commonjs-classNamespaceMerge.ts, 0, 11)) + +export class Example { +>Example : Symbol(Example, Decl(esDecorators-classDeclaration-commonjs-classNamespaceMerge.ts, 0, 22), Decl(esDecorators-classDeclaration-commonjs-classNamespaceMerge.ts, 5, 1)) + + static foo() {} +>foo : Symbol(Example.foo, Decl(esDecorators-classDeclaration-commonjs-classNamespaceMerge.ts, 3, 22)) +} + +export namespace Example { +>Example : Symbol(Example, Decl(esDecorators-classDeclaration-commonjs-classNamespaceMerge.ts, 0, 22), Decl(esDecorators-classDeclaration-commonjs-classNamespaceMerge.ts, 5, 1)) + + export const x = 1; +>x : Symbol(x, Decl(esDecorators-classDeclaration-commonjs-classNamespaceMerge.ts, 8, 16)) +} + +Example.foo(); +>Example.foo : Symbol(Example.foo, Decl(esDecorators-classDeclaration-commonjs-classNamespaceMerge.ts, 3, 22)) +>Example : Symbol(Example, Decl(esDecorators-classDeclaration-commonjs-classNamespaceMerge.ts, 0, 22), Decl(esDecorators-classDeclaration-commonjs-classNamespaceMerge.ts, 5, 1)) +>foo : Symbol(Example.foo, Decl(esDecorators-classDeclaration-commonjs-classNamespaceMerge.ts, 3, 22)) + diff --git a/tests/baselines/reference/esDecorators-classDeclaration-commonjs-classNamespaceMerge.types b/tests/baselines/reference/esDecorators-classDeclaration-commonjs-classNamespaceMerge.types new file mode 100644 index 00000000000..45d5297b285 --- /dev/null +++ b/tests/baselines/reference/esDecorators-classDeclaration-commonjs-classNamespaceMerge.types @@ -0,0 +1,28 @@ +=== tests/cases/conformance/esDecorators/classDeclaration/esDecorators-classDeclaration-commonjs-classNamespaceMerge.ts === +declare var deco: any; +>deco : any + +@deco +>deco : any + +export class Example { +>Example : Example + + static foo() {} +>foo : () => void +} + +export namespace Example { +>Example : typeof Example + + export const x = 1; +>x : 1 +>1 : 1 +} + +Example.foo(); +>Example.foo() : void +>Example.foo : () => void +>Example : typeof Example +>foo : () => void + diff --git a/tests/baselines/reference/esDecorators-classDeclaration-commonjs.js b/tests/baselines/reference/esDecorators-classDeclaration-commonjs.js new file mode 100644 index 00000000000..13fb03cb1bf --- /dev/null +++ b/tests/baselines/reference/esDecorators-classDeclaration-commonjs.js @@ -0,0 +1,64 @@ +//// [esDecorators-classDeclaration-commonjs.ts] +declare var deco: any; + +@deco +export class Example { + static foo() {} +} + +Example.foo(); + +//// [esDecorators-classDeclaration-commonjs.js] +"use strict"; +var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) { + function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; } + var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value"; + var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null; + var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {}); + var _, done = false; + for (var i = decorators.length - 1; i >= 0; i--) { + var context = {}; + for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p]; + for (var p in contextIn.access) context.access[p] = contextIn.access[p]; + context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); }; + var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context); + if (kind === "accessor") { + if (result === void 0) continue; + if (result === null || typeof result !== "object") throw new TypeError("Object expected"); + if (_ = accept(result.get)) descriptor.get = _; + if (_ = accept(result.set)) descriptor.set = _; + if (_ = accept(result.init)) initializers.push(_); + } + else if (_ = accept(result)) { + if (kind === "field") initializers.push(_); + else descriptor[key] = _; + } + } + if (target) Object.defineProperty(target, contextIn.name, descriptor); + done = true; +}; +var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) { + var useValue = arguments.length > 2; + for (var i = 0; i < initializers.length; i++) { + value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg); + } + return useValue ? value : void 0; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.Example = void 0; +let Example = exports.Example = (() => { + let _classDecorators = [deco]; + let _classDescriptor; + let _classExtraInitializers = []; + let _classThis; + var Example = class { + static { + __esDecorate(null, _classDescriptor = { value: this }, _classDecorators, { kind: "class", name: this.name }, null, _classExtraInitializers); + Example = _classThis = _classDescriptor.value; + __runInitializers(_classThis, _classExtraInitializers); + } + static foo() { } + }; + return Example = _classThis; +})(); +Example.foo(); diff --git a/tests/baselines/reference/esDecorators-classDeclaration-commonjs.symbols b/tests/baselines/reference/esDecorators-classDeclaration-commonjs.symbols new file mode 100644 index 00000000000..b8d968f9404 --- /dev/null +++ b/tests/baselines/reference/esDecorators-classDeclaration-commonjs.symbols @@ -0,0 +1,19 @@ +=== tests/cases/conformance/esDecorators/classDeclaration/esDecorators-classDeclaration-commonjs.ts === +declare var deco: any; +>deco : Symbol(deco, Decl(esDecorators-classDeclaration-commonjs.ts, 0, 11)) + +@deco +>deco : Symbol(deco, Decl(esDecorators-classDeclaration-commonjs.ts, 0, 11)) + +export class Example { +>Example : Symbol(Example, Decl(esDecorators-classDeclaration-commonjs.ts, 0, 22)) + + static foo() {} +>foo : Symbol(Example.foo, Decl(esDecorators-classDeclaration-commonjs.ts, 3, 22)) +} + +Example.foo(); +>Example.foo : Symbol(Example.foo, Decl(esDecorators-classDeclaration-commonjs.ts, 3, 22)) +>Example : Symbol(Example, Decl(esDecorators-classDeclaration-commonjs.ts, 0, 22)) +>foo : Symbol(Example.foo, Decl(esDecorators-classDeclaration-commonjs.ts, 3, 22)) + diff --git a/tests/baselines/reference/esDecorators-classDeclaration-commonjs.types b/tests/baselines/reference/esDecorators-classDeclaration-commonjs.types new file mode 100644 index 00000000000..10b6e62d79a --- /dev/null +++ b/tests/baselines/reference/esDecorators-classDeclaration-commonjs.types @@ -0,0 +1,20 @@ +=== tests/cases/conformance/esDecorators/classDeclaration/esDecorators-classDeclaration-commonjs.ts === +declare var deco: any; +>deco : any + +@deco +>deco : any + +export class Example { +>Example : Example + + static foo() {} +>foo : () => void +} + +Example.foo(); +>Example.foo() : void +>Example.foo : () => void +>Example : typeof Example +>foo : () => void + diff --git a/tests/cases/conformance/esDecorators/classDeclaration/esDecorators-classDeclaration-commonjs-classNamespaceMerge.ts b/tests/cases/conformance/esDecorators/classDeclaration/esDecorators-classDeclaration-commonjs-classNamespaceMerge.ts new file mode 100644 index 00000000000..e1d2d5cf388 --- /dev/null +++ b/tests/cases/conformance/esDecorators/classDeclaration/esDecorators-classDeclaration-commonjs-classNamespaceMerge.ts @@ -0,0 +1,15 @@ +// @target: es2022 +// @module: commonjs + +declare var deco: any; + +@deco +export class Example { + static foo() {} +} + +export namespace Example { + export const x = 1; +} + +Example.foo(); \ No newline at end of file diff --git a/tests/cases/conformance/esDecorators/classDeclaration/esDecorators-classDeclaration-commonjs.ts b/tests/cases/conformance/esDecorators/classDeclaration/esDecorators-classDeclaration-commonjs.ts new file mode 100644 index 00000000000..c61de5342b0 --- /dev/null +++ b/tests/cases/conformance/esDecorators/classDeclaration/esDecorators-classDeclaration-commonjs.ts @@ -0,0 +1,11 @@ +// @target: es2022 +// @module: commonjs + +declare var deco: any; + +@deco +export class Example { + static foo() {} +} + +Example.foo(); \ No newline at end of file