diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 3cb9031fd9..278159469e 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -4846,7 +4846,7 @@ namespace ts { const parent = container && container.parent; if (parent && (isClassLike(parent) || parent.kind === SyntaxKind.InterfaceDeclaration)) { if (!(container.flags & NodeFlags.Static) && - (container.kind !== SyntaxKind.Constructor || isNodeDescendentOf(node, (container).body))) { + (container.kind !== SyntaxKind.Constructor || isNodeDescendantOf(node, (container).body))) { return getDeclaredTypeOfClassOrInterface(getSymbolOfNode(parent)).thisType; } } @@ -7206,8 +7206,8 @@ namespace ts { let container = getContainingClass(node); while (container !== undefined) { if (container === localOrExportSymbol.valueDeclaration && container.name !== node) { - getNodeLinks(container).flags |= NodeCheckFlags.ClassWithBodyScopedClassBinding; - getNodeLinks(node).flags |= NodeCheckFlags.BodyScopedClassBinding; + getNodeLinks(container).flags |= NodeCheckFlags.DecoratedClassWithSelfReference; + getNodeLinks(node).flags |= NodeCheckFlags.SelfReferenceInDecoratedClass; break; } diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index a3c27d8d81..bda2bd7950 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -346,7 +346,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge }; function isUniqueLocalName(name: string, container: Node): boolean { - for (let node = container; isNodeDescendentOf(node, container); node = node.nextContainer) { + for (let node = container; isNodeDescendantOf(node, container); node = node.nextContainer) { if (node.locals && hasProperty(node.locals, name)) { // We conservatively include alias symbols to cover cases where they're emitted as locals if (node.locals[name].flags & (SymbolFlags.Value | SymbolFlags.ExportValue | SymbolFlags.Alias)) { @@ -1529,7 +1529,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge return; } } - else if (resolver.getNodeCheckFlags(node) & NodeCheckFlags.BodyScopedClassBinding) { + else if (resolver.getNodeCheckFlags(node) & NodeCheckFlags.SelfReferenceInDecoratedClass) { // 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. @@ -5203,7 +5203,7 @@ const _super = (function (geti, seti) { // [Example 4] // - if (resolver.getNodeCheckFlags(node) & NodeCheckFlags.ClassWithBodyScopedClassBinding) { + if (resolver.getNodeCheckFlags(node) & NodeCheckFlags.DecoratedClassWithSelfReference) { decoratedClassAlias = unescapeIdentifier(makeUniqueName(node.name ? node.name.text : "default")); decoratedClassAliases[getNodeId(node)] = decoratedClassAlias; write(`let ${decoratedClassAlias};`); diff --git a/src/compiler/printer.ts b/src/compiler/printer.ts index e44e88b461..331e502b93 100644 --- a/src/compiler/printer.ts +++ b/src/compiler/printer.ts @@ -2368,7 +2368,7 @@ const _super = (function (geti, seti) { } function isUniqueLocalName(name: string, container: Node): boolean { - for (let node = container; isNodeDescendentOf(node, container); node = node.nextContainer) { + for (let node = container; isNodeDescendantOf(node, container); node = node.nextContainer) { if (node.locals && hasProperty(node.locals, name)) { // We conservatively include alias symbols to cover cases where they're emitted as locals if (node.locals[name].flags & (SymbolFlags.Value | SymbolFlags.ExportValue | SymbolFlags.Alias)) { diff --git a/src/compiler/transformers/ts.ts b/src/compiler/transformers/ts.ts index b7a180aab9..58f06cb214 100644 --- a/src/compiler/transformers/ts.ts +++ b/src/compiler/transformers/ts.ts @@ -6,6 +6,15 @@ namespace ts { type SuperContainer = ClassDeclaration | MethodDeclaration | GetAccessorDeclaration | SetAccessorDeclaration | ConstructorDeclaration; + const enum TypeScriptSubstitutionFlags { + /** Enables substitutions for decorated classes. */ + DecoratedClasses = 1 << 0, + /** Enables substitutions for namespace exports. */ + NamespaceExports = 1 << 1, + /** Enables substitutions for async methods with `super` calls. */ + AsyncMethodsWithSuper = 1 << 2, + } + export function transformTypeScript(context: TransformationContext) { const { setNodeEmitFlags, @@ -19,14 +28,14 @@ namespace ts { const languageVersion = getEmitScriptTarget(compilerOptions); // Save the previous transformation hooks. - const previousExpressionSubstitution = context.expressionSubstitution; const previousOnBeforeEmitNode = context.onBeforeEmitNode; const previousOnAfterEmitNode = context.onAfterEmitNode; + const previousExpressionSubstitution = context.expressionSubstitution; // Set new transformation hooks. - context.expressionSubstitution = substituteExpression; context.onBeforeEmitNode = onBeforeEmitNode; context.onAfterEmitNode = onAfterEmitNode; + context.expressionSubstitution = substituteExpression; // These variables contain state that changes as we descend into the tree. let currentSourceFile: SourceFile; @@ -36,31 +45,46 @@ namespace ts { let currentParent: Node; let currentNode: Node; - // These variables keep track of whether expression substitution has been enabled for - // specific edge cases. They are persisted between each SourceFile transformation and - // should not be reset. - let hasEnabledExpressionSubstitutionForDecoratedClasses = false; - let hasEnabledExpressionSubstitutionForNamespaceExports = false; - let hasEnabledExpressionSubstitutionForAsyncMethodsWithSuper = false; + /** + * Keeps track of whether expression substitution has been enabled for specific edge cases. + * They are persisted between each SourceFile transformation and should not be reset. + */ + let enabledSubstitutions: TypeScriptSubstitutionFlags; - // This map keeps track of aliases created for classes with decorators to avoid issues - // with the double-binding behavior of classes. + /** + * A map that keeps track of aliases created for classes with decorators to avoid issues + * with the double-binding behavior of classes. + */ let decoratedClassAliases: Map; - // This map keeps track of currently active aliases defined in `decoratedClassAliases` - // when just-in-time substitution occurs while printing an expression identifier. + /** + * A map that keeps track of currently active aliases defined in `decoratedClassAliases` + * when just-in-time substitution occurs while printing an expression identifier. + */ let currentDecoratedClassAliases: Map; - // This value keeps track of how deeply nested we are within any containing namespaces - // when performing just-in-time substitution while printing an expression identifier. + /** + * Keeps track of how deeply nested we are within any containing namespaces + * when performing just-in-time substitution while printing an expression identifier. + * If the nest level is greater than zero, then we are performing a substitution + * inside of a namespace and we should perform the more costly checks to determine + * whether the identifier points to an exported declaration. + */ let namespaceNestLevel: number; - // This array keeps track of containers where `super` is valid, for use with - // just-in-time substitution for `super` expressions inside of async methods. + /** + * This array keeps track of containers where `super` is valid, for use with + * just-in-time substitution for `super` expressions inside of async methods. + */ let superContainerStack: SuperContainer[]; return transformSourceFile; + /** + * Transform TypeScript-specific syntax in a SourceFile. + * + * @param node A SourceFile node. + */ function transformSourceFile(node: SourceFile) { currentSourceFile = node; node = visitEachChild(node, visitor, context); @@ -136,8 +160,7 @@ namespace ts { * @param node The node to visit. */ function namespaceElementVisitorWorker(node: Node): Node { - if (node.transformFlags & TransformFlags.TypeScript - || node.flags & NodeFlags.Export) { + if (node.transformFlags & TransformFlags.TypeScript || isExported(node)) { // This node is explicitly marked as TypeScript, or is exported at the namespace // level, so we should transform the node. node = visitTypeScript(node); @@ -165,12 +188,25 @@ namespace ts { * @param node The node to visit. */ function classElementVisitorWorker(node: Node) { - if (node.kind === SyntaxKind.Constructor) { - // TypeScript constructors are elided. - return undefined; - } + switch (node.kind) { + case SyntaxKind.Constructor: + // TypeScript constructors are transformed in `transformClassDeclaration`. + // We elide them here as `visitorWorker` checks transform flags, which could + // erronously include an ES6 constructor without TypeScript syntax. + return undefined; - return visitorWorker(node); + case SyntaxKind.PropertyDeclaration: + case SyntaxKind.IndexSignature: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + case SyntaxKind.MethodDeclaration: + // Fallback to the default visit behavior. + return visitorWorker(node); + + default: + Debug.fail("Unexpected node."); + break; + } } /** @@ -179,9 +215,9 @@ namespace ts { * @param node The node to visit. */ function visitTypeScript(node: Node): Node { - // TypeScript ambient declarations are elided. if (node.flags & NodeFlags.Ambient) { - return; + // TypeScript ambient declarations are elided. + return undefined; } switch (node.kind) { @@ -233,8 +269,7 @@ namespace ts { // TypeScript property declarations are elided. case SyntaxKind.Constructor: - // TypeScript constructors are elided. The constructor of a class will be - // transformed as part of `transformClassDeclaration`. + // TypeScript constructors are transformed in `transformClassDeclaration`. return undefined; case SyntaxKind.ClassDeclaration: @@ -376,11 +411,7 @@ namespace ts { */ function visitClassDeclaration(node: ClassDeclaration): NodeArrayNode { const staticProperties = getInitializedProperties(node, /*isStatic*/ true); - const statements: Statement[] = []; - const modifiers = visitNodes(node.modifiers, visitor, isModifier); - const heritageClauses = visitNodes(node.heritageClauses, visitor, isHeritageClause); - const members = transformClassMembers(node, heritageClauses !== undefined); - let decoratedClassAlias: Identifier; + const hasExtendsClause = getClassExtendsHeritageClauseElement(node) !== undefined; // emit name if // - node has a name @@ -391,166 +422,28 @@ namespace ts { name = getGeneratedNameForNode(node); } - if (node.decorators) { - // When we emit an ES6 class that has a class decorator, we must tailor the - // emit to certain specific cases. - // - // In the simplest case, we emit the class declaration as a let declaration, and - // evaluate decorators after the close of the class body: - // - // [Example 1] - // --------------------------------------------------------------------- - // TypeScript | Javascript - // --------------------------------------------------------------------- - // @dec | let C = class C { - // class C { | } - // } | C = __decorate([dec], C); - // --------------------------------------------------------------------- - // @dec | let C = class C { - // export class C { | } - // } | C = __decorate([dec], C); - // | export { C }; - // --------------------------------------------------------------------- - // - // 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. - // - // 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: - // - // [Example 2] - // --------------------------------------------------------------------- - // 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 { | 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 { C }; - // --------------------------------------------------------------------- - // - // If a class declaration is the default export of a module, we instead emit - // the export after the decorated declaration: - // - // [Example 3] - // --------------------------------------------------------------------- - // 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 { | } - // } | C = __decorate([dec], C); - // | export default 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: - // - // [Example 4] - // --------------------------------------------------------------------- - // 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; - // --------------------------------------------------------------------- - // - - // class ${name} ${heritageClauses} { - // ${members} - // } - let classExpression: Expression = setOriginalNode( - createClassExpression( - name, - heritageClauses, - members, - /*location*/ node - ), - node - ); - - // Record an alias to avoid class double-binding. - if (resolver.getNodeCheckFlags(getOriginalNode(node)) & NodeCheckFlags.ClassWithBodyScopedClassBinding) { - enableExpressionSubstitutionForDecoratedClasses(); - decoratedClassAlias = createUniqueName(node.name && !isGeneratedIdentifier(node.name) ? node.name.text : "default"); - decoratedClassAliases[getOriginalNodeId(node)] = decoratedClassAlias; - - // We emit the class alias as a `let` declaration here so that it has the same - // TDZ as the class. - - // let ${decoratedClassAlias}; - addNode(statements, - createVariableStatement( - /*modifiers*/ undefined, - createVariableDeclarationList([ - createVariableDeclaration(decoratedClassAlias) - ], - /*location*/ undefined, - NodeFlags.Let) - ) - ); - - // ${decoratedClassAlias} = ${classExpression} - classExpression = createAssignment( - cloneNode(decoratedClassAlias), - classExpression, - /*location*/ node); - } - - // let ${name} = ${classExpression}; - addNode(statements, - createVariableStatement( - /*modifiers*/ undefined, - createVariableDeclarationList([ - createVariableDeclaration( - name, - classExpression - ) - ], - /*location*/ undefined, - NodeFlags.Let) - ) - ); - } - else { + let decoratedClassAlias: Identifier; + const statements: Statement[] = []; + if (!node.decorators) { // ${modifiers} class ${name} ${heritageClauses} { // ${members} // } addNode(statements, setOriginalNode( createClassDeclaration( - modifiers, + visitNodes(node.modifiers, visitor, isModifier), name, - heritageClauses, - members, + visitNodes(node.heritageClauses, visitor, isHeritageClause), + transformClassMembers(node, hasExtendsClause), /*location*/ node ), node ) ); } + else { + decoratedClassAlias = addClassDeclarationHeadWithDecorators(statements, node, name, hasExtendsClause); + } // Emit static property assignment. Because classDeclaration is lexically evaluated, // it is safe to emit static property assignment after classDeclaration @@ -575,13 +468,168 @@ namespace ts { addNode(statements, createExportDefault(name)); } else if (isNamedExternalModuleExport(node)) { - addNode(statements, createModuleExport(name)); + addNode(statements, createExternalModuleExport(name)); } } return createNodeArrayNode(statements); } + /** + * Transforms a decorated class declaration and appends the resulting statements. If + * the class requires an alias to avoid issues with double-binding, the alias is returned. + * + * @param node A ClassDeclaration node. + * @param name The name of the class. + * @param hasExtendsClause A value indicating whether + */ + function addClassDeclarationHeadWithDecorators(statements: Statement[], node: ClassDeclaration, name: Identifier, hasExtendsClause: boolean) { + // When we emit an ES6 class that has a class decorator, we must tailor the + // emit to certain specific cases. + // + // In the simplest case, we emit the class declaration as a let declaration, and + // evaluate decorators after the close of the class body: + // + // [Example 1] + // --------------------------------------------------------------------- + // TypeScript | Javascript + // --------------------------------------------------------------------- + // @dec | let C = class C { + // class C { | } + // } | C = __decorate([dec], C); + // --------------------------------------------------------------------- + // @dec | let C = class C { + // export class C { | } + // } | C = __decorate([dec], C); + // | export { C }; + // --------------------------------------------------------------------- + // + // 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. + // + // 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: + // + // [Example 2] + // --------------------------------------------------------------------- + // 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 { | 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 { C }; + // --------------------------------------------------------------------- + // + // If a class declaration is the default export of a module, we instead emit + // the export after the decorated declaration: + // + // [Example 3] + // --------------------------------------------------------------------- + // 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 { | } + // } | C = __decorate([dec], C); + // | export default 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: + // + // [Example 4] + // --------------------------------------------------------------------- + // 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; + // --------------------------------------------------------------------- + // + + // ... = class ${name} ${heritageClauses} { + // ${members} + // } + let classExpression: Expression = setOriginalNode( + createClassExpression( + name, + visitNodes(node.heritageClauses, visitor, isHeritageClause), + transformClassMembers(node, hasExtendsClause), + /*location*/ node + ), + node + ); + + // Record an alias to avoid class double-binding. + let decoratedClassAlias: Identifier; + if (resolver.getNodeCheckFlags(getOriginalNode(node)) & NodeCheckFlags.DecoratedClassWithSelfReference) { + enableExpressionSubstitutionForDecoratedClasses(); + decoratedClassAlias = createUniqueName(node.name && !isGeneratedIdentifier(node.name) ? node.name.text : "default"); + decoratedClassAliases[getOriginalNodeId(node)] = decoratedClassAlias; + + // We emit the class alias as a `let` declaration here so that it has the same + // TDZ as the class. + + // let ${decoratedClassAlias}; + addNode(statements, + createVariableStatement( + /*modifiers*/ undefined, + createVariableDeclarationList([ + createVariableDeclaration(decoratedClassAlias) + ], + /*location*/ undefined, + NodeFlags.Let) + ) + ); + + // ${decoratedClassAlias} = ${classExpression} + classExpression = createAssignment( + decoratedClassAlias, + classExpression, + /*location*/ node); + } + + // let ${name} = ${classExpression}; + addNode(statements, + createVariableStatement( + /*modifiers*/ undefined, + createVariableDeclarationList([ + createVariableDeclaration( + name, + classExpression + ) + ], + /*location*/ undefined, + NodeFlags.Let) + ) + ); + + return decoratedClassAlias; + } + /** * Transforms a class expression with TypeScript syntax into compatible ES6. * @@ -638,7 +686,7 @@ namespace ts { const members: ClassElement[] = []; addNode(members, transformConstructor(node, hasExtendsClause)); addNodes(members, visitNodes(node.members, classElementVisitor, isClassElement)); - return members; + return createNodeArray(members, /*location*/ node.members); } /** @@ -2489,9 +2537,15 @@ namespace ts { && (node.flags & NodeFlags.Default) !== 0; } + /** + * Gets a value indicating whether a node is the first declaration of its kind. + * + * @param node A Declaration node. + * @param kind The SyntaxKind to find among related declarations. + */ function isFirstDeclarationOfKind(node: Declaration, kind: SyntaxKind) { const original = getOriginalNode(node); - return !forEach(original.symbol && original.symbol.declarations, declaration => declaration.kind === kind && declaration.pos < original.pos); + return original.symbol && getDeclarationOfKind(original.symbol, kind) === original; } /** @@ -2511,7 +2565,7 @@ namespace ts { ); } - function createModuleExport(exportName: Identifier) { + function createExternalModuleExport(exportName: Identifier) { return createExportDeclaration( createNamedExports([ createExportSpecifier(exportName) @@ -2544,12 +2598,13 @@ namespace ts { previousOnBeforeEmitNode(node); const kind = node.kind; - if (hasEnabledExpressionSubstitutionForDecoratedClasses - && kind === SyntaxKind.ClassDeclaration && node.decorators) { + if (enabledSubstitutions & TypeScriptSubstitutionFlags.DecoratedClasses + && kind === SyntaxKind.ClassDeclaration + && node.decorators) { currentDecoratedClassAliases[getOriginalNodeId(node)] = decoratedClassAliases[getOriginalNodeId(node)]; } - if (hasEnabledExpressionSubstitutionForAsyncMethodsWithSuper + if (enabledSubstitutions & TypeScriptSubstitutionFlags.NamespaceExports && (kind === SyntaxKind.ClassDeclaration || kind === SyntaxKind.Constructor || kind === SyntaxKind.MethodDeclaration @@ -2563,7 +2618,7 @@ namespace ts { superContainerStack.push(node); } - if (hasEnabledExpressionSubstitutionForNamespaceExports + if (enabledSubstitutions & TypeScriptSubstitutionFlags.NamespaceExports && kind === SyntaxKind.ModuleDeclaration) { namespaceNestLevel++; } @@ -2573,12 +2628,13 @@ namespace ts { previousOnAfterEmitNode(node); const kind = node.kind; - if (hasEnabledExpressionSubstitutionForDecoratedClasses - && kind === SyntaxKind.ClassDeclaration && node.decorators) { + if (enabledSubstitutions & TypeScriptSubstitutionFlags.DecoratedClasses + && kind === SyntaxKind.ClassDeclaration + && node.decorators) { currentDecoratedClassAliases[getOriginalNodeId(node)] = undefined; } - if (hasEnabledExpressionSubstitutionForAsyncMethodsWithSuper + if (enabledSubstitutions & TypeScriptSubstitutionFlags.NamespaceExports && (kind === SyntaxKind.ClassDeclaration || kind === SyntaxKind.Constructor || kind === SyntaxKind.MethodDeclaration @@ -2590,7 +2646,7 @@ namespace ts { } } - if (hasEnabledExpressionSubstitutionForNamespaceExports + if (enabledSubstitutions & TypeScriptSubstitutionFlags.NamespaceExports && kind === SyntaxKind.ModuleDeclaration) { namespaceNestLevel--; } @@ -2604,7 +2660,7 @@ namespace ts { return substituteExpressionIdentifier(node); } - if (hasEnabledExpressionSubstitutionForAsyncMethodsWithSuper) { + if (enabledSubstitutions & TypeScriptSubstitutionFlags.NamespaceExports) { switch (node.kind) { case SyntaxKind.CallExpression: return substituteCallExpression(node); @@ -2619,9 +2675,9 @@ namespace ts { } function substituteExpressionIdentifier(node: Identifier): Expression { - if (hasEnabledExpressionSubstitutionForDecoratedClasses + if (enabledSubstitutions & TypeScriptSubstitutionFlags.DecoratedClasses && !nodeIsSynthesized(node) - && resolver.getNodeCheckFlags(node) & NodeCheckFlags.BodyScopedClassBinding) { + && resolver.getNodeCheckFlags(node) & NodeCheckFlags.SelfReferenceInDecoratedClass) { // 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. @@ -2635,7 +2691,7 @@ namespace ts { } } - if (hasEnabledExpressionSubstitutionForNamespaceExports + if (enabledSubstitutions & TypeScriptSubstitutionFlags.NamespaceExports && namespaceNestLevel > 0) { // If we are nested within a namespace declaration, we may need to qualifiy // an identifier that is exported from a merged namespace. @@ -2702,8 +2758,8 @@ namespace ts { } function enableExpressionSubstitutionForAsyncMethodsWithSuper() { - if (!hasEnabledExpressionSubstitutionForAsyncMethodsWithSuper) { - hasEnabledExpressionSubstitutionForAsyncMethodsWithSuper = true; + if ((enabledSubstitutions & TypeScriptSubstitutionFlags.NamespaceExports) === 0) { + enabledSubstitutions |= TypeScriptSubstitutionFlags.NamespaceExports; // We need to enable substitutions for call, property access, and element access // if we need to rewrite super calls. @@ -2721,8 +2777,8 @@ namespace ts { } function enableExpressionSubstitutionForDecoratedClasses() { - if (!hasEnabledExpressionSubstitutionForDecoratedClasses) { - hasEnabledExpressionSubstitutionForDecoratedClasses = true; + if ((enabledSubstitutions & TypeScriptSubstitutionFlags.DecoratedClasses) === 0) { + enabledSubstitutions |= TypeScriptSubstitutionFlags.DecoratedClasses; // We need to enable substitutions for identifiers. This allows us to // substitute class names inside of a class declaration. @@ -2735,8 +2791,8 @@ namespace ts { } function enableExpressionSubstitutionForNamespaceExports() { - if (!hasEnabledExpressionSubstitutionForNamespaceExports) { - hasEnabledExpressionSubstitutionForNamespaceExports = true; + if ((enabledSubstitutions & TypeScriptSubstitutionFlags.NamespaceExports) === 0) { + enabledSubstitutions |= TypeScriptSubstitutionFlags.NamespaceExports; // We need to enable substitutions for identifiers. This allows us to // substitute the names of exported members of a namespace. diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 07d1daec66..5cced24249 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2091,8 +2091,8 @@ namespace ts { 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. + DecoratedClassWithSelfReference = 0x00100000, // Decorated class that contains a binding to itself inside of the class body. + SelfReferenceInDecoratedClass = 0x00200000, // Binding to a decorated class inside of the class's body. } /* @internal */ diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 1b707cedc4..421737553d 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -1352,7 +1352,7 @@ namespace ts { - export function isNodeDescendentOf(node: Node, ancestor: Node): boolean { + export function isNodeDescendantOf(node: Node, ancestor: Node): boolean { while (node) { if (node === ancestor) return true; node = node.parent; @@ -2866,7 +2866,8 @@ namespace ts { } export function isGeneratedIdentifier(node: Node): node is Identifier { - return isIdentifier(node) && node.autoGenerateKind > GeneratedIdentifierKind.Node; + // Using `>` here catches both `GeneratedIdentifierKind.None` and `undefined`. + return isIdentifier(node) && node.autoGenerateKind > GeneratedIdentifierKind.None; } // Keywords