From 387b30c296e6ef5faff275f3afb7a60db16c15e2 Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Fri, 12 Feb 2016 14:50:22 -0800 Subject: [PATCH 1/4] Some cleanup and reorganization --- src/compiler/factory.ts | 84 ++++++- src/compiler/types.ts | 9 + src/compiler/utilities.ts | 510 ++++++++++++++++++++------------------ src/compiler/visitor.ts | 266 ++++++++++++-------- 4 files changed, 510 insertions(+), 359 deletions(-) diff --git a/src/compiler/factory.ts b/src/compiler/factory.ts index 99f99230645..c41182d4e2b 100644 --- a/src/compiler/factory.ts +++ b/src/compiler/factory.ts @@ -6,33 +6,91 @@ namespace ts { let NodeConstructor: new (kind: SyntaxKind, pos: number, end: number) => Node; let SourceFileConstructor: new (kind: SyntaxKind, pos: number, end: number) => Node; - function createNode(kind: SyntaxKind, location?: TextRange): Node { + function createNode(kind: SyntaxKind, location?: TextRange, flags?: NodeFlags): Node { const ConstructorForKind = kind === SyntaxKind.SourceFile ? (SourceFileConstructor || (SourceFileConstructor = objectAllocator.getSourceFileConstructor())) : (NodeConstructor || (NodeConstructor = objectAllocator.getNodeConstructor())); - return location + const node = location ? new ConstructorForKind(kind, location.pos, location.end) : new ConstructorForKind(kind, /*pos*/ -1, /*end*/ -1); + + if (flags) { + node.flags = flags; + } + + return node; } - export function createNodeArray(elements?: T[], pos?: number, end?: number): NodeArray { - const array = >(elements || []); - array.pos = pos; - array.end = end; + export function createNodeArray(elements?: T[], location?: TextRange): NodeArray { + if (elements !== undefined) { + if (isNodeArray(elements)) { + return elements; + } + } + else { + elements = []; + } + + const array = >elements; + if (location !== undefined) { + array.pos = location.pos; + array.end = location.end; + } + else { + array.pos = -1; + array.end = -1; + } + array.arrayKind = ArrayKind.NodeArray; return array; } - export function createModifiersArray(elements?: Modifier[], pos?: number, end?: number): ModifiersArray { - const array = (elements || []); - array.pos = pos; - array.end = end; + export function createModifiersArray(elements?: Modifier[], location?: TextRange): ModifiersArray { + let flags: NodeFlags; + if (elements !== undefined) { + if (isModifiersArray(elements)) { + return elements; + } + + flags = 0; + for (const modifier of elements) { + flags |= modifierToFlag(modifier.kind); + } + } + else { + elements = []; + flags = 0; + } + + const array = elements; + if (location !== undefined) { + array.pos = location.pos; + array.end = location.end; + } + else { + array.pos = -1; + array.end = -1; + } + array.arrayKind = ArrayKind.ModifiersArray; - array.flags = 0; + array.flags = flags; return array; } + export function setModifiers(node: T, modifiers: Modifier[]) { + if (modifiers !== undefined) { + const array = createModifiersArray(modifiers); + node.modifiers = array; + node.flags |= array.flags; + } + else { + node.modifiers = undefined; + } + + return node; + } + export function createSynthesizedNode(kind: SyntaxKind, startsOnNewLine?: boolean): Node { const node = createNode(kind, /*location*/ undefined); node.startsOnNewLine = startsOnNewLine; @@ -40,11 +98,11 @@ namespace ts { } export function createSynthesizedNodeArray(elements?: T[]): NodeArray { - return createNodeArray(elements, /*pos*/ -1, /*end*/ -1); + return createNodeArray(elements, /*location*/ undefined); } export function createSynthesizedModifiersArray(elements?: Modifier[]): ModifiersArray { - return createModifiersArray(elements, /*pos*/ -1, /*end*/ -1); + return createModifiersArray(elements, /*location*/ undefined); } /** diff --git a/src/compiler/types.ts b/src/compiler/types.ts index e123d08e81c..6e4f1b35d6c 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2734,6 +2734,15 @@ namespace ts { resolveModuleNames?(moduleNames: string[], containingFile: string): ResolvedModule[]; } + /** Additional context provided to `visitEachChild` */ + export interface LexicalEnvironment { + /** Starts a new lexical environment. */ + startLexicalEnvironment(): void; + + /** Ends a lexical environment, returning any declarations. */ + endLexicalEnvironment(): Statement[]; + } + export interface TextSpan { start: number; length: number; diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 9cc5ffcf30f..1cef5e8835a 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -1276,17 +1276,7 @@ namespace ts { return false; } - export function isLiteralKind(kind: SyntaxKind): boolean { - return SyntaxKind.FirstLiteralToken <= kind && kind <= SyntaxKind.LastLiteralToken; - } - export function isTextualLiteralKind(kind: SyntaxKind): boolean { - return kind === SyntaxKind.StringLiteral || kind === SyntaxKind.NoSubstitutionTemplateLiteral; - } - - export function isTemplateLiteralKind(kind: SyntaxKind): boolean { - return SyntaxKind.FirstTemplateToken <= kind && kind <= SyntaxKind.LastTemplateToken; - } export function isNodeDescendentOf(node: Node, ancestor: Node): boolean { while (node) { @@ -1555,8 +1545,17 @@ namespace ts { return node; } - export function nodeStartsNewLexicalEnvironment(n: Node): boolean { - return isFunctionLike(n) || n.kind === SyntaxKind.ModuleDeclaration || n.kind === SyntaxKind.SourceFile; + export function nodeStartsNewLexicalEnvironment(node: Node): boolean { + const kind = node.kind; + return kind === SyntaxKind.Constructor + || kind === SyntaxKind.FunctionExpression + || kind === SyntaxKind.FunctionDeclaration + || kind === SyntaxKind.ArrowFunction + || kind === SyntaxKind.MethodDeclaration + || kind === SyntaxKind.GetAccessor + || kind === SyntaxKind.SetAccessor + || kind === SyntaxKind.ModuleDeclaration + || kind === SyntaxKind.SourceFile; } /** @@ -2443,23 +2442,111 @@ namespace ts { // All node tests in the following list should *not* reference parent pointers so that // they may be used with transformations. - export function isPropertyAccessExpression(node: Node): node is PropertyAccessExpression { - return node.kind === SyntaxKind.PropertyAccessExpression; + // Node Arrays + + export function isNodeArray(array: T[]): array is NodeArray { + return (>array).arrayKind === ArrayKind.NodeArray; } - export function isElementAccessExpression(node: Node): node is ElementAccessExpression { - return node.kind === SyntaxKind.ElementAccessExpression; + export function isModifiersArray(array: Modifier[]): array is ModifiersArray { + return (array).arrayKind === ArrayKind.ModifiersArray; } - function isBindingPatternKind(kind: SyntaxKind) { - return kind === SyntaxKind.ArrayBindingPattern - || kind === SyntaxKind.ObjectBindingPattern; + // Literals + + export function isLiteralKind(kind: SyntaxKind): boolean { + return SyntaxKind.FirstLiteralToken <= kind && kind <= SyntaxKind.LastLiteralToken; } - export function isBindingPattern(node: Node): node is BindingPattern { - return node && isBindingPatternKind(node.kind); + export function isTextualLiteralKind(kind: SyntaxKind): boolean { + return kind === SyntaxKind.StringLiteral || kind === SyntaxKind.NoSubstitutionTemplateLiteral; } + export function isLiteralExpression(node: Node): node is LiteralExpression { + return isLiteralKind(node.kind); + } + + // Pseudo-literals + + export function isTemplateLiteralKind(kind: SyntaxKind): boolean { + return SyntaxKind.FirstTemplateToken <= kind && kind <= SyntaxKind.LastTemplateToken; + } + + function isTemplateLiteralFragmentKind(kind: SyntaxKind) { + return kind === SyntaxKind.TemplateHead + || kind === SyntaxKind.TemplateMiddle + || kind === SyntaxKind.TemplateTail; + } + + export function isTemplateLiteralFragment(node: Node): node is TemplateLiteralFragment { + return isTemplateLiteralFragmentKind(node.kind); + } + + // Identifiers + + export function isIdentifier(node: Node): node is Identifier { + return node.kind === SyntaxKind.Identifier; + } + + // Keywords + + export function isModifier(node: Node): node is Modifier { + return isModifierKind(node.kind); + } + + // Names + + export function isQualifiedName(node: Node): node is QualifiedName { + return node.kind === SyntaxKind.QualifiedName; + } + + export function isComputedPropertyName(node: Node): node is ComputedPropertyName { + return node.kind === SyntaxKind.ComputedPropertyName; + } + + export function isEntityName(node: Node): node is EntityName { + const kind = node.kind; + return kind === SyntaxKind.QualifiedName + || kind === SyntaxKind.Identifier; + } + + export function isPropertyName(node: Node): node is PropertyName { + const kind = node.kind; + return kind === SyntaxKind.Identifier + || kind === SyntaxKind.StringLiteral + || kind === SyntaxKind.NumericLiteral + || kind === SyntaxKind.ComputedPropertyName; + } + + export function isModuleName(node: Node): node is ModuleName { + const kind = node.kind; + return kind === SyntaxKind.Identifier + || kind === SyntaxKind.StringLiteral; + } + + export function isBindingName(node: Node): node is BindingName { + const kind = node.kind; + return kind === SyntaxKind.Identifier + || kind === SyntaxKind.ObjectBindingPattern + || kind === SyntaxKind.ArrayBindingPattern; + } + + // Signature elements + + export function isTypeParameter(node: Node): node is TypeParameterDeclaration { + return node.kind === SyntaxKind.TypeParameter; + } + + export function isParameter(node: Node): node is ParameterDeclaration { + return node.kind === SyntaxKind.Parameter; + } + + export function isDecorator(node: Node): node is Decorator { + return node.kind === SyntaxKind.Decorator; + } + + // Type members + export function isClassElement(node: Node): node is ClassElement { const kind = node.kind; return kind === SyntaxKind.Constructor @@ -2471,37 +2558,76 @@ namespace ts { || kind === SyntaxKind.IndexSignature; } - export function isQualifiedName(node: Node): node is QualifiedName { - return node.kind === SyntaxKind.QualifiedName; + export function isObjectLiteralElement(node: Node): node is ObjectLiteralElement { + const kind = node.kind; + return kind === SyntaxKind.PropertyAssignment + || kind === SyntaxKind.ShorthandPropertyAssignment + || kind === SyntaxKind.MethodDeclaration + || kind === SyntaxKind.GetAccessor + || kind === SyntaxKind.SetAccessor + || kind === SyntaxKind.MissingDeclaration; } - export function isLiteralExpression(node: Node): node is LiteralExpression { - return isLiteralKind(node.kind); + // Type + + function isTypeNodeKind(kind: SyntaxKind) { + return (kind >= SyntaxKind.FirstTypeNode && kind <= SyntaxKind.LastTypeNode) + || kind === SyntaxKind.AnyKeyword + || kind === SyntaxKind.NumberKeyword + || kind === SyntaxKind.BooleanKeyword + || kind === SyntaxKind.StringKeyword + || kind === SyntaxKind.SymbolKeyword + || kind === SyntaxKind.VoidKeyword + || kind === SyntaxKind.ExpressionWithTypeArguments; } - function isEntityNameKind(kind: SyntaxKind) { - return kind === SyntaxKind.QualifiedName - || kind === SyntaxKind.Identifier; + /** + * Node test that determines whether a node is a valid type node. + * This differs from the `isPartOfTypeNode` function which determines whether a node is *part* + * of a TypeNode. + */ + export function isTypeNode(node: Node): node is TypeNode { + return isTypeNodeKind(node.kind); } - export function isEntityName(node: Node): node is EntityName { - return isEntityNameKind(node.kind); + // Binding patterns + + export function isBindingPattern(node: Node): node is BindingPattern { + if (node) { + const kind = node.kind; + return kind === SyntaxKind.ArrayBindingPattern + || kind === SyntaxKind.ObjectBindingPattern; + } + + return false; } - export function isIdentifier(node: Node): node is Identifier { - return node.kind === SyntaxKind.Identifier; + export function isBindingElement(node: Node): node is BindingElement { + return node.kind === SyntaxKind.BindingElement; } - export function isComputedPropertyName(node: Node): node is ComputedPropertyName { - return node.kind === SyntaxKind.ComputedPropertyName; + // Expression + + export function isPropertyAccessExpression(node: Node): node is PropertyAccessExpression { + return node.kind === SyntaxKind.PropertyAccessExpression; + } + + export function isElementAccessExpression(node: Node): node is ElementAccessExpression { + return node.kind === SyntaxKind.ElementAccessExpression; } export function isBinaryExpression(node: Node): node is BinaryExpression { return node.kind === SyntaxKind.BinaryExpression; } - export function isShortHandPropertyAssignment(node: Node): node is ShorthandPropertyAssignment { - return node.kind === SyntaxKind.ShorthandPropertyAssignment; + export function isTemplate(node: Node): node is Template { + const kind = node.kind; + return kind === SyntaxKind.TemplateExpression + || kind === SyntaxKind.NoSubstitutionTemplateLiteral; + } + + export function isExpressionWithTypeArguments(node: Node): node is ExpressionWithTypeArguments { + return node.kind === SyntaxKind.ExpressionWithTypeArguments; } function isLeftHandSideExpressionKind(kind: SyntaxKind) { @@ -2528,7 +2654,7 @@ namespace ts { || kind === SyntaxKind.ThisKeyword || kind === SyntaxKind.TrueKeyword || kind === SyntaxKind.SuperKeyword; - } + } export function isLeftHandSideExpression(node: Node): node is LeftHandSideExpression { return isLeftHandSideExpressionKind(node.kind); @@ -2564,32 +2690,70 @@ namespace ts { return isExpressionKind(node.kind); } - export function isDecorator(node: Node): node is Decorator { - return node.kind === SyntaxKind.Decorator; + // Misc + + export function isTemplateSpan(node: Node): node is TemplateSpan { + return node.kind === SyntaxKind.TemplateSpan; } - export function isModifier(node: Node): node is Modifier { - return isModifierKind(node.kind); + // Element + + export function isBlock(node: Node): node is Block { + return node.kind === SyntaxKind.Block; } - function isTypeNodeKind(kind: SyntaxKind) { - return (kind >= SyntaxKind.FirstTypeNode && kind <= SyntaxKind.LastTypeNode) - || kind === SyntaxKind.AnyKeyword - || kind === SyntaxKind.NumberKeyword - || kind === SyntaxKind.BooleanKeyword - || kind === SyntaxKind.StringKeyword - || kind === SyntaxKind.SymbolKeyword - || kind === SyntaxKind.VoidKeyword - || kind === SyntaxKind.ExpressionWithTypeArguments; + export function isConciseBody(node: Node): node is ConciseBody { + return isBlock(node) + || isExpression(node); } - /** - * Node test that determines whether a node is a valid type node. - * This differs from the `isPartOfTypeNode` function which determines whether a node is *part* - * of a TypeNode. - */ - export function isTypeNode(node: Node): node is TypeNode { - return isTypeNodeKind(node.kind); + export function isFunctionBody(node: Node): node is FunctionBody { + return isBlock(node); + } + + export function isForInitializer(node: Node): node is ForInitializer { + return isVariableDeclarationList(node) + || isExpression(node); + } + + export function isVariableDeclaration(node: Node): node is VariableDeclaration { + return node.kind === SyntaxKind.VariableDeclaration; + } + + export function isVariableDeclarationList(node: Node): node is VariableDeclarationList { + return node.kind === SyntaxKind.VariableDeclarationList; + } + + export function isCaseBlock(node: Node): node is CaseBlock { + return node.kind === SyntaxKind.CaseBlock; + } + + export function isModuleBody(node: Node): node is ModuleBody { + const kind = node.kind; + return kind === SyntaxKind.ModuleBlock + || kind === SyntaxKind.ModuleDeclaration; + } + + export function isImportClause(node: Node): node is ImportClause { + return node.kind === SyntaxKind.ImportClause; + } + + export function isNamedImportBindings(node: Node): node is NamedImportBindings { + const kind = node.kind; + return kind === SyntaxKind.NamedImports + || kind === SyntaxKind.NamespaceImport; + } + + export function isImportSpecifier(node: Node): node is ImportSpecifier { + return node.kind === SyntaxKind.ImportSpecifier; + } + + export function isNamedExports(node: Node): node is NamedExports { + return node.kind === SyntaxKind.NamedExports; + } + + export function isExportSpecifier(node: Node): node is ExportSpecifier { + return node.kind === SyntaxKind.ExportSpecifier; } function isDeclarationKind(kind: SyntaxKind) { @@ -2623,10 +2787,6 @@ namespace ts { || kind === SyntaxKind.VariableDeclaration; } - export function isDeclaration(node: Node): node is Declaration { - return isDeclarationKind(node.kind); - } - function isDeclarationStatementKind(kind: SyntaxKind) { return kind === SyntaxKind.FunctionDeclaration || kind === SyntaxKind.MissingDeclaration @@ -2640,10 +2800,6 @@ namespace ts { || kind === SyntaxKind.ExportAssignment; } - export function isDeclarationStatement(node: Node): node is DeclarationStatement { - return isDeclarationStatementKind(node.kind); - } - function isStatementKindButNotDeclarationKind(kind: SyntaxKind) { return kind === SyntaxKind.BreakStatement || kind === SyntaxKind.ContinueStatement @@ -2665,6 +2821,14 @@ namespace ts { || kind === SyntaxKind.WithStatement; } + export function isDeclaration(node: Node): node is Declaration { + return isDeclarationKind(node.kind); + } + + export function isDeclarationStatement(node: Node): node is DeclarationStatement { + return isDeclarationStatementKind(node.kind); + } + /** * Determines whether the node is a statement that is not also a declaration */ @@ -2672,171 +2836,22 @@ namespace ts { return isStatementKindButNotDeclarationKind(node.kind); } - function isStatementKind(kind: SyntaxKind) { + export function isStatement(node: Node): node is Statement { + const kind = node.kind; return isStatementKindButNotDeclarationKind(kind) || isDeclarationStatementKind(kind); } - export function isStatement(node: Node): node is Statement { - return isStatementKind(node.kind); - } - - function isPropertyNameKind(kind: SyntaxKind) { - return kind === SyntaxKind.Identifier - || kind === SyntaxKind.StringLiteral - || kind === SyntaxKind.NumericLiteral - || kind === SyntaxKind.ComputedPropertyName; - } - - export function isPropertyName(node: Node): node is PropertyName { - return isPropertyNameKind(node.kind); - } - - function isConciseBodyKind(kind: SyntaxKind) { - return kind === SyntaxKind.Block - || isExpressionKind(kind); - } - - export function isConciseBody(node: Node): node is ConciseBody { - return isConciseBodyKind(node.kind); - } - - export function isTypeParameter(node: Node): node is TypeParameterDeclaration { - return node.kind === SyntaxKind.TypeParameter; - } - - export function isParameter(node: Node): node is ParameterDeclaration { - return node.kind === SyntaxKind.Parameter; - } - - export function isBindingElement(node: Node): node is BindingElement { - return node.kind === SyntaxKind.BindingElement; - } - - function isObjectLiteralElementKind(kind: SyntaxKind) { - return kind === SyntaxKind.PropertyAssignment - || kind === SyntaxKind.ShorthandPropertyAssignment - || kind === SyntaxKind.MethodDeclaration - || kind === SyntaxKind.GetAccessor - || kind === SyntaxKind.SetAccessor - || kind === SyntaxKind.MissingDeclaration; - } - - export function isObjectLiteralElement(node: Node): node is ObjectLiteralElement { - return isObjectLiteralElementKind(node.kind); - } - - function isTemplateKind(kind: SyntaxKind) { - return kind === SyntaxKind.TemplateExpression - || kind === SyntaxKind.NoSubstitutionTemplateLiteral; - } - - export function isTemplate(node: Node): node is Template { - return isTemplateKind(node.kind); - } - - function isTemplateLiteralFragmentKind(kind: SyntaxKind) { - return kind === SyntaxKind.TemplateHead - || kind === SyntaxKind.TemplateMiddle - || kind === SyntaxKind.TemplateTail; - } - - export function isTemplateLiteralFragment(node: Node): node is TemplateLiteralFragment { - return isTemplateLiteralFragmentKind(node.kind); - } - - export function isTemplateSpan(node: Node): node is TemplateSpan { - return node.kind === SyntaxKind.TemplateSpan; - } - - export function isHeritageClause(node: Node): node is HeritageClause { - return node.kind === SyntaxKind.HeritageClause; - } - - export function isVariableDeclarationList(node: Node): node is VariableDeclarationList { - return node.kind === SyntaxKind.VariableDeclarationList; - } - - function isForInitializerKind(kind: SyntaxKind) { - return kind === SyntaxKind.VariableDeclarationList - || isExpressionKind(kind); - } - - export function isForInitializer(node: Node): node is ForInitializer { - return isForInitializerKind(node.kind); - } - - export function isCaseBlock(node: Node): node is CaseBlock { - return node.kind === SyntaxKind.CaseBlock; - } - - export function isBlock(node: Node): node is Block { - return node.kind === SyntaxKind.Block; - } - - export function isCatchClause(node: Node): node is CatchClause { - return node.kind === SyntaxKind.CatchClause; - } - - export function isVariableDeclaration(node: Node): node is VariableDeclaration { - return node.kind === SyntaxKind.VariableDeclaration; - } - - export function isEnumMember(node: Node): node is EnumMember { - return node.kind === SyntaxKind.EnumMember; - } - - function isModuleNameKind(kind: SyntaxKind) { - return kind === SyntaxKind.Identifier - || kind === SyntaxKind.StringLiteral; - } - - export function isModuleName(node: Node): node is ModuleName { - return isModuleNameKind(node.kind); - } - - function isCaseOrDefaultClauseKind(kind: SyntaxKind) { - return kind === SyntaxKind.CaseClause - || kind === SyntaxKind.DefaultClause; - } - - export function isCaseOrDefaultClause(node: Node): node is CaseOrDefaultClause { - return isCaseOrDefaultClauseKind(node.kind); - } - - function isModuleReferenceKind(kind: SyntaxKind) { - return kind === SyntaxKind.ExternalModuleReference - || isEntityNameKind(kind); - } + // Module references export function isModuleReference(node: Node): node is ModuleReference { - return isModuleReferenceKind(node.kind); + const kind = node.kind; + return kind === SyntaxKind.ExternalModuleReference + || kind === SyntaxKind.QualifiedName + || kind === SyntaxKind.Identifier; } - export function isImportClause(node: Node): node is ImportClause { - return node.kind === SyntaxKind.ImportClause; - } - - function isNamedImportBindingsKind(kind: SyntaxKind) { - return kind === SyntaxKind.NamedImports - || kind === SyntaxKind.NamespaceImport; - } - - export function isNamedImportBindings(node: Node): node is NamedImportBindings { - return isNamedImportBindingsKind(node.kind); - } - - export function isImportSpecifier(node: Node): node is ImportSpecifier { - return node.kind === SyntaxKind.ImportSpecifier; - } - - export function isNamedExports(node: Node): node is NamedExports { - return node.kind === SyntaxKind.NamedExports; - } - - export function isExportSpecifier(node: Node): node is ExportSpecifier { - return node.kind === SyntaxKind.ExportSpecifier; - } + // JSX export function isJsxOpeningElement(node: Node): node is JsxOpeningElement { return node.kind === SyntaxKind.JsxOpeningElement; @@ -2846,55 +2861,56 @@ namespace ts { return node.kind === SyntaxKind.JsxClosingElement; } - function isJsxChildKind(kind: SyntaxKind) { + export function isJsxChild(node: Node): node is JsxChild { + const kind = node.kind; return kind === SyntaxKind.JsxElement || kind === SyntaxKind.JsxExpression || kind === SyntaxKind.JsxSelfClosingElement || kind === SyntaxKind.JsxText; } - export function isJsxChild(node: Node): node is JsxChild { - return isJsxChildKind(node.kind); - } - - function isJsxAttributeLikeKind(kind: SyntaxKind) { + export function isJsxAttributeLike(node: Node): node is JsxAttributeLike { + const kind = node.kind; return kind === SyntaxKind.JsxAttribute || kind === SyntaxKind.JsxSpreadAttribute; } - export function isJsxAttributeLike(node: Node): node is JsxAttributeLike { - return isJsxAttributeLikeKind(node.kind); + // Clauses + + export function isCaseOrDefaultClause(node: Node): node is CaseOrDefaultClause { + const kind = node.kind; + return kind === SyntaxKind.CaseClause + || kind === SyntaxKind.DefaultClause; } - export function isExpressionWithTypeArguments(node: Node): node is ExpressionWithTypeArguments { - return node.kind === SyntaxKind.ExpressionWithTypeArguments; + export function isHeritageClause(node: Node): node is HeritageClause { + return node.kind === SyntaxKind.HeritageClause; } - function isModuleBodyKind(kind: SyntaxKind) { - return kind === SyntaxKind.ModuleBlock - || kind === SyntaxKind.ModuleDeclaration; + export function isCatchClause(node: Node): node is CatchClause { + return node.kind === SyntaxKind.CatchClause; } - export function isModuleBody(node: Node): node is ModuleBody { - return isModuleBodyKind(node.kind); + + // Property assignments + + export function isShortHandPropertyAssignment(node: Node): node is ShorthandPropertyAssignment { + const kind = node.kind; + return kind === SyntaxKind.ShorthandPropertyAssignment; } - function isBindingNameKind(kind: SyntaxKind) { - return kind === SyntaxKind.Identifier - || isBindingPatternKind(kind); + // Enum + + export function isEnumMember(node: Node): node is EnumMember { + return node.kind === SyntaxKind.EnumMember; } - export function isBindingName(node: Node): node is BindingName { - return isBindingNameKind(node.kind); - } + + // Synthesized export function isNodeArrayNode(node: Node): node is NodeArrayNode { return node.kind === SyntaxKind.NodeArrayNode; } - - export function isModifiersArray(array: NodeArray): array is ModifiersArray { - return array.arrayKind === ArrayKind.ModifiersArray; - } } namespace ts { diff --git a/src/compiler/visitor.ts b/src/compiler/visitor.ts index 315f0777503..0c12eaa9be1 100644 --- a/src/compiler/visitor.ts +++ b/src/compiler/visitor.ts @@ -2,14 +2,7 @@ /* @internal */ namespace ts { - /** Additional context provided to `visitEachChild` */ - export interface LexicalEnvironment { - /** Starts a new lexical environment. */ - startLexicalEnvironment(): void; - - /** Ends a lexical environment, returning any declarations. */ - endLexicalEnvironment(): Statement[]; - } + export type OneOrMore = T | NodeArrayNode; /** * Describes an edge of a Node, used when traversing a syntax tree. @@ -478,10 +471,10 @@ namespace ts { * @param node The Node to visit. * @param visitor The callback used to visit the Node. * @param test A callback to execute to verify the Node is valid. - * @param lift A callback to execute to lift a NodeArrayNode into a valid Node. - * @param optional A value indicating whether the Node is optional. + * @param optional An optional value indicating whether the Node is itself optional. + * @param lift An optional callback to execute to lift a NodeArrayNode into a valid Node. */ - export function visitNode(node: T, visitor: (node: Node) => Node, test?: (node: Node) => boolean, lift?: (node: NodeArray) => T, optional?: boolean): T { + export function visitNode(node: T, visitor: (node: Node) => Node, test: (node: Node) => boolean, optional?: boolean, lift?: (node: NodeArray) => T): T { if (node === undefined) { return undefined; } @@ -507,51 +500,52 @@ namespace ts { * @param nodes The NodeArray to visit. * @param visitor The callback used to visit a Node. * @param test A node test to execute for each node. + * @param start An optional value indicating the starting offset at which to start visiting. + * @param count An optional value indicating the maximum number of nodes to visit. */ - export function visitNodes>(nodes: TArray, visitor: (node: Node) => Node, test?: (node: Node) => boolean): TArray { + export function visitNodes>(nodes: TArray, visitor: (node: Node) => Node, test: (node: Node) => boolean, start?: number, count?: number): TArray { if (nodes === undefined) { return undefined; } - let updated: NodeArray | ModifiersArray; - for (let i = 0, len = nodes.length; i < len; i++) { - const node = nodes[i]; - if (node === undefined) { - continue; - } + let updated: T[]; - const visited = visitor(node); + // Ensure start and count have valid values + const length = nodes.length; + if (start === undefined || start < 0) { + start = 0; + } + + if (count === undefined || count > length - start) { + count = length - start; + } + + // If we are not visiting all of the original nodes, we must always create a new array. + if (start > 0 || count < length) { + updated = []; + } + + // Visit each original node. + for (let i = 0; i < count; i++) { + const node = nodes[i + start]; + const visited = node && >visitor(node); if (updated !== undefined || visited === undefined || visited !== node) { if (updated === undefined) { - updated = isModifiersArray(nodes) - ? createModifiersArray(nodes.slice(0, i), nodes.pos, nodes.end) - : createNodeArray(nodes.slice(0, i), nodes.pos, nodes.end); + // Ensure we have a copy of `nodes`, up to the current index. + updated = nodes.slice(0, i); } - if (visited === undefined) { - continue; - } - - if (isNodeArrayNode(visited)) { - spreadNodeArrayNode(visited, updated, test); - } - else if (visited !== undefined) { - Debug.assert(test === undefined || test(visited), "Wrong node type after visit."); - updated.push(visited); - } + addNode(updated, visited, test); } } - if (updated && isModifiersArray(updated)) { - let flags: NodeFlags = 0; - for (const node of updated) { - flags |= modifierToFlag(node.kind); - } - - updated.flags = flags; + if (updated !== undefined) { + return (isModifiersArray(nodes) + ? createModifiersArray(updated, nodes) + : createNodeArray(updated, nodes)); } - return updated || nodes; + return nodes; } /** @@ -559,36 +553,40 @@ namespace ts { * * @param node The Node whose children will be visited. * @param visitor The callback used to visit each child. - * @param environment An optional lexical environment context for the visitor. + * @param context A lexical environment context for the visitor. */ - export function visitEachChild(node: T, visitor: (node: Node) => Node, environment?: LexicalEnvironment): T { + export function visitEachChild(node: T, visitor: (node: Node) => Node, context: LexicalEnvironment): T; + export function visitEachChild(node: T & Map, visitor: (node: Node) => Node, context: LexicalEnvironment): T { if (node === undefined) { return undefined; } - const isNewLexicalEnvironment = environment !== undefined && nodeStartsNewLexicalEnvironment(node); + let updated: T & Map; + + // If this node starts a new lexical environment, start a new lexical environment on the context. + const isNewLexicalEnvironment = nodeStartsNewLexicalEnvironment(node); if (isNewLexicalEnvironment) { - environment.startLexicalEnvironment(); + context.startLexicalEnvironment(); } - let modifiers: NodeFlags; - let updated: T & Map; const edgeTraversalPath = nodeEdgeTraversalMap[node.kind]; if (edgeTraversalPath) { for (const edge of edgeTraversalPath) { - const value = (>node)[edge.name]; + const value = >node[edge.name]; if (value !== undefined) { const visited = visitEdge(edge, value, visitor); if (updated !== undefined || visited !== value) { if (updated === undefined) { - updated = cloneNode(node, /*location*/ undefined, node.flags & ~NodeFlags.Modifier, /*parent*/ undefined, /*original*/ node); + updated = cloneNode(node, /*location*/ node, node.flags & ~NodeFlags.Modifier, /*parent*/ undefined, /*original*/ node); } - updated[edge.name] = visited; - } - - if (visited && isArray(visited) && isModifiersArray(visited)) { - modifiers = visited.flags; + if (visited && isArray(visited) && isModifiersArray(visited)) { + updated[edge.name] = visited; + updated.flags |= visited.flags; + } + else { + updated[edge.name] = visited; + } } } } @@ -597,14 +595,11 @@ namespace ts { if (updated === undefined) { updated = node; } - else if (modifiers) { - updated.flags |= modifiers; - } if (isNewLexicalEnvironment) { - const declarations = environment.endLexicalEnvironment(); + const declarations = context.endLexicalEnvironment(); if (declarations !== undefined && declarations.length > 0) { - return mergeLexicalEnvironment(updated, declarations, /*nodeIsMutable*/ updated !== node); + updated = mergeLexicalEnvironment(updated, declarations); } } @@ -620,104 +615,177 @@ namespace ts { */ function visitEdge(edge: NodeEdge, value: Node | NodeArray, visitor: (node: Node) => Node) { return isArray(value) - ? visitNodes(>value, visitor, edge.test) - : visitNode(value, visitor, edge.test, edge.lift, edge.optional); + ? visitNodes(>value, visitor, edge.test, /*start*/ undefined, /*count*/ undefined) + : visitNode(value, visitor, edge.test, edge.optional, edge.lift); } /** - * Spreads a NodeArrayNode into a NodeArray. + * Appends a node to an array. * - * @param source The source NodeArrayNode. - * @param dest The destination NodeArray. + * @param to The destination array. + * @param from The source Node or NodeArrayNode. * @param test The node test used to validate each node. */ - function spreadNodeArrayNode(source: NodeArrayNode, dest: NodeArray, test: (node: Node) => boolean) { - for (const element of source.nodes) { - if (element === undefined) { - continue; + export function addNode(to: T[], from: OneOrMore, test?: (node: Node) => boolean) { + if (to !== undefined && from !== undefined) { + if (isNodeArrayNode(from)) { + addNodes(to, from.nodes, test); } + else { + Debug.assert(test === undefined || test(from), "Wrong node type after visit."); + to.push(from); + } + } + } - Debug.assert(test === undefined || test(element), "Wrong node type after visit."); - dest.push(element); + /** + * Appends an array of nodes to an array. + * + * @param to The destination NodeArray. + * @param from The source array of Node or NodeArrayNode. + * @param test The node test used to validate each node. + */ + export function addNodes(to: T[], from: OneOrMore[], test?: (node: Node) => boolean) { + if (to !== undefined && from !== undefined) { + for (const node of from) { + addNode(to, node, test); + } } } /** * Merge generated declarations of a lexical environment. + * + * @param node The source node. + * @param declarations The generated lexical declarations. */ - function mergeLexicalEnvironment(node: Node, declarations: Statement[], nodeIsMutable: boolean) { - const mutableNode = nodeIsMutable ? node : cloneNode(node, /*location*/ node, node.flags, /*parent*/ undefined, /*original*/ node); + function mergeLexicalEnvironment(node: Node, declarations: Statement[]): Node { switch (node.kind) { case SyntaxKind.SourceFile: - mergeSourceFileLexicalEnvironment(mutableNode, declarations); - break; + return mergeSourceFileLexicalEnvironment(node, declarations); case SyntaxKind.ModuleDeclaration: - mergeModuleDeclarationLexicalEnvironment(mutableNode, declarations); - break; + return mergeModuleDeclarationLexicalEnvironment(node, declarations); case SyntaxKind.FunctionDeclaration: case SyntaxKind.FunctionExpression: - case SyntaxKind.ArrowFunction: case SyntaxKind.MethodDeclaration: case SyntaxKind.GetAccessor: case SyntaxKind.SetAccessor: case SyntaxKind.Constructor: - mergeFunctionLikeLexicalEnvironment(mutableNode, declarations); - break; - - case SyntaxKind.ModuleBlock: - case SyntaxKind.Block: - mergeBlockLexicalEnvironment(mutableNode, declarations); - break; + case SyntaxKind.ArrowFunction: + return mergeFunctionLikeLexicalEnvironment(node, declarations); } - return mutableNode; + Debug.fail("Node is not a valid lexical environment."); } /** * Merge generated declarations of a lexical environment into a SourceFile. + * + * @param node The SourceFile node. + * @param declarations The generated lexical declarations. */ - function mergeSourceFileLexicalEnvironment(node: SourceFile, declarations: Statement[]) { - node.statements = mergeStatements(node.statements, declarations); + export function mergeSourceFileLexicalEnvironment(node: SourceFile, declarations: Statement[]) { + if (declarations !== undefined && declarations.length) { + const mutableNode = cloneNode(node, /*location*/ node, node.flags, /*parent*/ undefined, /*original*/ node); + mutableNode.statements = mergeStatements(mutableNode.statements, declarations); + return mutableNode; + } + + return node; } /** * Merge generated declarations of a lexical environment into a ModuleDeclaration. + * + * @param node The ModuleDeclaration node. + * @param declarations The generated lexical declarations. */ - function mergeModuleDeclarationLexicalEnvironment(node: ModuleDeclaration, declarations: Statement[]) { + export function mergeModuleDeclarationLexicalEnvironment(node: ModuleDeclaration, declarations: Statement[]) { Debug.assert(node.body.kind === SyntaxKind.ModuleBlock); - node.body = mergeLexicalEnvironment(node.body, declarations, /*nodeIsMutable*/ false); + if (declarations !== undefined && declarations.length) { + const mutableNode = cloneNode(node, /*location*/ node, node.flags, /*parent*/ undefined, /*original*/ node); + mutableNode.body = mergeBlockLexicalEnvironment(node.body, declarations); + return mutableNode; + } + + return node; } /** * Merge generated declarations of a lexical environment into a FunctionLikeDeclaration. + * + * @param node The function-like node. + * @param declarations The generated lexical declarations. */ function mergeFunctionLikeLexicalEnvironment(node: FunctionLikeDeclaration, declarations: Statement[]) { Debug.assert(node.body !== undefined); - if (node.body.kind === SyntaxKind.Block) { - node.body = mergeLexicalEnvironment(node.body, declarations, /*nodeIsMutable*/ false); + if (declarations !== undefined && declarations.length) { + const mutableNode = cloneNode(node, /*location*/ node, node.flags, /*parent*/ undefined, /*original*/ node); + mutableNode.body = mergeConciseBodyLexicalEnvironment(mutableNode.body, declarations); + return mutableNode; } - else { - node.body = createBlock([ - createReturn(node.body), - ...declarations - ]); + + return node; + } + + /** + * Merges generated lexical declarations into the FunctionBody of a non-arrow function-like declaration. + * + * @param node The ConciseBody of an arrow function. + * @param declarations The lexical declarations to merge. + */ + export function mergeFunctionBodyLexicalEnvironment(body: FunctionBody, declarations: Statement[]) { + if (declarations !== undefined && declarations.length > 0) { + return mergeBlockLexicalEnvironment(body, declarations); } + + return body; + } + + /** + * Merges generated lexical declarations into the ConciseBody of an ArrowFunction. + * + * @param node The ConciseBody of an arrow function. + * @param declarations The lexical declarations to merge. + */ + export function mergeConciseBodyLexicalEnvironment(body: ConciseBody, declarations: Statement[]) { + if (declarations !== undefined && declarations.length > 0) { + if (isBlock(body)) { + return mergeBlockLexicalEnvironment(body, declarations); + } + else { + return createBlock([ + createReturn(body), + ...declarations + ]); + } + } + + return body; } /** * Merge generated declarations of a lexical environment into a FunctionBody or ModuleBlock. + * + * @param node The block into which to merge lexical declarations. + * @param declarations The lexical declarations to merge. */ - function mergeBlockLexicalEnvironment(node: FunctionBody | ModuleBlock, declarations: Statement[]) { - node.statements = mergeStatements(node.statements, declarations); + function mergeBlockLexicalEnvironment(node: T, declarations: Statement[]) { + const mutableNode = cloneNode(node, /*location*/ node, node.flags, /*parent*/ undefined, /*original*/ node); + mutableNode.statements = mergeStatements(node.statements, declarations); + return mutableNode; } /** * Merge generated declarations of a lexical environment into a NodeArray of Statement. + * + * @param statements The node array to concatentate with the supplied lexical declarations. + * @param declarations The lexical declarations to merge. */ function mergeStatements(statements: NodeArray, declarations: Statement[]) { - return createNodeArray(statements.concat(declarations), statements.pos, statements.end); + return createNodeArray(concatenate(statements, declarations), /*location*/ statements); } /** From 319ff614a0e824f8f2f4d4b67179883813c40d80 Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Fri, 12 Feb 2016 15:07:33 -0800 Subject: [PATCH 2/4] PR Feedback --- src/compiler/binder.ts | 33 ++++++++++++++++++--------------- src/compiler/utilities.ts | 4 ++++ 2 files changed, 22 insertions(+), 15 deletions(-) diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index d1101ad57e1..9c1e0cfe4b9 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -1962,16 +1962,20 @@ namespace ts { transformFlags = TransformFlags.AssertTypeScript; } } + break; case SyntaxKind.ExpressionStatement: - // if (node.flags & NodeFlags.Generated) { - // let expression = (node).expression; - // if (expression.kind === SyntaxKind.CallExpression - // && (expression).expression.kind === SyntaxKind.SuperKeyword) { - // transformFlags |= TransformFlags.AssertES6; - // } - // } + if (nodeIsSynthesized(node)) { + const expression = (node).expression; + if (nodeIsSynthesized(expression) + && isCallExpression(expression) + && expression.expression.kind === SyntaxKind.SuperKeyword) { + // A synthesized call to `super` should be transformed to a cleaner emit + // when transpiling to ES5/3. + transformFlags |= TransformFlags.AssertES6; + } + } break; @@ -2082,17 +2086,16 @@ namespace ts { case SyntaxKind.VariableDeclarationList: // If a VariableDeclarationList is `let` or `const`, then it is ES6 syntax. - if (node.flags & NodeFlags.Let - || node.flags & NodeFlags.Const) { + if (node.flags & NodeFlags.BlockScoped) { transformFlags |= TransformFlags.AssertES6; } break; case SyntaxKind.VariableStatement: - // If a VariableStatement is exported, then it is ES6 syntax. + // If a VariableStatement is exported, then it is either ES6 or TypeScript syntax. if (node.flags & NodeFlags.Export) { - transformFlags |= TransformFlags.AssertES6; + transformFlags |= TransformFlags.AssertES6 | TransformFlags.AssertTypeScript; } break; @@ -2114,13 +2117,13 @@ namespace ts { break; case SyntaxKind.HeritageClause: - // An `extends` HertiageClause is ES6 syntax. if ((node).token === SyntaxKind.ExtendsKeyword) { + // An `extends` HeritageClause is ES6 syntax. transformFlags |= TransformFlags.AssertES6; } - - // An `implements` HeritageClause is TypeScript syntax. - else if ((node).token === SyntaxKind.ImplementsKeyword) { + else { + // An `implements` HeritageClause is TypeScript syntax. + Debug.assert((node).token === SyntaxKind.ImplementsKeyword); transformFlags |= TransformFlags.AssertTypeScript; } diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 62f8359c828..15d2b4d671a 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -2640,6 +2640,10 @@ namespace ts { return node.kind === SyntaxKind.BinaryExpression; } + export function isCallExpression(node: Node): node is CallExpression { + return node.kind === SyntaxKind.CallExpression; + } + export function isTemplate(node: Node): node is Template { const kind = node.kind; return kind === SyntaxKind.TemplateExpression From f8ed021f1e97da3974aaeba27d89800e68f8708b Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Fri, 12 Feb 2016 17:46:38 -0800 Subject: [PATCH 3/4] PR feedback and cleanup --- src/compiler/core.ts | 13 ++- src/compiler/factory.ts | 187 +++++++++++++++++++++++++----------- src/compiler/transformer.ts | 15 ++- 3 files changed, 150 insertions(+), 65 deletions(-) diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 5f68777a63b..55903cb34de 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -169,8 +169,19 @@ namespace ts { export function concatenate(array1: T[], array2: T[]): T[] { if (!array2 || !array2.length) return array1; if (!array1 || !array1.length) return array2; + return [...array1, ...array2]; + } - return array1.concat(array2); + export function append(array: T[], value: T): T[] { + if (value === undefined) return array; + if (!array || !array.length) return [value]; + return [...array, value]; + } + + export function prepend(array: T[], value: T): T[] { + if (value === undefined) return array; + if (!array || !array.length) return [value]; + return [value, ...array]; } export function deduplicate(array: T[]): T[] { diff --git a/src/compiler/factory.ts b/src/compiler/factory.ts index 992c46fdcf1..8b81d7d0d2c 100644 --- a/src/compiler/factory.ts +++ b/src/compiler/factory.ts @@ -233,14 +233,14 @@ namespace ts { const node = createNode(SyntaxKind.PropertyAccessExpression); node.expression = parenthesizeForAccess(expression); node.dotToken = createSynthesizedNode(SyntaxKind.DotToken); - node.name = coerceIdentifier(name); + node.name = typeof name === "string" ? createIdentifier(name) : name; return node; } - export function createElementAccess(expression: Expression, index: string | number | Expression) { + export function createElementAccess(expression: Expression, index: number | Expression) { const node = createNode(SyntaxKind.ElementAccessExpression); node.expression = parenthesizeForAccess(expression); - node.argumentExpression = coerceExpression(index); + node.argumentExpression = typeof index === "number" ? createLiteral(index) : index; return node; } @@ -256,9 +256,9 @@ namespace ts { export function createBinary(left: Expression, operator: SyntaxKind, right: Expression, location?: TextRange) { const node = createNode(SyntaxKind.BinaryExpression, location); - node.left = parenthesizeForBinary(left, operator, BinaryOperand.Left); + node.left = parenthesizeBinaryOperand(operator, left, /*isLeftSideOfBinary*/ true); node.operatorToken = createSynthesizedNode(operator); - node.right = parenthesizeForBinary(right, operator, BinaryOperand.Right); + node.right = parenthesizeBinaryOperand(operator, right, /*isLeftSideOfBinary*/ false); return node; } @@ -282,7 +282,11 @@ namespace ts { } export function createArraySlice(array: Expression, start?: number | Expression) { - const argumentsList: Expression[] = start !== undefined ? [coerceExpression(start)] : []; + const argumentsList: Expression[] = []; + if (start !== undefined) { + argumentsList.push(typeof start === "number" ? createLiteral(start) : start); + } + return createCall(createPropertyAccess(array, "slice"), argumentsList); } @@ -296,94 +300,165 @@ namespace ts { return reduceLeft(expressions, createComma); } - function coerceIdentifier(value: string | Identifier) { - if (typeof value === "string") { - return createIdentifier(value); - } - else { - return value; - } - } - - function coerceExpression(value: string | number | boolean | Expression): Expression { - if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") { - return createLiteral(value); - } - else { - return value; - } - } - - const enum BinaryOperand { - Left, - Right - } - - function parenthesizeForBinary(operand: Expression, operator: SyntaxKind, side: BinaryOperand) { + /** + * Wraps the operand to a BinaryExpression in parentheses if they are needed to preserve the intended + * order of operations. + * + * @param binaryOperator The operator for the BinaryExpression. + * @param operand The operand for the BinaryExpression. + * @param isLeftSideOfBinary A value indicating whether the operand is the left side of the + * BinaryExpression. + */ + function parenthesizeBinaryOperand(binaryOperator: SyntaxKind, operand: Expression, isLeftSideOfBinary: boolean) { // When diagnosing whether the expression needs parentheses, the decision should be based // on the innermost expression in a chain of nested type assertions. - while (operand.kind === SyntaxKind.TypeAssertionExpression || operand.kind === SyntaxKind.AsExpression) { - operand = (operand).expression; - } + operand = skipAssertions(operand); // If the resulting expression is already parenthesized, we do not need to do any further processing. if (operand.kind === SyntaxKind.ParenthesizedExpression) { return operand; } - return needsParenthesesForBinary(operand, operator, side) + return binaryOperandNeedsParentheses(binaryOperator, operand, isLeftSideOfBinary) ? parenthesizeExpression(operand) : operand; } - function needsParenthesesForBinary(operand: Expression, operator: SyntaxKind, side: BinaryOperand) { + /** + * Determines whether the operand to a BinaryExpression needs to be parenthesized. + * + * @param binaryOperator The operator for the BinaryExpression. + * @param operand The operand for the BinaryExpression. + * @param isLeftSideOfBinary A value indicating whether the operand is the left side of the + * BinaryExpression. + */ + function binaryOperandNeedsParentheses(binaryOperator: SyntaxKind, operand: Expression, isLeftSideOfBinary: boolean) { + // If the operand has lower precedence, then it needs to be parenthesized to preserve the + // intent of the expression. For example, if the operand is `a + b` and the operator is + // `*`, then we need to parenthesize the operand to preserve the intended order of + // operations: `(a + b) * x`. + // + // If the operand has higher precedence, then it does not need to be parenthesized. For + // example, if the operand is `a * b` and the operator is `+`, then we do not need to + // parenthesize to preserve the intended order of operations: `a * b + x`. + // + // If the operand has the same precedence, then we need to check the associativity of + // the operator based on whether this is the left or right operand of the expression. + // + // For example, if `a / d` is on the right of operator `*`, we need to parenthesize + // to preserve the intended order of operations: `x * (a / d)` + // + // If `a ** d` is on the left of operator `**`, we need to parenthesize to preserve + // the intended order of operations: `(a ** b) ** c` + const binaryOperatorPrecedence = getOperatorPrecedence(SyntaxKind.BinaryExpression, binaryOperator); const operandPrecedence = getExpressionPrecedence(operand); - const operatorPrecedence = getOperatorPrecedence(SyntaxKind.BinaryExpression, operator); - switch (compareValues(operandPrecedence, operatorPrecedence)) { + switch (compareValues(operandPrecedence, binaryOperatorPrecedence)) { case Comparison.LessThan: return true; - case Comparison.EqualTo: - return isRightAssociativeOperandOnLeftHandSide(operand, side) - || isModuloOperandOnRightHandSide(operand, operator, side); + case Comparison.GreaterThan: return false; + + case Comparison.EqualTo: + if (isLeftSideOfBinary) { + // No need to parenthesize the left operand when the binary operator is + // left associative: + // (a*b)/x -> a*b/x + // (a**b)/x -> a**b/x + + // Parentheses are needed for the left operand when the binary operator is + // right associative: + // (a/b)**x -> (a/b)**x + // (a**b)**x -> (a**b)**x + const binaryOperatorAssociativity = getOperatorAssociativity(SyntaxKind.BinaryExpression, binaryOperator); + return binaryOperatorAssociativity === Associativity.Right; + } + else { + // No need to parenthesize the right operand when the binary operator and + // operand are the same and one of the following: + // x*(a*b) => x*a*b + // x|(a|b) => x|a|b + // x&(a&b) => x&a&b + // x^(a^b) => x^a^b + if (isBinaryExpression(operand) + && operand.operatorToken.kind === binaryOperator + && isMathAssociativeOperator(binaryOperator)) { + return false; + } + + // No need to parenthesize the right operand when the operand is right + // associative: + // x/(a**b) -> x/a**b + // x**(a**b) -> x**a**b + + // Parentheses are needed for the right operand when the operand is left + // associative: + // x/(a*b) -> x/(a*b) + // x**(a/b) -> x**(a/b) + const operandAssociativity = getExpressionAssociativity(operand); + return operandAssociativity === Associativity.Left; + } } } - function isRightAssociativeOperandOnLeftHandSide(operand: Expression, side: BinaryOperand) { - return side === BinaryOperand.Left - && getExpressionAssociativity(operand) === Associativity.Right; - } - - function isModuloOperandOnRightHandSide(operand: Expression, operator: SyntaxKind, side: BinaryOperand) { - return side === BinaryOperand.Right - && operator !== SyntaxKind.PercentToken - && operand.kind === SyntaxKind.BinaryExpression - && (operand).operatorToken.kind === SyntaxKind.PercentToken; + /** + * Determines whether a binary operator is mathematically associative. + * + * @param binaryOperator The binary operator. + */ + function isMathAssociativeOperator(binaryOperator: SyntaxKind) { + // The following operators are associative in JavaScript: + // (a*b)*c -> a*(b*c) -> a*b*c + // (a|b)|c -> a|(b|c) -> a|b|c + // (a&b)&c -> a&(b&c) -> a&b&c + // (a^b)^c -> a^(b^c) -> a^b^c + // + // While addition is associative in mathematics, JavaScript's `+` is not + // guaranteed to be associative as it is overloaded with string concatenation. + return binaryOperator === SyntaxKind.AsteriskToken + || binaryOperator === SyntaxKind.BarToken + || binaryOperator === SyntaxKind.AmpersandToken + || binaryOperator === SyntaxKind.CaretToken; } + /** + * Wraps an expression in parentheses if it is needed in order to use the expression for + * property or element access. + * + * @param expr The expression node. + */ function parenthesizeForAccess(expr: Expression): LeftHandSideExpression { // When diagnosing whether the expression needs parentheses, the decision should be based // on the innermost expression in a chain of nested type assertions. - while (expr.kind === SyntaxKind.TypeAssertionExpression || expr.kind === SyntaxKind.AsExpression) { - expr = (expr).expression; - } + expr = skipAssertions(expr); // isLeftHandSideExpression is almost the correct criterion for when it is not necessary // to parenthesize the expression before a dot. The known exceptions are: // // NewExpression: // new C.x -> not the same as (new C).x - // NumberLiteral + // NumericLiteral // 1.x -> not the same as (1).x // if (isLeftHandSideExpression(expr) && expr.kind !== SyntaxKind.NewExpression && expr.kind !== SyntaxKind.NumericLiteral) { - - return expr; + return expr; } return parenthesizeExpression(expr); } + + /** + * Skips past any TypeAssertionExpression or AsExpression nodes to their inner expression. + * + * @param node The expression node. + */ + function skipAssertions(node: Expression) { + while (node.kind === SyntaxKind.TypeAssertionExpression || node.kind === SyntaxKind.AsExpression) { + node = (node).expression; + } + + return node; + } } \ No newline at end of file diff --git a/src/compiler/transformer.ts b/src/compiler/transformer.ts index d6d3dedd361..f29113e3617 100644 --- a/src/compiler/transformer.ts +++ b/src/compiler/transformer.ts @@ -162,6 +162,8 @@ namespace ts { return generateNameForImportOrExportDeclaration(node); case SyntaxKind.FunctionDeclaration: case SyntaxKind.ClassDeclaration: + Debug.assert((node.flags & NodeFlags.Default) !== 0, "Can only generate a name for a default export."); + return generateNameForExportDefault(); case SyntaxKind.ExportAssignment: return generateNameForExportDefault(); case SyntaxKind.ClassExpression: @@ -227,7 +229,6 @@ namespace ts { lexicalEnvironmentVariableDeclarationsStack[lexicalEnvironmentStackOffset] = hoistedVariableDeclarations; lexicalEnvironmentFunctionDeclarationsStack[lexicalEnvironmentStackOffset] = hoistedFunctionDeclarations; lexicalEnvironmentStackOffset++; - hoistedVariableDeclarations = undefined; hoistedFunctionDeclarations = undefined; } @@ -239,15 +240,12 @@ namespace ts { function endLexicalEnvironment(): Statement[] { let statements: Statement[]; if (hoistedVariableDeclarations || hoistedFunctionDeclarations) { - statements = []; if (hoistedFunctionDeclarations) { - for (const declaration of hoistedFunctionDeclarations) { - statements.push(declaration); - } + statements = [...hoistedFunctionDeclarations]; } if (hoistedVariableDeclarations) { - statements.push( + statements = append(statements, createVariableStatement( createVariableDeclarationList(hoistedVariableDeclarations) ) @@ -325,8 +323,9 @@ namespace ts { * Makes an array from an ArrayLike. */ function arrayOf(arrayLike: ArrayLike) { - const array: T[] = []; - for (let i = 0; i < arrayLike.length; i++) { + const length = arrayLike.length; + const array: T[] = new Array(length); + for (let i = 0; i < length; i++) { array[i] = arrayLike[i]; } return array; From ab811f90d0bc36bcd0e513fc141dd82be893cbc8 Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Fri, 12 Feb 2016 18:03:33 -0800 Subject: [PATCH 4/4] Updates to TransformationContext and transformer.ts --- src/compiler/transformer.ts | 29 +++++++++++++++++- src/compiler/types.ts | 60 +++++++++++++++++++++++++++++++++---- 2 files changed, 83 insertions(+), 6 deletions(-) diff --git a/src/compiler/transformer.ts b/src/compiler/transformer.ts index f29113e3617..5be2c7a41b7 100644 --- a/src/compiler/transformer.ts +++ b/src/compiler/transformer.ts @@ -2,6 +2,11 @@ /* @internal */ namespace ts { + const enum SyntaxKindFeatureFlags { + ExpressionSubstitution = 1 << 0, + EmitNotifications = 1 << 1, + } + /** * Transforms an array of SourceFiles by passing them through each transformer. * @@ -16,6 +21,7 @@ namespace ts { const nodeEmitFlags: NodeEmitFlags[] = []; const lexicalEnvironmentVariableDeclarationsStack: VariableDeclaration[][] = []; const lexicalEnvironmentFunctionDeclarationsStack: FunctionDeclaration[][] = []; + const enabledSyntaxKindFeatures = new Array(SyntaxKind.Count); let lexicalEnvironmentStackOffset = 0; let hoistedVariableDeclarations: VariableDeclaration[]; let hoistedFunctionDeclarations: FunctionDeclaration[]; @@ -35,7 +41,11 @@ namespace ts { hoistVariableDeclaration, hoistFunctionDeclaration, startLexicalEnvironment, - endLexicalEnvironment + endLexicalEnvironment, + enableExpressionSubstitution, + isExpressionSubstitutionEnabled, + enableEmitNotification, + isEmitNotificationEnabled, }; // Chain together and initialize each transformer. @@ -60,6 +70,23 @@ namespace ts { return visited; } + function enableExpressionSubstitution(kind: SyntaxKind) { + enabledSyntaxKindFeatures[kind] |= SyntaxKindFeatureFlags.ExpressionSubstitution; + } + + function isExpressionSubstitutionEnabled(node: Node) { + return (enabledSyntaxKindFeatures[node.kind] & SyntaxKindFeatureFlags.ExpressionSubstitution) !== 0; + } + + function enableEmitNotification(kind: SyntaxKind) { + enabledSyntaxKindFeatures[kind] |= SyntaxKindFeatureFlags.EmitNotifications; + } + + function isEmitNotificationEnabled(node: Node) { + return (enabledSyntaxKindFeatures[node.kind] & SyntaxKindFeatureFlags.EmitNotifications) !== 0 + || (getNodeEmitFlags(node) & NodeEmitFlags.AdviseOnEmitNode) !== 0; + } + /** * Gets flags that control emit behavior of a node. */ diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 772c90fb08c..f9e48e8c8a6 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2790,11 +2790,15 @@ namespace ts { /* @internal */ export const enum NodeEmitFlags { - EmitHelpers = 1 << 0, // Any emit helpers should be written to this node. - EmitExportStar = 1 << 1, // The export * helper should be written to this node. - UMDDefine = 1 << 2, // This node should be replaced with the UMD define helper. - NoLexicalEnvironment = 1 << 3, // A new LexicalEnvironment should *not* be introduced when emitting this node. - SingleLine = 1 << 4, // The contents of this node should be emit on a single line. + EmitEmitHelpers = 1 << 0, // Any emit helpers should be written to this node. + EmitExportStar = 1 << 1, // The export * helper should be written to this node. + EmitSuperHelper = 1 << 2, // Emit the basic _super helper for async methods. + EmitAdvancedSuperHelper = 1 << 3, // Emit the advanced _super helper for async methods. + UMDDefine = 1 << 4, // This node should be replaced with the UMD define helper. + NoLexicalEnvironment = 1 << 5, // A new LexicalEnvironment should *not* be introduced when emitting this node. + SingleLine = 1 << 6, // The contents of this node should be emit on a single line. + MultiLine = 1 << 7, // The contents of this node should be emit on multiple lines. + AdviseOnEmitNode = 1 << 8, // The node printer should invoke the onBeforeEmitNode and onAfterEmitNode callbacks when printing this node. } /** Additional context provided to `visitEachChild` */ @@ -2818,8 +2822,54 @@ namespace ts { getGeneratedNameForNode(node: Node): Identifier; nodeHasGeneratedName(node: Node): boolean; makeUniqueName(baseName: string): Identifier; + + /** + * Hook used by transformers to substitute non-expression identifiers + * just before theyare emitted by the pretty printer. + */ identifierSubstitution?: (node: Identifier) => Identifier; + + /** + * Enables expression substitutions in the pretty printer for + * the provided SyntaxKind. + */ + enableExpressionSubstitution(kind: SyntaxKind): void; + + /** + * Determines whether expression substitutions are enabled for the + * provided node. + */ + isExpressionSubstitutionEnabled(node: Node): boolean; + + /** + * Hook used by transformers to substitute expressions just before they + * are emitted by the pretty printer. + */ expressionSubstitution?: (node: Expression) => Expression; + + /** + * Enables before/after emit notifications in the pretty printer for + * the provided SyntaxKind. + */ + enableEmitNotification(kind: SyntaxKind): void; + + /** + * Determines whether before/after emit notifications should be raised + * in the pretty printer when it emits a node. + */ + isEmitNotificationEnabled(node: Node): boolean; + + /** + * Hook used to notify transformers immediately before the pretty printer + * emits a node. + */ + onBeforeEmitNode?: (node: Node) => void; + + /** + * Hook used to notify transformers immediately after the pretty printer + * emits a node. + */ + onAfterEmitNode?: (node: Node) => void; } /* @internal */