mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-05-27 13:42:16 -05:00
Merge pull request #6684 from Microsoft/fixDecoratedClassName
Fix ES6 decorated class double binding.
This commit is contained in:
@@ -7192,11 +7192,32 @@ namespace ts {
|
||||
markAliasSymbolAsReferenced(symbol);
|
||||
}
|
||||
|
||||
const localOrExportSymbol = getExportSymbolOfValueSymbolIfExported(symbol);
|
||||
|
||||
// Due to the emit for class decorators, any reference to the class from inside of the class body
|
||||
// must instead be rewritten to point to a temporary variable to avoid issues with the double-bind
|
||||
// behavior of class names in ES6.
|
||||
if (languageVersion === ScriptTarget.ES6
|
||||
&& localOrExportSymbol.flags & SymbolFlags.Class
|
||||
&& localOrExportSymbol.valueDeclaration.kind === SyntaxKind.ClassDeclaration
|
||||
&& nodeIsDecorated(localOrExportSymbol.valueDeclaration)) {
|
||||
let container = getContainingClass(node);
|
||||
while (container !== undefined) {
|
||||
if (container === localOrExportSymbol.valueDeclaration && container.name !== node) {
|
||||
getNodeLinks(container).flags |= NodeCheckFlags.ClassWithBodyScopedClassBinding;
|
||||
getNodeLinks(node).flags |= NodeCheckFlags.BodyScopedClassBinding;
|
||||
break;
|
||||
}
|
||||
|
||||
container = getContainingClass(container);
|
||||
}
|
||||
}
|
||||
|
||||
checkCollisionWithCapturedSuperVariable(node, node);
|
||||
checkCollisionWithCapturedThisVariable(node, node);
|
||||
checkNestedBlockScopedBinding(node, symbol);
|
||||
|
||||
return getNarrowedTypeOfSymbol(getExportSymbolOfValueSymbolIfExported(symbol), node);
|
||||
return getNarrowedTypeOfSymbol(localOrExportSymbol, node);
|
||||
}
|
||||
|
||||
function isInsideFunction(node: Node, threshold: Node): boolean {
|
||||
@@ -7238,7 +7259,7 @@ namespace ts {
|
||||
|
||||
if (containedInIterationStatement) {
|
||||
if (usedInFunction) {
|
||||
// mark iteration statement as containing block-scoped binding captured in some function
|
||||
// mark iteration statement as containing block-scoped binding captured in some function
|
||||
getNodeLinks(current).flags |= NodeCheckFlags.LoopWithCapturedBlockScopedBinding;
|
||||
}
|
||||
// set 'declared inside loop' bit on the block-scoped binding
|
||||
@@ -15758,7 +15779,7 @@ namespace ts {
|
||||
// - binding is not top level - top level bindings never collide with anything
|
||||
// AND
|
||||
// - binding is not declared in loop, should be renamed to avoid name reuse across siblings
|
||||
// let a, b
|
||||
// let a, b
|
||||
// { let x = 1; a = () => x; }
|
||||
// { let x = 100; b = () => x; }
|
||||
// console.log(a()); // should print '1'
|
||||
|
||||
@@ -482,6 +482,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
||||
let generatedNameSet: Map<string>;
|
||||
let nodeToGeneratedName: string[];
|
||||
let computedPropertyNamesToGeneratedNames: string[];
|
||||
let decoratedClassAliases: string[];
|
||||
|
||||
let convertedLoopState: ConvertedLoopState;
|
||||
|
||||
@@ -532,6 +533,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
||||
sourceMap.initialize(jsFilePath, sourceMapFilePath, sourceFiles, isBundledEmit);
|
||||
generatedNameSet = {};
|
||||
nodeToGeneratedName = [];
|
||||
decoratedClassAliases = [];
|
||||
isOwnFileEmit = !isBundledEmit;
|
||||
|
||||
// Emit helpers from all the files
|
||||
@@ -561,6 +563,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
||||
contextObjectForFile = undefined;
|
||||
generatedNameSet = undefined;
|
||||
nodeToGeneratedName = undefined;
|
||||
decoratedClassAliases = undefined;
|
||||
computedPropertyNamesToGeneratedNames = undefined;
|
||||
convertedLoopState = undefined;
|
||||
extendsEmitted = false;
|
||||
@@ -1532,13 +1535,26 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
||||
}
|
||||
}
|
||||
|
||||
if (languageVersion !== ScriptTarget.ES6) {
|
||||
if (languageVersion < ScriptTarget.ES6) {
|
||||
const declaration = resolver.getReferencedDeclarationWithCollidingName(node);
|
||||
if (declaration) {
|
||||
write(getGeneratedNameForNode(declaration.name));
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (resolver.getNodeCheckFlags(node) & NodeCheckFlags.BodyScopedClassBinding) {
|
||||
// Due to the emit for class decorators, any reference to the class from inside of the class body
|
||||
// must instead be rewritten to point to a temporary variable to avoid issues with the double-bind
|
||||
// behavior of class names in ES6.
|
||||
const declaration = resolver.getReferencedValueDeclaration(node);
|
||||
if (declaration) {
|
||||
const classAlias = decoratedClassAliases[getNodeId(declaration)];
|
||||
if (classAlias !== undefined) {
|
||||
write(classAlias);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (nodeIsSynthesized(node)) {
|
||||
@@ -4070,7 +4086,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
||||
let initializer = node.initializer;
|
||||
if (!initializer &&
|
||||
languageVersion < ScriptTarget.ES6 &&
|
||||
// for names - binding patterns that lack initializer there is no point to emit explicit initializer
|
||||
// for names - binding patterns that lack initializer there is no point to emit explicit initializer
|
||||
// since downlevel codegen for destructuring will fail in the absence of initializer so all binding elements will say uninitialized
|
||||
node.name.kind === SyntaxKind.Identifier) {
|
||||
|
||||
@@ -4091,11 +4107,11 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
||||
// explicitly initialized. One particular case: non-captured binding declared inside loop body (but not in loop initializer)
|
||||
// let x;
|
||||
// for (;;) {
|
||||
// let x;
|
||||
// let x;
|
||||
// }
|
||||
// in downlevel codegen inner 'x' will be renamed so it won't collide with outer 'x' however it will should be reset on every iteration
|
||||
// as if it was declared anew.
|
||||
// * Why non-captured binding - because if loop contains block scoped binding captured in some function then loop body will be rewritten
|
||||
// as if it was declared anew.
|
||||
// * Why non-captured binding - because if loop contains block scoped binding captured in some function then loop body will be rewritten
|
||||
// to have a fresh scope on every iteration so everything will just work.
|
||||
// * Why loop initializer is excluded - since we've introduced a fresh name it already will be undefined.
|
||||
const isCapturedInFunction = flags & NodeCheckFlags.CapturedBlockScopedBinding;
|
||||
@@ -5109,64 +5125,107 @@ const _super = (function (geti, seti) {
|
||||
}
|
||||
|
||||
function emitClassLikeDeclarationForES6AndHigher(node: ClassLikeDeclaration) {
|
||||
let decoratedClassAlias: string;
|
||||
const thisNodeIsDecorated = nodeIsDecorated(node);
|
||||
if (node.kind === SyntaxKind.ClassDeclaration) {
|
||||
if (thisNodeIsDecorated) {
|
||||
// To preserve the correct runtime semantics when decorators are applied to the class,
|
||||
// the emit needs to follow one of the following rules:
|
||||
// When we emit an ES6 class that has a class decorator, we must tailor the
|
||||
// emit to certain specific cases.
|
||||
//
|
||||
// * For a local class declaration:
|
||||
// In the simplest case, we emit the class declaration as a let declaration, and
|
||||
// evaluate decorators after the close of the class body:
|
||||
//
|
||||
// @dec class C {
|
||||
// }
|
||||
// TypeScript | Javascript
|
||||
// --------------------------------|------------------------------------
|
||||
// @dec | let C = class C {
|
||||
// class C { | }
|
||||
// } | C = __decorate([dec], C);
|
||||
// --------------------------------|------------------------------------
|
||||
// @dec | export let C = class C {
|
||||
// export class C { | }
|
||||
// } | C = __decorate([dec], C);
|
||||
// ---------------------------------------------------------------------
|
||||
// [Example 1]
|
||||
//
|
||||
// The emit should be:
|
||||
// If a class declaration contains a reference to itself *inside* of the class body,
|
||||
// this introduces two bindings to the class: One outside of the class body, and one
|
||||
// inside of the class body. If we apply decorators as in [Example 1] above, there
|
||||
// is the possibility that the decorator `dec` will return a new value for the
|
||||
// constructor, which would result in the binding inside of the class no longer
|
||||
// pointing to the same reference as the binding outside of the class.
|
||||
//
|
||||
// let C = class {
|
||||
// };
|
||||
// C = __decorate([dec], C);
|
||||
// As a result, we must instead rewrite all references to the class *inside* of the
|
||||
// class body to instead point to a local temporary alias for the class:
|
||||
//
|
||||
// * For an exported class declaration:
|
||||
// TypeScript | Javascript
|
||||
// --------------------------------|------------------------------------
|
||||
// @dec | let C_1;
|
||||
// class C { | let C = C_1 = class C {
|
||||
// static x() { return C.y; } | static x() { return C_1.y; }
|
||||
// static y = 1; | }
|
||||
// } | C.y = 1;
|
||||
// | C = C_1 = __decorate([dec], C);
|
||||
// --------------------------------|------------------------------------
|
||||
// @dec | let C_1;
|
||||
// export class C { | export let C = C_1 = class C {
|
||||
// static x() { return C.y; } | static x() { return C_1.y; }
|
||||
// static y = 1; | }
|
||||
// } | C.y = 1;
|
||||
// | C = C_1 = __decorate([dec], C);
|
||||
// ---------------------------------------------------------------------
|
||||
// [Example 2]
|
||||
//
|
||||
// @dec export class C {
|
||||
// }
|
||||
// If a class declaration is the default export of a module, we instead emit
|
||||
// the export after the decorated declaration:
|
||||
//
|
||||
// The emit should be:
|
||||
// TypeScript | Javascript
|
||||
// --------------------------------|------------------------------------
|
||||
// @dec | let default_1 = class {
|
||||
// export default class { | }
|
||||
// } | default_1 = __decorate([dec], default_1);
|
||||
// | export default default_1;
|
||||
// --------------------------------|------------------------------------
|
||||
// @dec | let C = class C {
|
||||
// export default class { | }
|
||||
// } | C = __decorate([dec], C);
|
||||
// | export default C;
|
||||
// ---------------------------------------------------------------------
|
||||
// [Example 3]
|
||||
//
|
||||
// export let C = class {
|
||||
// };
|
||||
// C = __decorate([dec], C);
|
||||
// If the class declaration is the default export and a reference to itself
|
||||
// inside of the class body, we must emit both an alias for the class *and*
|
||||
// move the export after the declaration:
|
||||
//
|
||||
// * For a default export of a class declaration with a name:
|
||||
//
|
||||
// @dec default export class C {
|
||||
// }
|
||||
//
|
||||
// The emit should be:
|
||||
//
|
||||
// let C = class {
|
||||
// }
|
||||
// C = __decorate([dec], C);
|
||||
// export default C;
|
||||
//
|
||||
// * For a default export of a class declaration without a name:
|
||||
//
|
||||
// @dec default export class {
|
||||
// }
|
||||
//
|
||||
// The emit should be:
|
||||
//
|
||||
// let _default = class {
|
||||
// }
|
||||
// _default = __decorate([dec], _default);
|
||||
// export default _default;
|
||||
// TypeScript | Javascript
|
||||
// --------------------------------|------------------------------------
|
||||
// @dec | let C_1;
|
||||
// export default class C { | let C = C_1 = class C {
|
||||
// static x() { return C.y; } | static x() { return C_1.y; }
|
||||
// static y = 1; | }
|
||||
// } | C.y = 1;
|
||||
// | C = C_1 = __decorate([dec], C);
|
||||
// | export default C;
|
||||
// ---------------------------------------------------------------------
|
||||
// [Example 4]
|
||||
//
|
||||
|
||||
if (resolver.getNodeCheckFlags(node) & NodeCheckFlags.ClassWithBodyScopedClassBinding) {
|
||||
decoratedClassAlias = unescapeIdentifier(makeUniqueName(node.name ? node.name.text : "default"));
|
||||
decoratedClassAliases[getNodeId(node)] = decoratedClassAlias;
|
||||
write(`let ${decoratedClassAlias};`);
|
||||
writeLine();
|
||||
}
|
||||
|
||||
if (isES6ExportedDeclaration(node) && !(node.flags & NodeFlags.Default)) {
|
||||
write("export ");
|
||||
}
|
||||
|
||||
write("let ");
|
||||
emitDeclarationName(node);
|
||||
if (decoratedClassAlias !== undefined) {
|
||||
write(` = ${decoratedClassAlias}`);
|
||||
}
|
||||
|
||||
write(" = ");
|
||||
}
|
||||
else if (isES6ExportedDeclaration(node)) {
|
||||
@@ -5205,7 +5264,7 @@ const _super = (function (geti, seti) {
|
||||
// emit name if
|
||||
// - node has a name
|
||||
// - this is default export with static initializers
|
||||
if ((node.name || (node.flags & NodeFlags.Default && (staticProperties.length > 0 || modulekind !== ModuleKind.ES6))) && !thisNodeIsDecorated) {
|
||||
if (node.name || (node.flags & NodeFlags.Default && (staticProperties.length > 0 || modulekind !== ModuleKind.ES6) && !thisNodeIsDecorated)) {
|
||||
write(" ");
|
||||
emitDeclarationName(node);
|
||||
}
|
||||
@@ -5225,16 +5284,8 @@ const _super = (function (geti, seti) {
|
||||
writeLine();
|
||||
emitToken(SyntaxKind.CloseBraceToken, node.members.end);
|
||||
|
||||
// TODO(rbuckton): Need to go back to `let _a = class C {}` approach, removing the defineProperty call for now.
|
||||
|
||||
// For a decorated class, we need to assign its name (if it has one). This is because we emit
|
||||
// the class as a class expression to avoid the double-binding of the identifier:
|
||||
//
|
||||
// let C = class {
|
||||
// }
|
||||
// Object.defineProperty(C, "name", { value: "C", configurable: true });
|
||||
//
|
||||
if (thisNodeIsDecorated) {
|
||||
decoratedClassAliases[getNodeId(node)] = undefined;
|
||||
write(";");
|
||||
}
|
||||
|
||||
@@ -5259,7 +5310,7 @@ const _super = (function (geti, seti) {
|
||||
else {
|
||||
writeLine();
|
||||
emitPropertyDeclarations(node, staticProperties);
|
||||
emitDecoratorsOfClass(node);
|
||||
emitDecoratorsOfClass(node, decoratedClassAlias);
|
||||
}
|
||||
|
||||
if (!(node.flags & NodeFlags.Export)) {
|
||||
@@ -5333,7 +5384,7 @@ const _super = (function (geti, seti) {
|
||||
emitMemberFunctionsForES5AndLower(node);
|
||||
emitPropertyDeclarations(node, getInitializedProperties(node, /*isStatic*/ true));
|
||||
writeLine();
|
||||
emitDecoratorsOfClass(node);
|
||||
emitDecoratorsOfClass(node, /*decoratedClassAlias*/ undefined);
|
||||
writeLine();
|
||||
emitToken(SyntaxKind.CloseBraceToken, node.members.end, () => {
|
||||
write("return ");
|
||||
@@ -5375,13 +5426,13 @@ const _super = (function (geti, seti) {
|
||||
}
|
||||
}
|
||||
|
||||
function emitDecoratorsOfClass(node: ClassLikeDeclaration) {
|
||||
function emitDecoratorsOfClass(node: ClassLikeDeclaration, decoratedClassAlias: string) {
|
||||
emitDecoratorsOfMembers(node, /*staticFlag*/ 0);
|
||||
emitDecoratorsOfMembers(node, NodeFlags.Static);
|
||||
emitDecoratorsOfConstructor(node);
|
||||
emitDecoratorsOfConstructor(node, decoratedClassAlias);
|
||||
}
|
||||
|
||||
function emitDecoratorsOfConstructor(node: ClassLikeDeclaration) {
|
||||
function emitDecoratorsOfConstructor(node: ClassLikeDeclaration, decoratedClassAlias: string) {
|
||||
const decorators = node.decorators;
|
||||
const constructor = getFirstConstructorWithBody(node);
|
||||
const firstParameterDecorator = constructor && forEach(constructor.parameters, parameter => parameter.decorators);
|
||||
@@ -5405,6 +5456,10 @@ const _super = (function (geti, seti) {
|
||||
writeLine();
|
||||
emitStart(node.decorators || firstParameterDecorator);
|
||||
emitDeclarationName(node);
|
||||
if (decoratedClassAlias !== undefined) {
|
||||
write(` = ${decoratedClassAlias}`);
|
||||
}
|
||||
|
||||
write(" = __decorate([");
|
||||
increaseIndent();
|
||||
writeLine();
|
||||
|
||||
@@ -2054,23 +2054,23 @@ namespace ts {
|
||||
|
||||
/* @internal */
|
||||
export const enum NodeCheckFlags {
|
||||
TypeChecked = 0x00000001, // Node has been type checked
|
||||
LexicalThis = 0x00000002, // Lexical 'this' reference
|
||||
CaptureThis = 0x00000004, // Lexical 'this' used in body
|
||||
SuperInstance = 0x00000100, // Instance 'super' reference
|
||||
SuperStatic = 0x00000200, // Static 'super' reference
|
||||
ContextChecked = 0x00000400, // Contextual types have been assigned
|
||||
AsyncMethodWithSuper = 0x00000800, // An async method that reads a value from a member of 'super'.
|
||||
AsyncMethodWithSuperBinding = 0x00001000, // An async method that assigns a value to a member of 'super'.
|
||||
CaptureArguments = 0x00002000, // Lexical 'arguments' used in body (for async functions)
|
||||
|
||||
// Values for enum members have been computed, and any errors have been reported for them.
|
||||
EnumValuesComputed = 0x00004000,
|
||||
LexicalModuleMergesWithClass = 0x00008000, // Instantiated lexical module declaration is merged with a previous class declaration.
|
||||
LoopWithCapturedBlockScopedBinding = 0x00010000, // Loop that contains block scoped variable captured in closure
|
||||
CapturedBlockScopedBinding = 0x00020000, // Block-scoped binding that is captured in some function
|
||||
BlockScopedBindingInLoop = 0x00040000, // Block-scoped binding with declaration nested inside iteration statement
|
||||
HasSeenSuperCall = 0x00080000, // Set during the binding when encounter 'super'
|
||||
TypeChecked = 0x00000001, // Node has been type checked
|
||||
LexicalThis = 0x00000002, // Lexical 'this' reference
|
||||
CaptureThis = 0x00000004, // Lexical 'this' used in body
|
||||
SuperInstance = 0x00000100, // Instance 'super' reference
|
||||
SuperStatic = 0x00000200, // Static 'super' reference
|
||||
ContextChecked = 0x00000400, // Contextual types have been assigned
|
||||
AsyncMethodWithSuper = 0x00000800, // An async method that reads a value from a member of 'super'.
|
||||
AsyncMethodWithSuperBinding = 0x00001000, // An async method that assigns a value to a member of 'super'.
|
||||
CaptureArguments = 0x00002000, // Lexical 'arguments' used in body (for async functions)
|
||||
EnumValuesComputed = 0x00004000, // Values for enum members have been computed, and any errors have been reported for them.
|
||||
LexicalModuleMergesWithClass = 0x00008000, // Instantiated lexical module declaration is merged with a previous class declaration.
|
||||
LoopWithCapturedBlockScopedBinding = 0x00010000, // Loop that contains block scoped variable captured in closure
|
||||
CapturedBlockScopedBinding = 0x00020000, // Block-scoped binding that is captured in some function
|
||||
BlockScopedBindingInLoop = 0x00040000, // Block-scoped binding with declaration nested inside iteration statement
|
||||
HasSeenSuperCall = 0x00080000, // Set during the binding when encounter 'super'
|
||||
ClassWithBodyScopedClassBinding = 0x00100000, // Decorated class that contains a binding to itself inside of the class body.
|
||||
BodyScopedClassBinding = 0x00200000, // Binding to a decorated class inside of the class's body.
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
|
||||
Reference in New Issue
Block a user