From ad0dd4e2e2de21ddd0c6154ddbcf0feca5e56d6f Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Fri, 18 Mar 2016 12:38:19 -0700 Subject: [PATCH] Fixed perf issue in binder, plus PR feedback --- src/compiler/binder.ts | 531 ++++++++++++--------- src/compiler/core.ts | 5 +- src/compiler/transformers/module/module.ts | 343 ++++++------- src/compiler/types.ts | 11 +- src/compiler/utilities.ts | 11 +- src/compiler/visitor.ts | 10 +- 6 files changed, 506 insertions(+), 405 deletions(-) diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index b5aec17fe66..08233c24fa0 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -37,7 +37,7 @@ namespace ts { return ModuleInstanceState.ConstEnumOnly; } // 3. non-exported import declarations - else if ((node.kind === SyntaxKind.ImportDeclaration || node.kind === SyntaxKind.ImportEqualsDeclaration) && !(getModifierFlags(node) & ModifierFlags.Export)) { + else if ((node.kind === SyntaxKind.ImportDeclaration || node.kind === SyntaxKind.ImportEqualsDeclaration) && !(hasModifier(node, ModifierFlags.Export))) { return ModuleInstanceState.NonInstantiated; } // 4. other uninstantiated module declarations. @@ -133,7 +133,7 @@ namespace ts { let classifiableNames: Map; // state used to aggregate transform flags during bind. - let subtreeTransformFlags: TransformFlags; + let subtreeTransformFlags: TransformFlags = TransformFlags.None; let skipTransformFlagAggregation: boolean; function bindSourceFile(f: SourceFile, opts: CompilerOptions) { @@ -141,7 +141,6 @@ namespace ts { options = opts; inStrictMode = !!file.externalModuleIndicator; classifiableNames = {}; - subtreeTransformFlags = undefined; skipTransformFlagAggregation = isDeclarationFile(file); Symbol = objectAllocator.getSymbolConstructor(); @@ -167,6 +166,7 @@ namespace ts { hasAsyncFunctions = false; hasDecorators = false; hasParameterDecorators = false; + subtreeTransformFlags = TransformFlags.None; } return bindSourceFile; @@ -256,7 +256,7 @@ namespace ts { case SyntaxKind.FunctionDeclaration: case SyntaxKind.ClassDeclaration: - return getModifierFlags(node) & ModifierFlags.Default ? "default" : undefined; + return hasModifier(node, ModifierFlags.Default) ? "default" : undefined; case SyntaxKind.JSDocFunctionType: return isJSDocConstructSignature(node) ? "__new" : "__call"; case SyntaxKind.Parameter: @@ -284,7 +284,7 @@ namespace ts { function declareSymbol(symbolTable: SymbolTable, parent: Symbol, node: Declaration, includes: SymbolFlags, excludes: SymbolFlags): Symbol { Debug.assert(!hasDynamicName(node)); - const isDefaultExport = getModifierFlags(node) & ModifierFlags.Default; + const isDefaultExport = hasModifier(node, ModifierFlags.Default); // The exported symbol for an export default function/class node is always named "default" const name = isDefaultExport && parent ? "default" : getDeclarationName(node); @@ -329,7 +329,7 @@ namespace ts { : Diagnostics.Duplicate_identifier_0; forEach(symbol.declarations, declaration => { - if (getModifierFlags(declaration) & ModifierFlags.Default) { + if (hasModifier(declaration, ModifierFlags.Default)) { message = Diagnostics.A_module_cannot_have_multiple_default_exports; } }); @@ -862,7 +862,7 @@ namespace ts { } function declareClassMember(node: Declaration, symbolFlags: SymbolFlags, symbolExcludes: SymbolFlags) { - return getModifierFlags(node) & ModifierFlags.Static + return hasModifier(node, ModifierFlags.Static) ? declareSymbol(container.symbol.exports, container.symbol, node, symbolFlags, symbolExcludes) : declareSymbol(container.symbol.members, container.symbol, node, symbolFlags, symbolExcludes); } @@ -899,7 +899,7 @@ namespace ts { function bindModuleDeclaration(node: ModuleDeclaration) { setExportContextFlag(node); if (isAmbientModule(node)) { - if (getModifierFlags(node) & ModifierFlags.Export) { + if (hasModifier(node, ModifierFlags.Export)) { errorOnFirstToken(node, Diagnostics.export_modifier_cannot_be_applied_to_ambient_modules_and_module_augmentations_since_they_are_always_visible); } declareSymbolAndAddToSymbolTable(node, SymbolFlags.ValueModule, SymbolFlags.ValueModuleExcludes); @@ -1184,41 +1184,22 @@ namespace ts { // symbols we do specialized work when we recurse. For example, we'll keep track of // the current 'container' node when it changes. This helps us know which symbol table // a local should go into for example. - aggregateTransformFlagsIfNeededAndBindChildren(node); - - inStrictMode = savedInStrictMode; - } - - function aggregateTransformFlagsIfNeededAndBindChildren(node: Node) { - if (node.transformFlags !== undefined) { - skipTransformFlagAggregationAndBindChildren(node); + if (skipTransformFlagAggregation) { + bindChildren(node); } - else { - aggregateTransformFlagsAndBindChildren(node); - } - } - - function skipTransformFlagAggregationAndBindChildren(node: Node) { - if (!skipTransformFlagAggregation) { + else if (node.transformFlags & TransformFlags.HasComputedFlags) { skipTransformFlagAggregation = true; bindChildren(node); skipTransformFlagAggregation = false; } else { - bindChildren(node); - } - } - - function aggregateTransformFlagsAndBindChildren(node: Node) { - if (!skipTransformFlagAggregation) { const savedSubtreeTransformFlags = subtreeTransformFlags; subtreeTransformFlags = 0; bindChildren(node); subtreeTransformFlags = savedSubtreeTransformFlags | computeTransformFlagsForNode(node, subtreeTransformFlags); } - else { - bindChildren(node); - } + + inStrictMode = savedInStrictMode; } function updateStrictMode(node: Node) { @@ -1799,15 +1780,9 @@ namespace ts { * @param subtreeFlags Transform flags computed for this node's subtree */ export function computeTransformFlagsForNode(node: Node, subtreeFlags: TransformFlags): TransformFlags { - // Ambient nodes are TypeScript syntax and the flags of their subtree are ignored. - if (getModifierFlags(node) & ModifierFlags.Ambient) { - return (node.transformFlags = TransformFlags.AssertTypeScript) - & ~(node.excludeTransformFlags = TransformFlags.NodeExcludes); - } - // Mark transformations needed for each node - let transformFlags: TransformFlags; - let excludeFlags: TransformFlags; + let transformFlags = TransformFlags.None; + let excludeFlags = TransformFlags.None; switch (node.kind) { case SyntaxKind.PublicKeyword: case SyntaxKind.PrivateKeyword: @@ -1823,7 +1798,7 @@ namespace ts { case SyntaxKind.AsExpression: case SyntaxKind.ReadonlyKeyword: // These nodes are TypeScript syntax. - transformFlags |= TransformFlags.AssertTypeScript; + transformFlags = TransformFlags.AssertTypeScript; break; case SyntaxKind.JsxElement: @@ -1835,12 +1810,12 @@ namespace ts { case SyntaxKind.JsxSpreadAttribute: case SyntaxKind.JsxExpression: // These nodes are Jsx syntax. - transformFlags |= TransformFlags.AssertJsx; + transformFlags = TransformFlags.AssertJsx; break; case SyntaxKind.ExportKeyword: // This node is both ES6 and TypeScript syntax. - transformFlags |= TransformFlags.AssertES6 | TransformFlags.TypeScript; + transformFlags = TransformFlags.AssertES6 | TransformFlags.TypeScript; break; case SyntaxKind.DefaultKeyword: @@ -1854,10 +1829,9 @@ namespace ts { case SyntaxKind.ForOfStatement: case SyntaxKind.YieldExpression: // These nodes are ES6 syntax. - transformFlags |= TransformFlags.AssertES6; + transformFlags = TransformFlags.AssertES6; break; - case SyntaxKind.AnyKeyword: case SyntaxKind.NumberKeyword: case SyntaxKind.StringKeyword: @@ -1886,36 +1860,42 @@ namespace ts { case SyntaxKind.ThisType: case SyntaxKind.StringLiteralType: // Types and signatures are TypeScript syntax, and exclude all other facts. + subtreeFlags = TransformFlags.None; excludeFlags = TransformFlags.TypeExcludes; - transformFlags |= TransformFlags.AssertTypeScript; + transformFlags = TransformFlags.AssertTypeScript; break; case SyntaxKind.ComputedPropertyName: // Even though computed property names are ES6, we don't treat them as such. // This is so that they can flow through PropertyName transforms unaffected. // Instead, we mark the container as ES6, so that it can properly handle the transform. - transformFlags |= TransformFlags.ContainsComputedPropertyName; + transformFlags = TransformFlags.ContainsComputedPropertyName; break; case SyntaxKind.SpreadElementExpression: // This node is ES6 syntax, but is handled by a containing node. - transformFlags |= TransformFlags.ContainsSpreadElementExpression; + transformFlags = TransformFlags.ContainsSpreadElementExpression; break; case SyntaxKind.SuperKeyword: // This node is ES6 syntax. - transformFlags |= TransformFlags.AssertES6; + transformFlags = TransformFlags.AssertES6; break; case SyntaxKind.ThisKeyword: // Mark this node and its ancestors as containing a lexical `this` keyword. - transformFlags |= TransformFlags.ContainsLexicalThis; + transformFlags = TransformFlags.ContainsLexicalThis; break; case SyntaxKind.ObjectBindingPattern: case SyntaxKind.ArrayBindingPattern: // These nodes are ES6 syntax. - transformFlags |= TransformFlags.AssertES6; + transformFlags = TransformFlags.AssertES6; + break; + + case SyntaxKind.Decorator: + // This node is TypeScript syntax, and marks its container as also being TypeScript syntax. + transformFlags = TransformFlags.AssertTypeScript | TransformFlags.ContainsDecorators; break; case SyntaxKind.ObjectLiteralExpression: @@ -1923,20 +1903,12 @@ namespace ts { if (subtreeFlags & TransformFlags.ContainsComputedPropertyName) { // If an ObjectLiteralExpression contains a ComputedPropertyName, then it // is an ES6 node. - transformFlags |= TransformFlags.AssertES6; + transformFlags = TransformFlags.AssertES6; } break; case SyntaxKind.CallExpression: - excludeFlags = TransformFlags.ArrayLiteralOrCallOrNewExcludes; - if (subtreeFlags & TransformFlags.ContainsSpreadElementExpression - || isSuperCall(node) - || isSuperPropertyCall(node)) { - // If the this node contains a SpreadElementExpression, or is a super call, then it is an ES6 - // node. - transformFlags |= TransformFlags.AssertES6; - } - break; + return computeCallExpression(node, subtreeFlags); case SyntaxKind.ArrayLiteralExpression: case SyntaxKind.NewExpression: @@ -1944,179 +1916,66 @@ namespace ts { if (subtreeFlags & TransformFlags.ContainsSpreadElementExpression) { // If the this node contains a SpreadElementExpression, then it is an ES6 // node. - transformFlags |= TransformFlags.AssertES6; + transformFlags = TransformFlags.AssertES6; } - break; - case SyntaxKind.Decorator: - // This node is TypeScript syntax, and marks its container as also being TypeScript syntax. - transformFlags |= TransformFlags.AssertTypeScript | TransformFlags.ContainsDecorators; break; case SyntaxKind.ModuleDeclaration: + // An ambient declaration is TypeScript syntax. + if (hasModifier(node, ModifierFlags.Ambient)) { + subtreeFlags = TransformFlags.None; + } + // This node is TypeScript syntax, and excludes markers that should not escape the module scope. excludeFlags = TransformFlags.ModuleExcludes; - transformFlags |= TransformFlags.AssertTypeScript; + transformFlags = TransformFlags.AssertTypeScript; break; case SyntaxKind.ParenthesizedExpression: - // If the node is synthesized, it means the emitter put the parentheses there, - // not the user. If we didn't want them, the emitter would not have put them - // there. - if (!nodeIsSynthesized(node)) { - if ((node).expression.kind === SyntaxKind.AsExpression - || (node).expression.kind === SyntaxKind.TypeAssertionExpression) { - transformFlags = TransformFlags.AssertTypeScript; - } - } - - // If the expression of a ParenthesizedExpression is a destructuring assignment, - // then the ParenthesizedExpression is a destructuring assignment. - if ((node).expression.transformFlags & TransformFlags.DestructuringAssignment) { - transformFlags |= TransformFlags.DestructuringAssignment; - } - - break; + return computeParenthesizedExpression(node, subtreeFlags); case SyntaxKind.BinaryExpression: - if (isDestructuringAssignment(node)) { - // Destructuring assignments are ES6 syntax. - transformFlags |= TransformFlags.AssertES6 | TransformFlags.DestructuringAssignment; - } - else if ((node).operatorToken.kind === SyntaxKind.AsteriskAsteriskToken - || (node).operatorToken.kind === SyntaxKind.AsteriskAsteriskEqualsToken) { - // Exponentiation is ES7 syntax. - transformFlags |= TransformFlags.AssertES7; - } - - break; + return computeBinaryExpression(node, subtreeFlags); case SyntaxKind.ExpressionStatement: // If the expression of an expression statement is a destructuring assignment, // then we treat the statement as ES6 so that we can indicate that we do not // need to hold on to the right-hand side. if ((node).expression.transformFlags & TransformFlags.DestructuringAssignment) { - transformFlags |= TransformFlags.AssertES6; + transformFlags = TransformFlags.AssertES6; } break; case SyntaxKind.Parameter: - // If the parameter has a question token, then it is TypeScript syntax. - if ((node).questionToken) { - transformFlags |= TransformFlags.AssertTypeScript; - } - - // If a parameter has an accessibility modifier, then it is TypeScript syntax. - if (getModifierFlags(node) & ModifierFlags.AccessibilityModifier) { - transformFlags |= TransformFlags.AssertTypeScript | TransformFlags.ContainsParameterPropertyAssignments; - } - - // If a parameter has an initializer, a binding pattern or a dotDotDot token, then - // it is ES6 syntax and its container must emit default value assignments or parameter destructuring downlevel. - if ((node).initializer - || (node).dotDotDotToken - || isBindingPattern((node).name)) { - transformFlags |= TransformFlags.AssertES6 | TransformFlags.ContainsDefaultValueAssignments; - } - - break; + return computeParameter(node, subtreeFlags); case SyntaxKind.ArrowFunction: - // An ArrowFunction is ES6 syntax, and excludes markers that should not escape the scope of an ArrowFunction. - excludeFlags = TransformFlags.ArrowFunctionExcludes; - transformFlags = TransformFlags.AssertES6; - - // If an ArrowFunction contains a lexical this, its container must capture the lexical this. - if (subtreeFlags & TransformFlags.ContainsLexicalThis) { - transformFlags |= TransformFlags.ContainsCapturedLexicalThis; - } - - // An async arrow function is TypeScript syntax. - if (getModifierFlags(node) & ModifierFlags.Async) { - transformFlags |= TransformFlags.AssertTypeScript; - } - - break; + return computeArrowFunction(node, subtreeFlags); case SyntaxKind.FunctionExpression: - // A FunctionExpression excludes markers that should not escape the scope of a FunctionExpression. - excludeFlags = TransformFlags.FunctionExcludes; - - // If a FunctionExpression contains an asterisk token, or its subtree has marked the container - // as needing to capture the lexical this, then this node is ES6 syntax. - if ((node).asteriskToken - || subtreeFlags & TransformFlags.ContainsCapturedLexicalThis - || subtreeFlags & TransformFlags.ContainsDefaultValueAssignments) { - transformFlags |= TransformFlags.AssertES6; - } - - // An async function expression is TypeScript syntax. - if (getModifierFlags(node) & ModifierFlags.Async) { - transformFlags |= TransformFlags.AssertTypeScript; - } - - break; + return computeFunctionExpression(node, subtreeFlags); case SyntaxKind.FunctionDeclaration: - // A FunctionDeclaration excludes markers that should not escape the scope of a FunctionDeclaration. - excludeFlags = TransformFlags.FunctionExcludes; - - // A FunctionDeclaration without a body is an overload and is TypeScript syntax. - if (!(node).body) { - transformFlags = TransformFlags.AssertTypeScript; - break; - } - - // If a FunctionDeclaration has an asterisk token, is exported, or its - // subtree has marked the container as needing to capture the lexical `this`, - // then this node is ES6 syntax. - if ((node).asteriskToken - || getModifierFlags(node) & ModifierFlags.Export - || subtreeFlags & TransformFlags.ContainsCapturedLexicalThis - || subtreeFlags & TransformFlags.ContainsDefaultValueAssignments) { - transformFlags |= TransformFlags.AssertES6; - } - - // An async function declaration is TypeScript syntax. - if (getModifierFlags(node) & ModifierFlags.Async) { - transformFlags |= TransformFlags.AssertTypeScript; - } - - break; + return computeFunctionDeclaration(node, subtreeFlags); case SyntaxKind.VariableDeclaration: - // A VariableDeclaration with a binding pattern is ES6 syntax. - if (isBindingPattern((node).name)) { - transformFlags |= TransformFlags.AssertES6; - } - - break; + return computeVariableDeclaration(node, subtreeFlags); case SyntaxKind.VariableDeclarationList: // If a VariableDeclarationList is `let` or `const`, then it is ES6 syntax. if (node.flags & NodeFlags.BlockScoped) { - transformFlags |= TransformFlags.AssertES6 | TransformFlags.ContainsBlockScopedBinding; + transformFlags = TransformFlags.AssertES6 | TransformFlags.ContainsBlockScopedBinding; } break; case SyntaxKind.VariableStatement: - // If a VariableStatement is exported, then it is either ES6 or TypeScript syntax. - if (getModifierFlags(node) & ModifierFlags.Export) { - transformFlags |= TransformFlags.AssertES6 | TransformFlags.AssertTypeScript; - } - - break; + return computeVariableStatement(node, subtreeFlags); case SyntaxKind.LabeledStatement: - // A labeled statement containing a block scoped binding *may* need to be transformed from ES6. - if (subtreeFlags & TransformFlags.ContainsBlockScopedBinding - && isIterationStatement(this, /*lookInLabeledStatements*/ true)) { - transformFlags |= TransformFlags.AssertES6; - } - - break; + return computeLabeledStatement(node, subtreeFlags); case SyntaxKind.DoStatement: case SyntaxKind.WhileStatement: @@ -2124,36 +1983,26 @@ namespace ts { case SyntaxKind.ForInStatement: // A loop containing a block scoped binding *may* need to be transformed from ES6. if (subtreeFlags & TransformFlags.ContainsBlockScopedBinding) { - transformFlags |= TransformFlags.AssertES6; + transformFlags = TransformFlags.AssertES6; } break; case SyntaxKind.ClassDeclaration: + return computeClassDeclaration(node, subtreeFlags); + case SyntaxKind.ClassExpression: - // A ClassDeclarations or ClassExpression is ES6 syntax. - excludeFlags = TransformFlags.ClassExcludes; - transformFlags = TransformFlags.AssertES6; - - // A class with a parameter property assignment, property initializer, or decorator is - // TypeScript syntax. - if (subtreeFlags & TransformFlags.ContainsParameterPropertyAssignments - || subtreeFlags & TransformFlags.ContainsPropertyInitializer - || subtreeFlags & TransformFlags.ContainsDecorators) { - transformFlags |= TransformFlags.AssertTypeScript; - } - - break; + return computeClassExpression(node, subtreeFlags); case SyntaxKind.HeritageClause: if ((node).token === SyntaxKind.ExtendsKeyword) { // An `extends` HeritageClause is ES6 syntax. - transformFlags |= TransformFlags.AssertES6; + transformFlags = TransformFlags.AssertES6; } else { // An `implements` HeritageClause is TypeScript syntax. Debug.assert((node).token === SyntaxKind.ImplementsKeyword); - transformFlags |= TransformFlags.AssertTypeScript; + transformFlags = TransformFlags.AssertTypeScript; } break; @@ -2161,7 +2010,7 @@ namespace ts { case SyntaxKind.ExpressionWithTypeArguments: // An ExpressionWithTypeArguments is ES6 syntax, as it is used in the // extends clause of a class. - transformFlags |= TransformFlags.AssertES6; + transformFlags = TransformFlags.AssertES6; // If an ExpressionWithTypeArguments contains type arguments, then it // is TypeScript syntax. @@ -2174,7 +2023,7 @@ namespace ts { case SyntaxKind.Constructor: // A Constructor is ES6 syntax. excludeFlags = TransformFlags.ConstructorExcludes; - transformFlags |= TransformFlags.AssertES6; + transformFlags = TransformFlags.AssertES6; // An overload constructor is TypeScript syntax. if (!(node).body) { @@ -2185,7 +2034,7 @@ namespace ts { case SyntaxKind.PropertyDeclaration: // A PropertyDeclaration is TypeScript syntax. - transformFlags |= TransformFlags.AssertTypeScript; + transformFlags = TransformFlags.AssertTypeScript; // If the PropertyDeclaration has an initializer, we need to inform its ancestor // so that it handle the transformation. @@ -2198,13 +2047,13 @@ namespace ts { case SyntaxKind.MethodDeclaration: // A MethodDeclaration is ES6 syntax. excludeFlags = TransformFlags.MethodOrAccessorExcludes; - transformFlags |= TransformFlags.AssertES6; + transformFlags = TransformFlags.AssertES6; // A MethodDeclaration is TypeScript syntax if it is either async, abstract, overloaded, // generic, or has both a computed property name and a decorator. if ((node).body === undefined || (node).typeParameters !== undefined - || getModifierFlags(node) & (ModifierFlags.Async | ModifierFlags.Abstract) + || hasModifier(node, ModifierFlags.Async | ModifierFlags.Abstract) || (subtreeFlags & TransformFlags.ContainsDecorators && subtreeFlags & TransformFlags.ContainsComputedPropertyName)) { transformFlags |= TransformFlags.AssertTypeScript; @@ -2219,10 +2068,10 @@ namespace ts { // A GetAccessor or SetAccessor is TypeScript syntax if it is either abstract, // or has both a computed property name and a decorator. - if (getModifierFlags(node) & ModifierFlags.Abstract || - subtreeFlags & TransformFlags.ContainsDecorators && - subtreeFlags & TransformFlags.ContainsComputedPropertyName) { - transformFlags |= TransformFlags.AssertTypeScript; + if (hasModifier(node, ModifierFlags.Abstract) + || (subtreeFlags & TransformFlags.ContainsDecorators + && subtreeFlags & TransformFlags.ContainsComputedPropertyName)) { + transformFlags = TransformFlags.AssertTypeScript; } break; @@ -2230,7 +2079,7 @@ namespace ts { case SyntaxKind.ImportEqualsDeclaration: // An ImportEqualsDeclaration with a namespace reference is TypeScript. if (!isExternalModuleImportEqualsDeclaration(node)) { - transformFlags |= TransformFlags.AssertTypeScript; + transformFlags = TransformFlags.AssertTypeScript; } break; @@ -2239,20 +2088,258 @@ namespace ts { // If a PropertyAccessExpression starts with a super keyword, then it is // ES6 syntax, and requires a lexical `this` binding. if ((node).expression.kind === SyntaxKind.SuperKeyword) { - transformFlags |= TransformFlags.ContainsLexicalThis; + transformFlags = TransformFlags.ContainsLexicalThis; } break; case SyntaxKind.SourceFile: if (subtreeFlags & TransformFlags.ContainsCapturedLexicalThis) { - transformFlags |= TransformFlags.AssertES6; + transformFlags = TransformFlags.AssertES6; } break; } - return (node.transformFlags = subtreeFlags | transformFlags) - & ~(node.excludeTransformFlags = excludeFlags | TransformFlags.NodeExcludes); + return updateTransformFlags(node, subtreeFlags, transformFlags, excludeFlags); + } + + function computeCallExpression(node: CallExpression, subtreeFlags: TransformFlags) { + let transformFlags = TransformFlags.None; + if (subtreeFlags & TransformFlags.ContainsSpreadElementExpression + || isSuperCall(node) + || isSuperPropertyCall(node)) { + // If the this node contains a SpreadElementExpression, or is a super call, then it is an ES6 + // node. + transformFlags = TransformFlags.AssertES6; + } + + return updateTransformFlags(node, subtreeFlags, transformFlags, TransformFlags.ArrayLiteralOrCallOrNewExcludes); + } + + function computeBinaryExpression(node: BinaryExpression, subtreeFlags: TransformFlags) { + let transformFlags = TransformFlags.None; + if (isDestructuringAssignment(node)) { + // Destructuring assignments are ES6 syntax. + transformFlags = TransformFlags.AssertES6 | TransformFlags.DestructuringAssignment; + } + else if (isExponentiation(node)) { + // Exponentiation is ES7 syntax. + transformFlags = TransformFlags.AssertES7; + } + + return updateTransformFlags(node, subtreeFlags, transformFlags, TransformFlags.None); + } + + function isDestructuringAssignment(node: BinaryExpression) { + return node.operatorToken.kind === SyntaxKind.EqualsToken + && isObjectOrArrayLiteral(node.left); + } + + function isObjectOrArrayLiteral(node: Node) { + switch (node.kind) { + case SyntaxKind.ObjectLiteralExpression: + case SyntaxKind.ArrayLiteralExpression: + return true; + } + + return false; + } + + function isExponentiation(operatorToken: Node) { + switch (operatorToken.kind) { + case SyntaxKind.AsteriskAsteriskToken: + case SyntaxKind.AsteriskAsteriskEqualsToken: + return true; + } + + return false; + } + + function computeParameter(node: ParameterDeclaration, subtreeFlags: TransformFlags) { + let transformFlags = TransformFlags.None; + // If the parameter has a question token, then it is TypeScript syntax. + if (isDefined(node.questionToken)) { + transformFlags |= TransformFlags.AssertTypeScript; + } + + // If a parameter has an accessibility modifier, then it is TypeScript syntax. + if (hasModifier(node, ModifierFlags.AccessibilityModifier)) { + transformFlags |= TransformFlags.AssertTypeScript | TransformFlags.ContainsParameterPropertyAssignments; + } + + // If a parameter has an initializer, a binding pattern or a dotDotDot token, then + // it is ES6 syntax and its container must emit default value assignments or parameter destructuring downlevel. + if (isDefined(node.initializer) || isDefined(node.dotDotDotToken) || isBindingPattern(node.name)) { + transformFlags |= TransformFlags.AssertES6 | TransformFlags.ContainsDefaultValueAssignments; + } + + return updateTransformFlags(node, subtreeFlags, transformFlags, TransformFlags.None); + } + + function computeParenthesizedExpression(node: ParenthesizedExpression, subtreeFlags: TransformFlags) { + let transformFlags = TransformFlags.None; + // If the node is synthesized, it means the emitter put the parentheses there, + // not the user. If we didn't want them, the emitter would not have put them + // there. + if (node.expression.kind === SyntaxKind.AsExpression + || node.expression.kind === SyntaxKind.TypeAssertionExpression) { + transformFlags = TransformFlags.AssertTypeScript; + } + + // If the expression of a ParenthesizedExpression is a destructuring assignment, + // then the ParenthesizedExpression is a destructuring assignment. + if (node.expression.transformFlags & TransformFlags.DestructuringAssignment) { + transformFlags |= TransformFlags.DestructuringAssignment; + } + + return updateTransformFlags(node, subtreeFlags, transformFlags, TransformFlags.None); + } + + function computeClassDeclaration(node: ClassDeclaration, subtreeFlags: TransformFlags) { + // An ambient declaration is TypeScript syntax. + if (hasModifier(node, ModifierFlags.Ambient)) { + return updateTransformFlags(node, TransformFlags.None, TransformFlags.TypeScript, TransformFlags.ClassExcludes); + } + + // A ClassDeclaration is ES6 syntax. + let transformFlags = TransformFlags.AssertES6; + + // A class with a parameter property assignment, property initializer, or decorator is + // TypeScript syntax. + // An exported declaration may be TypeScript syntax. + if (subtreeFlags + & (TransformFlags.ContainsParameterPropertyAssignments + | TransformFlags.ContainsPropertyInitializer + | TransformFlags.ContainsDecorators) + || hasModifier(node, ModifierFlags.Export)) { + transformFlags |= TransformFlags.AssertTypeScript; + } + + return updateTransformFlags(node, subtreeFlags, transformFlags, TransformFlags.ClassExcludes); + } + + function computeClassExpression(node: ClassExpression, subtreeFlags: TransformFlags) { + // A ClassExpression is ES6 syntax. + let transformFlags = TransformFlags.AssertES6; + + // A class with a parameter property assignment, property initializer, or decorator is + // TypeScript syntax. + if (subtreeFlags + & (TransformFlags.ContainsParameterPropertyAssignments + | TransformFlags.ContainsPropertyInitializer + | TransformFlags.ContainsDecorators)) { + transformFlags |= TransformFlags.AssertTypeScript; + } + + return updateTransformFlags(node, subtreeFlags, transformFlags, TransformFlags.ClassExcludes); + } + + function computeFunctionDeclaration(node: FunctionDeclaration, subtreeFlags: TransformFlags) { + const modifiers = getModifierFlags(node); + + // An ambient declaration is TypeScript syntax. + // A FunctionDeclaration without a body is an overload and is TypeScript syntax. + if (!node.body || modifiers & ModifierFlags.Ambient) { + return updateTransformFlags(node, TransformFlags.None, TransformFlags.AssertTypeScript, TransformFlags.FunctionExcludes); + } + + let transformFlags = TransformFlags.None; + + // If a FunctionDeclaration is exported, then it is either ES6 or TypeScript syntax. + if (modifiers & ModifierFlags.Export) { + transformFlags |= TransformFlags.AssertTypeScript | TransformFlags.AssertES6; + } + + // If a FunctionDeclaration has an asterisk token, is exported, or its + // subtree has marked the container as needing to capture the lexical `this`, + // then this node is ES6 syntax. + if (subtreeFlags & (TransformFlags.ContainsCapturedLexicalThis | TransformFlags.ContainsDefaultValueAssignments) + || node.asteriskToken) { + transformFlags |= TransformFlags.AssertES6; + } + + return updateTransformFlags(node, subtreeFlags, transformFlags, TransformFlags.FunctionExcludes); + } + + function computeFunctionExpression(node: FunctionExpression, subtreeFlags: TransformFlags) { + let transformFlags = TransformFlags.None; + + // An async function expression is TypeScript syntax. + if (hasModifier(node, ModifierFlags.Async)) { + transformFlags |= TransformFlags.AssertTypeScript; + } + + // If a FunctionExpression contains an asterisk token, or its subtree has marked the container + // as needing to capture the lexical this, then this node is ES6 syntax. + if (subtreeFlags & (TransformFlags.ContainsCapturedLexicalThis | TransformFlags.ContainsDefaultValueAssignments) + || node.asteriskToken) { + transformFlags |= TransformFlags.AssertES6; + } + + return updateTransformFlags(node, subtreeFlags, transformFlags, TransformFlags.FunctionExcludes); + } + + function computeArrowFunction(node: ArrowFunction, subtreeFlags: TransformFlags) { + // An ArrowFunction is ES6 syntax, and excludes markers that should not escape the scope of an ArrowFunction. + let transformFlags = TransformFlags.AssertES6; + + // An async arrow function is TypeScript syntax. + if (hasModifier(node, ModifierFlags.Async)) { + transformFlags |= TransformFlags.AssertTypeScript; + } + + // If an ArrowFunction contains a lexical this, its container must capture the lexical this. + if (subtreeFlags & TransformFlags.ContainsLexicalThis) { + transformFlags |= TransformFlags.ContainsCapturedLexicalThis; + } + + return updateTransformFlags(node, subtreeFlags, transformFlags, TransformFlags.ArrowFunctionExcludes); + } + + function computeVariableDeclaration(node: VariableDeclaration, subtreeFlags: TransformFlags) { + let transformFlags = TransformFlags.None; + + // A VariableDeclaration with a binding pattern is ES6 syntax. + if (isBindingPattern((node).name)) { + transformFlags = TransformFlags.AssertES6; + } + + return updateTransformFlags(node, subtreeFlags, transformFlags, TransformFlags.None); + } + + function computeVariableStatement(node: VariableStatement, subtreeFlags: TransformFlags) { + const modifiers = getModifierFlags(node); + // An ambient declaration is TypeScript syntax. + if (modifiers & ModifierFlags.Ambient) { + return updateTransformFlags(node, TransformFlags.None, TransformFlags.AssertTypeScript, TransformFlags.None); + } + + let transformFlags = TransformFlags.None; + + // If a VariableStatement is exported, then it is either ES6 or TypeScript syntax. + if (modifiers & ModifierFlags.Export) { + transformFlags = TransformFlags.AssertES6 | TransformFlags.AssertTypeScript; + } + + return updateTransformFlags(node, subtreeFlags, transformFlags, TransformFlags.None); + } + + function computeLabeledStatement(node: LabeledStatement, subtreeFlags: TransformFlags) { + let transformFlags = TransformFlags.None; + + // A labeled statement containing a block scoped binding *may* need to be transformed from ES6. + if (subtreeFlags & TransformFlags.ContainsBlockScopedBinding + && isIterationStatement(this, /*lookInLabeledStatements*/ true)) { + transformFlags = TransformFlags.AssertES6; + } + + return updateTransformFlags(node, subtreeFlags, transformFlags, TransformFlags.None); + } + + function updateTransformFlags(node: Node, subtreeFlags: TransformFlags, transformFlags: TransformFlags, excludeFlags: TransformFlags) { + node.transformFlags = transformFlags | subtreeFlags | TransformFlags.HasComputedFlags; + node.excludeTransformFlags = excludeFlags | TransformFlags.NodeExcludes; + return node.transformFlags & ~node.excludeTransformFlags; } } diff --git a/src/compiler/core.ts b/src/compiler/core.ts index c9a0da63cfe..e4bc42ee45e 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -1013,8 +1013,9 @@ namespace ts { this.pos = pos; this.end = end; this.flags = NodeFlags.None; - this.transformFlags = undefined; - this.excludeTransformFlags = undefined; + this.modifierFlagsCache = ModifierFlags.None; + this.transformFlags = TransformFlags.None; + this.excludeTransformFlags = TransformFlags.None; this.parent = undefined; this.original = undefined; } diff --git a/src/compiler/transformers/module/module.ts b/src/compiler/transformers/module/module.ts index bff1396b7a3..d6a89c4ed77 100644 --- a/src/compiler/transformers/module/module.ts +++ b/src/compiler/transformers/module/module.ts @@ -168,6 +168,26 @@ namespace ts { ]); } + function tryCreateExportEquals(emitAsReturn: boolean) { + if (exportEquals && resolver.isValueAliasDeclaration(exportEquals)) { + if (emitAsReturn) { + return createReturn(exportEquals.expression); + } + else { + return createStatement( + createAssignment( + createPropertyAccess( + createIdentifier("module"), + "exports" + ), + exportEquals.expression + ) + ); + } + } + return undefined; + } + /** * Visits a node at the top level of the source file. * @@ -209,186 +229,186 @@ namespace ts { * @param node The ImportDeclaration node. */ function visitImportDeclaration(node: ImportDeclaration): VisitResult { - if (contains(externalImports, node)) { - const statements: Statement[] = []; - const namespaceDeclaration = getNamespaceDeclarationNode(node); - if (moduleKind !== ModuleKind.AMD) { - if (!node.importClause) { - // import "mod"; - addNode(statements, - createStatement( - createRequireCall(node), - /*location*/ node + if (!contains(externalImports, node)) { + return undefined; + } + + const statements: Statement[] = []; + const namespaceDeclaration = getNamespaceDeclarationNode(node); + if (moduleKind !== ModuleKind.AMD) { + if (!node.importClause) { + // import "mod"; + addNode(statements, + createStatement( + createRequireCall(node), + /*location*/ node + ) + ); + } + else { + const variables: VariableDeclaration[] = []; + if (namespaceDeclaration && !isDefaultImport(node)) { + // import * as n from "mod"; + addNode(variables, + createVariableDeclaration( + getSynthesizedClone(namespaceDeclaration.name), + createRequireCall(node) ) ); } else { - const variables: VariableDeclaration[] = []; - if (namespaceDeclaration && !isDefaultImport(node)) { - // import * as n from "mod"; + // import d from "mod"; + // import { x, y } from "mod"; + // import d, { x, y } from "mod"; + // import d, * as n from "mod"; + addNode(variables, + createVariableDeclaration( + getGeneratedNameForNode(node), + createRequireCall(node) + ) + ); + + if (namespaceDeclaration && isDefaultImport(node)) { addNode(variables, createVariableDeclaration( getSynthesizedClone(namespaceDeclaration.name), - createRequireCall(node) + getGeneratedNameForNode(node) ) ); } - else { - // import d from "mod"; - // import { x, y } from "mod"; - // import d, { x, y } from "mod"; - // import d, * as n from "mod"; - addNode(variables, - createVariableDeclaration( - getGeneratedNameForNode(node), - createRequireCall(node) - ) - ); + } - if (namespaceDeclaration && isDefaultImport(node)) { - addNode(variables, - createVariableDeclaration( - getSynthesizedClone(namespaceDeclaration.name), - getGeneratedNameForNode(node) - ) - ); - } - } - - addNode(statements, - createVariableStatement( - /*modifiers*/ undefined, - createVariableDeclarationList(variables), + addNode(statements, + createVariableStatement( + /*modifiers*/ undefined, + createVariableDeclarationList(variables), + /*location*/ node + ) + ); + } + } + else if (namespaceDeclaration && isDefaultImport(node)) { + // import d, * as n from "mod"; + addNode(statements, + createVariableStatement( + /*modifiers*/ undefined, + createVariableDeclarationList([ + createVariableDeclaration( + getSynthesizedClone(namespaceDeclaration.name), + getGeneratedNameForNode(node), /*location*/ node ) - ); - } + ]) + ) + ); + } + + addExportImportAssignments(statements, node); + return statements; + } + + function visitImportEqualsDeclaration(node: ImportEqualsDeclaration): VisitResult { + if (!contains(externalImports, node)) { + return undefined; + } + + const statements: Statement[] = []; + if (moduleKind !== ModuleKind.AMD) { + if (hasModifier(node, ModifierFlags.Export)) { + addNode(statements, + createStatement( + createExportAssignment( + node.name, + createRequireCall(node) + ), + /*location*/ node + ) + ); } - else if (namespaceDeclaration && isDefaultImport(node)) { - // import d, * as n from "mod"; + else { addNode(statements, createVariableStatement( /*modifiers*/ undefined, createVariableDeclarationList([ createVariableDeclaration( - getSynthesizedClone(namespaceDeclaration.name), - getGeneratedNameForNode(node), + getSynthesizedClone(node.name), + createRequireCall(node), /*location*/ node ) ]) ) ); } - - addExportImportAssignments(statements, node); - return statements; } - - return undefined; - } - - function visitImportEqualsDeclaration(node: ImportEqualsDeclaration): VisitResult { - if (contains(externalImports, node)) { - const statements: Statement[] = []; - if (moduleKind !== ModuleKind.AMD) { - if (hasModifier(node, ModifierFlags.Export)) { - addNode(statements, - createStatement( - createExportAssignment( - node.name, - createRequireCall(node) - ), - /*location*/ node - ) - ); - } - else { - addNode(statements, - createVariableStatement( - /*modifiers*/ undefined, - createVariableDeclarationList([ - createVariableDeclaration( - getSynthesizedClone(node.name), - createRequireCall(node), - /*location*/ node - ) - ]) - ) - ); - } - } - else { - if (hasModifier(node, ModifierFlags.Export)) { - addNode(statements, - createStatement( - createExportAssignment(node.name, node.name), - /*location*/ node - ) - ); - } - } - - addExportImportAssignments(statements, node); - return statements; - } - - return undefined; - } - - function visitExportDeclaration(node: ExportDeclaration): VisitResult { - if (contains(externalImports, node)) { - const generatedName = getGeneratedNameForNode(node); - if (node.exportClause) { - const statements: Statement[] = []; - // export { x, y } from "mod"; - if (moduleKind !== ModuleKind.AMD) { - addNode(statements, - createVariableStatement( - /*modifiers*/ undefined, - createVariableDeclarationList([ - createVariableDeclaration( - generatedName, - createRequireCall(node), - /*location*/ node - ) - ]) - ) - ); - } - for (const specifier of node.exportClause.elements) { - if (resolver.isValueAliasDeclaration(specifier)) { - const exportedValue = createPropertyAccess( - generatedName, - specifier.propertyName || specifier.name - ); - addNode(statements, - createStatement( - createExportAssignment(specifier.name, exportedValue), - /*location*/ specifier - ) - ); - } - } - - return statements; - } - else { - // export * from "mod"; - return createStatement( - createCall( - createIdentifier("__export"), - [ - moduleKind !== ModuleKind.AMD - ? createRequireCall(node) - : generatedName - ] - ), - /*location*/ node + else { + if (hasModifier(node, ModifierFlags.Export)) { + addNode(statements, + createStatement( + createExportAssignment(node.name, node.name), + /*location*/ node + ) ); } } - return undefined; + addExportImportAssignments(statements, node); + return statements; + } + + function visitExportDeclaration(node: ExportDeclaration): VisitResult { + if (!contains(externalImports, node)) { + return undefined; + } + + const generatedName = getGeneratedNameForNode(node); + if (node.exportClause) { + const statements: Statement[] = []; + // export { x, y } from "mod"; + if (moduleKind !== ModuleKind.AMD) { + addNode(statements, + createVariableStatement( + /*modifiers*/ undefined, + createVariableDeclarationList([ + createVariableDeclaration( + generatedName, + createRequireCall(node), + /*location*/ node + ) + ]) + ) + ); + } + for (const specifier of node.exportClause.elements) { + if (resolver.isValueAliasDeclaration(specifier)) { + const exportedValue = createPropertyAccess( + generatedName, + specifier.propertyName || specifier.name + ); + addNode(statements, + createStatement( + createExportAssignment(specifier.name, exportedValue), + /*location*/ specifier + ) + ); + } + } + + return statements; + } + else { + // export * from "mod"; + return createStatement( + createCall( + createIdentifier("__export"), + [ + moduleKind !== ModuleKind.AMD + ? createRequireCall(node) + : generatedName + ] + ), + /*location*/ node + ); + } } function visitExportAssignment(node: ExportAssignment): VisitResult { @@ -605,26 +625,6 @@ namespace ts { return node; } - function tryCreateExportEquals(emitAsReturn: boolean) { - if (exportEquals && resolver.isValueAliasDeclaration(exportEquals)) { - if (emitAsReturn) { - return createReturn(exportEquals.expression); - } - else { - return createStatement( - createAssignment( - createPropertyAccess( - createIdentifier("module"), - "exports" - ), - exportEquals.expression - ) - ); - } - } - return undefined; - } - function getModuleMemberName(name: Identifier) { return createPropertyAccess( createIdentifier("exports"), @@ -669,9 +669,10 @@ namespace ts { function createRequireCall(importNode: ImportDeclaration | ImportEqualsDeclaration | ExportDeclaration) { const moduleName = getExternalModuleNameLiteral(importNode); + Debug.assert(isDefined(moduleName)); return createCall( createIdentifier("require"), - moduleName ? [moduleName] : [] + [moduleName] ); } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 0ca234f482b..38d1687acea 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -419,7 +419,9 @@ namespace ts { Abstract = 1 << 7, // Class/Method/ConstructSignature Async = 1 << 8, // Property/Method/Function Default = 1 << 9, // Function/Class (export default declaration) - Const = 1 << 11, // Variable declaration + Const = 1 << 11, // Variable declaration + + HasComputedFlags = 1 << 31, // Modifier flags have been computed AccessibilityModifier = Public | Private | Protected, NonPublicAccessibilityModifier = Private | Protected, @@ -450,10 +452,11 @@ namespace ts { export interface Node extends TextRange { kind: SyntaxKind; flags: NodeFlags; + /* @internal */ modifierFlagsCache?: ModifierFlags; /* @internal */ transformFlags?: TransformFlags; /* @internal */ excludeTransformFlags?: TransformFlags; decorators?: NodeArray; // Array of decorators (in document order) - modifiers?: NodeArray; // Array of modifiers + modifiers?: NodeArray; // Array of modifiers /* @internal */ id?: number; // Unique id (used to look up NodeLinks) parent?: Node; // Parent node (initialized by binding) /* @internal */ original?: Node; // The original node if this is an updated node. @@ -2761,6 +2764,8 @@ namespace ts { ContainsComputedPropertyName = 1 << 16, ContainsBlockScopedBinding = 1 << 17, + HasComputedFlags = 1 << 31, // Transform flags have been computed. + // Assertions // - Bitmasks that are used to assert facts about the syntax of a node and its subtree. AssertTypeScript = TypeScript | ContainsTypeScript, @@ -2771,7 +2776,7 @@ namespace ts { // Scope Exclusions // - Bitmasks that exclude flags from propagating out of a specific context // into the subtree flags of their container. - NodeExcludes = TypeScript | Jsx | ES7 | ES6 | DestructuringAssignment, + NodeExcludes = TypeScript | Jsx | ES7 | ES6 | DestructuringAssignment | HasComputedFlags, ArrowFunctionExcludes = ContainsDecorators | ContainsDefaultValueAssignments | ContainsLexicalThis | ContainsParameterPropertyAssignments | ContainsBlockScopedBinding, FunctionExcludes = ContainsDecorators | ContainsDefaultValueAssignments | ContainsCapturedLexicalThis | ContainsLexicalThis | ContainsParameterPropertyAssignments | ContainsBlockScopedBinding, ConstructorExcludes = ContainsDefaultValueAssignments | ContainsLexicalThis | ContainsCapturedLexicalThis | ContainsBlockScopedBinding, diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index ca8e4d5289e..a08e2211cdb 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -190,7 +190,7 @@ namespace ts { // However, this node will be 'missing' in the sense that no actual source-code/tokens are // contained within it. export function nodeIsMissing(node: Node) { - if (!node) { + if (node === undefined) { return true; } @@ -2561,19 +2561,26 @@ namespace ts { } export function hasModifier(node: Node, flags: ModifierFlags) { - return (getModifierFlags(node) & flags) != 0; + return (getModifierFlags(node) & flags) !== 0; } export function getModifierFlags(node: Node): ModifierFlags { + if (node.modifierFlagsCache & ModifierFlags.HasComputedFlags) { + return node.modifierFlagsCache & ~ModifierFlags.HasComputedFlags; + } + let flags = ModifierFlags.None; if (node.modifiers) { for (const modifier of node.modifiers) { flags |= modifierToFlag(modifier.kind); } } + if (node.flags & NodeFlags.NestedNamespace) { flags |= ModifierFlags.Export; } + + node.modifierFlagsCache = flags | ModifierFlags.HasComputedFlags; return flags; } diff --git a/src/compiler/visitor.ts b/src/compiler/visitor.ts index ee88d531577..051c020558a 100644 --- a/src/compiler/visitor.ts +++ b/src/compiler/visitor.ts @@ -882,15 +882,15 @@ namespace ts { */ function aggregateTransformFlagsForNode(node: Node): TransformFlags { if (node === undefined) { - return 0; + return TransformFlags.None; } - - if (node.transformFlags === undefined) { + else if (node.transformFlags & TransformFlags.HasComputedFlags) { + return node.transformFlags & ~node.excludeTransformFlags; + } + else { const subtreeFlags = aggregateTransformFlagsForSubtree(node); return computeTransformFlagsForNode(node, subtreeFlags); } - - return node.transformFlags & ~node.excludeTransformFlags; } /**