diff --git a/Jakefile.js b/Jakefile.js index 7b85aacaa89..a63df27b824 100644 --- a/Jakefile.js +++ b/Jakefile.js @@ -40,6 +40,8 @@ var compilerSources = [ "utilities.ts", "binder.ts", "checker.ts", + "factory.ts", + "visitor.ts", "sourcemap.ts", "declarationEmitter.ts", "emitter.ts", @@ -60,6 +62,8 @@ var servicesSources = [ "utilities.ts", "binder.ts", "checker.ts", + "factory.ts", + "visitor.ts", "sourcemap.ts", "declarationEmitter.ts", "emitter.ts", diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 21536da36ff..48abb6ae3e3 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -91,6 +91,23 @@ namespace ts { return undefined; } + /** + * Iterates through `array` by index and performs the callback on each element of array until the callback + * returns a falsey value, then returns false. + * If no such value is found, the callback is applied to each element of array and `true` is returned. + */ + export function trueForAll(array: T[], callback: (element: T, index: number) => boolean): boolean { + if (array) { + for (let i = 0, len = array.length; i < len; i++) { + if (!callback(array[i], i)) { + return false; + } + } + } + + return true; + } + export function contains(array: T[], value: T): boolean { if (array) { for (const v of array) { @@ -242,8 +259,14 @@ namespace ts { const count = array.length; if (count > 0) { let pos = 0; - let result = arguments.length <= 2 ? array[pos] : initial; - pos++; + let result: T | U; + if (arguments.length <= 2) { + result = array[pos]; + pos++; + } + else { + result = initial; + } while (pos < count) { result = f(result, array[pos]); pos++; @@ -260,8 +283,14 @@ namespace ts { if (array) { let pos = array.length - 1; if (pos >= 0) { - let result = arguments.length <= 2 ? array[pos] : initial; - pos--; + let result: T | U; + if (arguments.length <= 2) { + result = array[pos]; + pos--; + } + else { + result = initial; + } while (pos >= 0) { result = f(result, array[pos]); pos--; diff --git a/src/compiler/factory.ts b/src/compiler/factory.ts new file mode 100644 index 00000000000..06eb4b98b9c --- /dev/null +++ b/src/compiler/factory.ts @@ -0,0 +1,56 @@ +/// +/// +/* @internal */ +namespace ts { + export function createNodeArray(elements?: T[], location?: TextRange): NodeArray; + export function createNodeArray>(elements: TArray, location?: TextRange): TArray; + export function createNodeArray>(elements?: T[], location?: TextRange): TArray { + const array = (elements || []); + if (location) { + array.pos = location.pos; + array.end = location.end; + } + else if (array.pos === undefined || array.end === undefined) { + array.pos = -1; + array.end = -1; + } + + return array; + } + + export function createNodeArrayNode(elements?: (T | NodeArrayNode)[]): NodeArrayNode { + const array = >createNodeArray(elements); + array.kind = SyntaxKind.NodeArrayNode; + return array; + } + + export function createReturn(expression?: Expression): ReturnStatement { + const node = createSynthesizedNode(SyntaxKind.ReturnStatement); + node.expression = expression; + return node; + } + + export function createStatement(expression: Expression): ExpressionStatement { + const node = createSynthesizedNode(SyntaxKind.ExpressionStatement); + node.expression = expression; + return node; + } + + export function createVariableStatement(declarationList: VariableDeclarationList): VariableStatement { + const node = createSynthesizedNode(SyntaxKind.VariableStatement); + node.declarationList = declarationList; + return node; + } + + export function createVariableDeclarationList(declarations: VariableDeclaration[]): VariableDeclarationList { + const node = createSynthesizedNode(SyntaxKind.VariableDeclarationList); + node.declarations = createNodeArray(declarations); + return node; + } + + export function createBlock(statements: Statement[]): Block { + const block = createSynthesizedNode(SyntaxKind.Block); + block.statements = createNodeArray(statements); + return block; + } +} \ No newline at end of file diff --git a/src/compiler/tsconfig.json b/src/compiler/tsconfig.json index ca297c087cf..9d96dfbbb21 100644 --- a/src/compiler/tsconfig.json +++ b/src/compiler/tsconfig.json @@ -17,6 +17,8 @@ "utilities.ts", "binder.ts", "checker.ts", + "factory.ts", + "visitor.ts", "emitter.ts", "program.ts", "commandLineParser.ts", diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 5545fbd162e..d2d436f545f 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -341,6 +341,7 @@ namespace ts { // Synthesized list SyntaxList, + NodeArrayNode, // Enum value count Count, // Markers @@ -444,7 +445,8 @@ namespace ts { decorators?: NodeArray; // Array of decorators (in document order) modifiers?: ModifiersArray; // Array of modifiers /* @internal */ id?: number; // Unique id (used to look up NodeLinks) - parent?: Node; // Parent node (initialized by binding + parent?: Node; // Parent node (initialized by binding) + /* @internal */ original?: Node; // The original node if this is an updated node. /* @internal */ jsDocComment?: JSDocComment; // JSDoc for the node, if it has any. Only for .js files. /* @internal */ symbol?: Symbol; // Symbol declared by node (initialized by binding) /* @internal */ locals?: SymbolTable; // Locals associated with node (initialized by binding) @@ -456,6 +458,10 @@ namespace ts { hasTrailingComma?: boolean; } + // @kind(SyntaxKind.NodeArrayNode) + export interface NodeArrayNode extends Node, NodeArray> { + } + export interface ModifiersArray extends NodeArray { flags: number; } @@ -1287,13 +1293,15 @@ namespace ts { statements: NodeArray; } + export type ModuleReference = EntityName | ExternalModuleReference; + // @kind(SyntaxKind.ImportEqualsDeclaration) export interface ImportEqualsDeclaration extends DeclarationStatement { name: Identifier; // 'EntityName' for an internal module reference, 'ExternalModuleReference' for an external // module reference. - moduleReference: EntityName | ExternalModuleReference; + moduleReference: ModuleReference; } // @kind(SyntaxKind.ExternalModuleReference) diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 02b8385d0ff..9fe32c89a69 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -1194,10 +1194,10 @@ namespace ts { if (node.jsDocComment) { return node.jsDocComment; } - // Try to recognize this pattern when node is initializer of variable declaration and JSDoc comments are on containing variable statement. - // /** + // Try to recognize this pattern when node is initializer of variable declaration and JSDoc comments are on containing variable statement. + // /** // * @param {number} name - // * @returns {number} + // * @returns {number} // */ // var x = function(name) { return name.length; } if (checkParentVariableStatement) { @@ -1379,7 +1379,7 @@ namespace ts { } } - export function isClassElement(n: Node): boolean { + export function isClassElement(n: Node): n is ClassElement { switch (n.kind) { case SyntaxKind.Constructor: case SyntaxKind.PropertyDeclaration: @@ -1655,8 +1655,9 @@ namespace ts { * @param location An optional TextRange to use to supply the new position. * @param flags The NodeFlags to use for the cloned node. * @param parent The parent for the new node. + * @param original An optional pointer to the original node. */ - export function cloneNode(node: T, location?: TextRange, flags?: NodeFlags, parent?: Node): T { + export function cloneNode(node: T, location?: TextRange, flags?: NodeFlags, parent?: Node, original?: Node): T { // We don't use "clone" from core.ts here, as we need to preserve the prototype chain of // the original node. We also need to exclude specific properties and only include own- // properties (to skip members already defined on the shared prototype). @@ -1665,7 +1666,7 @@ namespace ts { : createSynthesizedNode(node.kind); for (const key in node) { - if (clone.hasOwnProperty(key) || !node.hasOwnProperty(key)) { + if (key === "parent" || key === "flags" || clone.hasOwnProperty(key) || !node.hasOwnProperty(key)) { continue; } @@ -1680,6 +1681,10 @@ namespace ts { clone.parent = parent; } + if (original !== undefined) { + clone.original = original; + } + return clone; } @@ -1703,8 +1708,15 @@ namespace ts { return node.kind === SyntaxKind.QualifiedName; } - export function nodeIsSynthesized(node: Node): boolean { - return node.pos === -1; + export function nodeIsSynthesized(node: TextRange): boolean { + return positionIsSynthesized(node.pos) + || positionIsSynthesized(node.end); + } + + export function positionIsSynthesized(pos: number): boolean { + // This is a fast way of testing the following conditions: + // pos === undefined || pos === null || isNaN(pos) || pos < 0; + return !(pos >= 0); } export function createSynthesizedNode(kind: SyntaxKind, startsOnNewLine?: boolean): Node { @@ -1720,6 +1732,19 @@ namespace ts { return array; } + export function getOriginalNode(node: Node): Node { + while (node.original !== undefined) { + node = node.original; + } + + return node; + } + + export function getOriginalNodeId(node: Node) { + node = getOriginalNode(node); + return node ? getNodeId(node) : 0; + } + export function createDiagnosticCollection(): DiagnosticCollection { let nonFileDiagnostics: Diagnostic[] = []; const fileDiagnostics: Map = {}; @@ -2361,9 +2386,13 @@ namespace ts { return 0; } - export function isLeftHandSideExpression(expr: Expression): boolean { - if (expr) { - switch (expr.kind) { + export function isLiteralExpression(node: Node): node is LiteralExpression { + return isLiteralKind(node.kind); + } + + export function isLeftHandSideExpression(node: Node): node is LeftHandSideExpression { + if (node) { + switch (node.kind) { case SyntaxKind.PropertyAccessExpression: case SyntaxKind.ElementAccessExpression: case SyntaxKind.NewExpression: @@ -2394,6 +2423,28 @@ namespace ts { return false; } + export function isEntityName(node: Node): node is EntityName { + const kind = node.kind; + return kind === SyntaxKind.QualifiedName + || kind === SyntaxKind.Identifier; + } + + export function isIdentifierNode(node: Node): node is Identifier { + return node.kind === SyntaxKind.Identifier; + } + + export function isComputedPropertyName(node: Node): node is ComputedPropertyName { + return node.kind === SyntaxKind.ComputedPropertyName; + } + + 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 isAssignmentOperator(token: SyntaxKind): boolean { return token >= SyntaxKind.FirstAssignment && token <= SyntaxKind.LastAssignment; } @@ -2404,6 +2455,245 @@ namespace ts { isClassLike(node.parent.parent); } + export function isExpressionNode(node: Node): node is Expression { + const kind = node.kind; + return isUnaryExpression(node) + || kind === SyntaxKind.ConditionalExpression + || kind === SyntaxKind.YieldExpression + || kind === SyntaxKind.ArrowFunction + || kind === SyntaxKind.BinaryExpression + || kind === SyntaxKind.SpreadElementExpression + || kind === SyntaxKind.AsExpression + || kind === SyntaxKind.OmittedExpression; + } + + export function isDecorator(node: Node): node is Decorator { + return node.kind === SyntaxKind.Decorator; + } + + export function isModifier(node: Node): node is Modifier { + return isModifierKind(node.kind); + } + + /** + * Node test that determines whether a node is a valid type node. + * This differs from the existing `isTypeNode` function which + * determines whether a node is *part* of a TypeNode. + */ + export function isTypeNodeNode(node: Node): node is TypeNode { + const kind = node.kind; + return kind === SyntaxKind.AnyKeyword + || kind === SyntaxKind.NumberKeyword + || kind === SyntaxKind.BooleanKeyword + || kind === SyntaxKind.StringKeyword + || kind === SyntaxKind.SymbolKeyword + || kind === SyntaxKind.VoidKeyword + || kind === SyntaxKind.FunctionType + || kind === SyntaxKind.ConstructorType + || kind === SyntaxKind.TypeReference + || kind === SyntaxKind.TypePredicate + || kind === SyntaxKind.TypeQuery + || kind === SyntaxKind.TypeLiteral + || kind === SyntaxKind.ArrayType + || kind === SyntaxKind.TupleType + || kind === SyntaxKind.UnionType + || kind === SyntaxKind.IntersectionType + || kind === SyntaxKind.ParenthesizedType + || kind === SyntaxKind.StringLiteralType + || kind === SyntaxKind.ExpressionWithTypeArguments + || kind === SyntaxKind.ThisType; + } + + export function isStatementNode(node: Node): node is Statement { + return isStatement(node) + || isDeclarationStatement(node); + } + + export function isDeclarationStatement(node: Node): node is DeclarationStatement { + const kind = node.kind; + return kind === SyntaxKind.FunctionDeclaration + || kind === SyntaxKind.MissingDeclaration + || kind === SyntaxKind.ClassDeclaration + || kind === SyntaxKind.InterfaceDeclaration + || kind === SyntaxKind.TypeAliasDeclaration + || kind === SyntaxKind.EnumDeclaration + || kind === SyntaxKind.ModuleDeclaration + || kind === SyntaxKind.ImportEqualsDeclaration + || kind === SyntaxKind.ExportDeclaration + || kind === SyntaxKind.ExportAssignment; + } + + 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 isConciseBody(node: Node): node is Expression | Block { + return isBlock(node) + || isExpressionNode(node); + } + + 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; + } + + 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 isTemplate(node: Node): node is LiteralExpression | TemplateExpression { + const kind = node.kind; + return kind === SyntaxKind.TemplateExpression + || kind === SyntaxKind.NoSubstitutionTemplateLiteral; + } + + export function isUnaryExpression(node: Node): node is UnaryExpression { + const kind = node.kind; + return isLeftHandSideExpression(node) + || kind === SyntaxKind.PrefixUnaryExpression + || kind === SyntaxKind.PostfixUnaryExpression + || kind === SyntaxKind.DeleteExpression + || kind === SyntaxKind.TypeOfExpression + || kind === SyntaxKind.VoidExpression + || kind === SyntaxKind.AwaitExpression + || kind === SyntaxKind.TypeAssertionExpression; + } + + export function isTemplateLiteralFragment(node: Node): node is TemplateLiteralFragment { + const kind = node.kind; + return kind === SyntaxKind.TemplateHead + || kind === SyntaxKind.TemplateMiddle + || kind === SyntaxKind.TemplateTail; + } + + 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; + } + + export function isExpressionOrVariableDeclarationList(node: Node): node is Expression | VariableDeclarationList { + return isVariableDeclarationList(node) + || isExpressionNode(node); + } + + 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; + } + + export function isModuleName(node: Node): node is Identifier | LiteralExpression { + return node.kind === SyntaxKind.Identifier + || node.kind === SyntaxKind.StringLiteral; + } + + export function isCaseOrDefaultClause(node: Node): node is CaseOrDefaultClause { + return node.kind === SyntaxKind.CaseClause + || node.kind === SyntaxKind.DefaultClause; + } + + export function isModuleReference(node: Node): node is EntityName | ExternalModuleReference { + return node.kind === SyntaxKind.ExternalModuleReference + || isEntityName(node); + } + + export function isImportClause(node: Node): node is ImportClause { + return node.kind === SyntaxKind.ImportClause; + } + + export function isNamedImportsOrNamespaceImport(node: Node): node is NamedImports | NamespaceImport { + 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; + } + + export function isJsxOpeningElement(node: Node): node is JsxOpeningElement { + return node.kind === SyntaxKind.JsxOpeningElement; + } + + export function isJsxClosingElement(node: Node): node is JsxClosingElement { + return node.kind === SyntaxKind.JsxClosingElement; + } + + 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 isJsxAttributeOrJsxSpreadAttribute(node: Node): node is JsxAttribute | JsxSpreadAttribute { + const kind = node.kind; + return kind === SyntaxKind.JsxAttribute + || kind === SyntaxKind.JsxSpreadAttribute; + } + + export function isExpressionWithTypeArguments(node: Node): node is ExpressionWithTypeArguments { + return node.kind === SyntaxKind.ExpressionWithTypeArguments; + } + + export function isModuleBody(node: Node): node is ModuleBody { + const kind = node.kind; + return kind === SyntaxKind.ModuleBlock + || kind === SyntaxKind.ModuleDeclaration; + } + + export function isBindingPatternOrIdentifier(node: Node): node is (BindingPattern | Identifier) { + return isBindingPattern(node) + || isIdentifierNode(node); + } + // Returns false if this heritage clause element's expression contains something unsupported // (i.e. not a name or dotted name). export function isSupportedExpressionWithTypeArguments(node: ExpressionWithTypeArguments): boolean { @@ -2438,6 +2728,14 @@ namespace ts { return false; } + export function isModifiersArray(nodes: NodeArray): nodes is ModifiersArray { + return !isNodeArrayNode(nodes) && typeof (nodes).flags === "number"; + } + + export function isNodeArrayNode(value: Node | NodeArray>): value is NodeArrayNode { + return (value).kind === SyntaxKind.NodeArrayNode; + } + export function getLocalSymbolForExportDefault(symbol: Symbol) { return symbol && symbol.valueDeclaration && (symbol.valueDeclaration.flags & NodeFlags.Default) ? symbol.valueDeclaration.localSymbol : undefined; } diff --git a/src/compiler/visitor.ts b/src/compiler/visitor.ts new file mode 100644 index 00000000000..15120148d1a --- /dev/null +++ b/src/compiler/visitor.ts @@ -0,0 +1,778 @@ +/// +/// +/* @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[]; + } + + /** + * Describes an edge of a Node, used when traversing a syntax tree. + */ + interface NodeEdge { + /** Indicates that the edge is a NodeArray. */ + array?: boolean; + + /** Indicates that the result is optional. */ + optional?: boolean; + + /** A callback used to test whether a node is valid. */ + test?: (node: Node) => node is Node; + + /** A callback used to lift a NodeArrayNode into a valid node. */ + lift?: (nodes: NodeArrayNode) => Node; + }; + + /** + * Describes the shape of a Node. + */ + type NodeTraversalPath = Map; + + /** + * This map contains information about the shape of each Node in "types.ts" pertaining to how + * each node should be traversed during a transformation. + * + * Each edge corresponds to a property in a Node subtype that should be traversed when visiting + * each child. The properties are assigned in the order in which traversal should occur. + * + * NOTE: This needs to be kept up to date with changes to nodes in "types.ts". Currently, this + * map is not comprehensive. Only node edges relevant to tree transformation are + * currently defined. We may extend this to be more comprehensive, and eventually + * supplant the existing `forEachChild` implementation if performance is not + * significantly impacted. + */ + const nodeEdgeTraversalMap: Map = { + [SyntaxKind.QualifiedName]: { + left: { test: isEntityName }, + right: { test: isIdentifierNode }, + }, + [SyntaxKind.ComputedPropertyName]: { + expression: { test: isExpressionNode }, + }, + [SyntaxKind.Parameter]: { + decorators: { test: isDecorator, array: true }, + modifiers: { test: isModifier, array: true }, + name: { test: isBindingPatternOrIdentifier }, + type: { test: isTypeNodeNode, optional: true }, + initializer: { test: isExpressionNode, optional: true }, + }, + [SyntaxKind.Decorator]: { + expression: { test: isLeftHandSideExpression }, + }, + [SyntaxKind.PropertyDeclaration]: { + decorators: { test: isDecorator, array: true }, + modifiers: { test: isModifier, array: true }, + name: { test: isPropertyName }, + type: { test: isTypeNodeNode, optional: true }, + initializer: { test: isExpressionNode, optional: true }, + }, + [SyntaxKind.MethodDeclaration]: { + decorators: { test: isDecorator, array: true }, + modifiers: { test: isModifier, array: true }, + name: { test: isPropertyName }, + typeParameters: { test: isTypeParameter, array: true }, + parameters: { test: isParameter, array: true }, + type: { test: isTypeNodeNode, optional: true }, + body: { test: isBlock, optional: true }, + }, + [SyntaxKind.Constructor]: { + decorators: { test: isDecorator, array: true }, + modifiers: { test: isModifier, array: true }, + typeParameters: { test: isTypeParameter, array: true }, + parameters: { test: isParameter, array: true }, + type: { test: isTypeNodeNode, optional: true }, + body: { test: isBlock, optional: true }, + }, + [SyntaxKind.GetAccessor]: { + decorators: { test: isDecorator, array: true }, + modifiers: { test: isModifier, array: true }, + name: { test: isPropertyName }, + typeParameters: { test: isTypeParameter, array: true }, + parameters: { test: isParameter, array: true }, + type: { test: isTypeNodeNode, optional: true }, + body: { test: isBlock, optional: true }, + }, + [SyntaxKind.SetAccessor]: { + decorators: { test: isDecorator, array: true }, + modifiers: { test: isModifier, array: true }, + name: { test: isPropertyName }, + typeParameters: { test: isTypeParameter, array: true }, + parameters: { test: isParameter, array: true }, + type: { test: isTypeNodeNode, optional: true }, + body: { test: isBlock, optional: true }, + }, + [SyntaxKind.ObjectBindingPattern]: { + elements: { test: isBindingElement, array: true }, + }, + [SyntaxKind.ArrayBindingPattern]: { + elements: { test: isBindingElement, array: true }, + }, + [SyntaxKind.BindingElement]: { + propertyName: { test: isPropertyName, optional: true }, + name: { test: isBindingPatternOrIdentifier }, + initializer: { test: isExpressionNode, optional: true }, + }, + [SyntaxKind.ArrayLiteralExpression]: { + elements: { test: isExpressionNode, array: true }, + }, + [SyntaxKind.ObjectLiteralExpression]: { + properties: { test: isObjectLiteralElement, array: true }, + }, + [SyntaxKind.PropertyAccessExpression]: { + expression: { test: isLeftHandSideExpression }, + name: { test: isIdentifierNode }, + }, + [SyntaxKind.ElementAccessExpression]: { + expression: { test: isLeftHandSideExpression }, + argumentExpression: { test: isExpressionNode }, + }, + [SyntaxKind.CallExpression]: { + expression: { test: isLeftHandSideExpression }, + typeArguments: { test: isTypeNodeNode, array: true }, + arguments: { test: isExpressionNode, array: true }, + }, + [SyntaxKind.NewExpression]: { + expression: { test: isLeftHandSideExpression }, + typeArguments: { test: isTypeNodeNode, array: true }, + arguments: { test: isExpressionNode, array: true }, + }, + [SyntaxKind.TaggedTemplateExpression]: { + tag: { test: isLeftHandSideExpression }, + template: { test: isTemplate }, + }, + [SyntaxKind.TypeAssertionExpression]: { + type: { test: isTypeNodeNode }, + expression: { test: isUnaryExpression }, + }, + [SyntaxKind.ParenthesizedExpression]: { + expression: { test: isExpressionNode }, + }, + [SyntaxKind.FunctionExpression]: { + decorators: { test: isDecorator, array: true }, + modifiers: { test: isModifier, array: true }, + name: { test: isIdentifierNode, optional: true }, + typeParameters: { test: isTypeParameter, array: true }, + parameters: { test: isParameter, array: true }, + type: { test: isTypeNodeNode, optional: true }, + body: { test: isBlock, optional: true }, + }, + [SyntaxKind.ArrowFunction]: { + decorators: { test: isDecorator, array: true }, + modifiers: { test: isModifier, array: true }, + typeParameters: { test: isTypeParameter, array: true }, + parameters: { test: isParameter, array: true }, + type: { test: isTypeNodeNode, optional: true }, + body: { test: isConciseBody, lift: liftToBlock }, + }, + [SyntaxKind.DeleteExpression]: { + expression: { test: isUnaryExpression }, + }, + [SyntaxKind.TypeOfExpression]: { + expression: { test: isUnaryExpression }, + }, + [SyntaxKind.VoidExpression]: { + expression: { test: isUnaryExpression }, + }, + [SyntaxKind.AwaitExpression]: { + expression: { test: isUnaryExpression }, + }, + [SyntaxKind.PrefixUnaryExpression]: { + operand: { test: isUnaryExpression }, + }, + [SyntaxKind.PostfixUnaryExpression]: { + operand: { test: isLeftHandSideExpression }, + }, + [SyntaxKind.BinaryExpression]: { + left: { test: isExpressionNode }, + right: { test: isExpressionNode }, + }, + [SyntaxKind.ConditionalExpression]: { + condition: { test: isExpressionNode }, + whenTrue: { test: isExpressionNode }, + whenFalse: { test: isExpressionNode }, + }, + [SyntaxKind.TemplateExpression]: { + head: { test: isTemplateLiteralFragment }, + templateSpans: { test: isTemplateSpan, array: true }, + }, + [SyntaxKind.YieldExpression]: { + expression: { test: isExpressionNode, optional: true }, + }, + [SyntaxKind.SpreadElementExpression]: { + expression: { test: isExpressionNode }, + }, + [SyntaxKind.ClassExpression]: { + decorators: { test: isDecorator, array: true }, + modifiers: { test: isModifier, array: true }, + name: { test: isIdentifierNode, optional: true }, + typeParameters: { test: isTypeParameter, array: true }, + heritageClauses: { test: isHeritageClause, array: true }, + members: { test: isClassElement, array: true }, + }, + [SyntaxKind.ExpressionWithTypeArguments]: { + expression: { test: isLeftHandSideExpression }, + typeArguments: { test: isTypeNodeNode, array: true }, + }, + [SyntaxKind.AsExpression]: { + expression: { test: isExpressionNode }, + type: { test: isTypeNodeNode }, + }, + [SyntaxKind.TemplateSpan]: { + expression: { test: isExpressionNode }, + literal: { test: isTemplateLiteralFragment }, + }, + [SyntaxKind.Block]: { + statements: { test: isStatementNode, array: true }, + }, + [SyntaxKind.VariableStatement]: { + decorators: { test: isDecorator, array: true }, + modifiers: { test: isModifier, array: true }, + declarationList: { test: isVariableDeclarationList }, + }, + [SyntaxKind.ExpressionStatement]: { + expression: { test: isExpressionNode }, + }, + [SyntaxKind.IfStatement]: { + expression: { test: isExpressionNode }, + thenStatement: { test: isStatementNode, lift: liftToBlock }, + elseStatement: { test: isStatementNode, lift: liftToBlock, optional: true}, + }, + [SyntaxKind.DoStatement]: { + statement: { test: isStatementNode, lift: liftToBlock }, + expression: { test: isExpressionNode }, + }, + [SyntaxKind.WhileStatement]: { + expression: { test: isExpressionNode }, + statement: { test: isStatementNode, lift: liftToBlock }, + }, + [SyntaxKind.ForStatement]: { + initializer: { test: isExpressionOrVariableDeclarationList, optional: true }, + condition: { test: isExpressionNode, optional: true }, + incrementor: { test: isExpressionNode, optional: true }, + statement: { test: isStatementNode, lift: liftToBlock }, + }, + [SyntaxKind.ForInStatement]: { + initializer: { test: isExpressionOrVariableDeclarationList }, + expression: { test: isExpressionNode }, + statement: { test: isStatementNode, lift: liftToBlock }, + }, + [SyntaxKind.ForOfStatement]: { + initializer: { test: isExpressionOrVariableDeclarationList }, + expression: { test: isExpressionNode }, + statement: { test: isStatementNode, lift: liftToBlock }, + }, + [SyntaxKind.ContinueStatement]: { + label: { test: isIdentifierNode, optional: true }, + }, + [SyntaxKind.BreakStatement]: { + label: { test: isIdentifierNode, optional: true }, + }, + [SyntaxKind.ReturnStatement]: { + expression: { test: isExpressionNode, optional: true }, + }, + [SyntaxKind.WithStatement]: { + expression: { test: isExpressionNode }, + statement: { test: isStatementNode, lift: liftToBlock }, + }, + [SyntaxKind.SwitchStatement]: { + expression: { test: isExpressionNode }, + caseBlock: { test: isCaseBlock }, + }, + [SyntaxKind.LabeledStatement]: { + label: { test: isIdentifierNode }, + statement: { test: isStatementNode, lift: liftToBlock }, + }, + [SyntaxKind.ThrowStatement]: { + expression: { test: isExpressionNode }, + }, + [SyntaxKind.TryStatement]: { + tryBlock: { test: isBlock }, + catchClause: { test: isCatchClause, optional: true }, + finallyBlock: { test: isBlock, optional: true }, + }, + [SyntaxKind.VariableDeclaration]: { + name: { test: isBindingPatternOrIdentifier }, + type: { test: isTypeNodeNode, optional: true }, + initializer: { test: isExpressionNode, optional: true }, + }, + [SyntaxKind.VariableDeclarationList]: { + declarations: { test: isVariableDeclaration, array: true }, + }, + [SyntaxKind.FunctionDeclaration]: { + decorators: { test: isDecorator, array: true }, + modifiers: { test: isModifier, array: true }, + name: { test: isIdentifierNode, optional: true }, + typeParameters: { test: isTypeParameter, array: true }, + parameters: { test: isParameter, array: true }, + type: { test: isTypeNodeNode, optional: true }, + body: { test: isBlock, optional: true }, + }, + [SyntaxKind.ClassDeclaration]: { + decorators: { test: isDecorator, array: true }, + modifiers: { test: isModifier, array: true }, + name: { test: isIdentifierNode, optional: true }, + typeParameters: { test: isTypeParameter, array: true }, + heritageClauses: { test: isHeritageClause, array: true }, + members: { test: isClassElement, array: true }, + }, + [SyntaxKind.EnumDeclaration]: { + decorators: { test: isDecorator, array: true }, + modifiers: { test: isModifier, array: true }, + name: { test: isIdentifierNode }, + members: { test: isEnumMember, array: true }, + }, + [SyntaxKind.ModuleDeclaration]: { + decorators: { test: isDecorator, array: true }, + modifiers: { test: isModifier, array: true }, + name: { test: isModuleName }, + body: { test: isModuleBody }, + }, + [SyntaxKind.ModuleBlock]: { + statements: { test: isStatementNode, array: true }, + }, + [SyntaxKind.CaseBlock]: { + clauses: { test: isCaseOrDefaultClause, array: true }, + }, + [SyntaxKind.ImportEqualsDeclaration]: { + decorators: { test: isDecorator, array: true }, + modifiers: { test: isModifier, array: true }, + name: { test: isIdentifierNode }, + moduleReference: { test: isModuleReference }, + }, + [SyntaxKind.ImportDeclaration]: { + decorators: { test: isDecorator, array: true }, + modifiers: { test: isModifier, array: true }, + importClause: { test: isImportClause, optional: true }, + moduleSpecifier: { test: isExpressionNode }, + }, + [SyntaxKind.ImportClause]: { + name: { test: isIdentifierNode, optional: true }, + namedBindings: { test: isNamedImportsOrNamespaceImport, optional: true }, + }, + [SyntaxKind.NamespaceImport]: { + name: { test: isIdentifierNode }, + }, + [SyntaxKind.NamedImports]: { + elements: { test: isImportSpecifier, array: true }, + }, + [SyntaxKind.ImportSpecifier]: { + propertyName: { test: isIdentifierNode, optional: true }, + name: { test: isIdentifierNode }, + }, + [SyntaxKind.ExportAssignment]: { + decorators: { test: isDecorator, array: true }, + modifiers: { test: isModifier, array: true }, + expression: { test: isExpressionNode }, + }, + [SyntaxKind.ExportDeclaration]: { + decorators: { test: isDecorator, array: true }, + modifiers: { test: isModifier, array: true }, + exportClause: { test: isNamedExports, optional: true }, + moduleSpecifier: { test: isExpressionNode, optional: true }, + }, + [SyntaxKind.NamedExports]: { + elements: { test: isExportSpecifier, array: true }, + }, + [SyntaxKind.ExportSpecifier]: { + propertyName: { test: isIdentifierNode, optional: true }, + name: { test: isIdentifierNode }, + }, + [SyntaxKind.ExternalModuleReference]: { + expression: { test: isExpressionNode, optional: true }, + }, + [SyntaxKind.JsxElement]: { + openingElement: { test: isJsxOpeningElement }, + children: { test: isJsxChild, array: true }, + closingElement: { test: isJsxClosingElement }, + }, + [SyntaxKind.JsxSelfClosingElement]: { + tagName: { test: isEntityName }, + attributes: { test: isJsxAttributeOrJsxSpreadAttribute, array: true }, + }, + [SyntaxKind.JsxOpeningElement]: { + tagName: { test: isEntityName }, + attributes: { test: isJsxAttributeOrJsxSpreadAttribute, array: true }, + }, + [SyntaxKind.JsxClosingElement]: { + tagName: { test: isEntityName }, + }, + [SyntaxKind.JsxAttribute]: { + name: { test: isIdentifierNode }, + initializer: { test: isExpressionNode, optional: true }, + }, + [SyntaxKind.JsxSpreadAttribute]: { + expression: { test: isExpressionNode }, + }, + [SyntaxKind.JsxExpression]: { + expression: { test: isExpressionNode, optional: true }, + }, + [SyntaxKind.CaseClause]: { + expression: { test: isExpressionNode }, + statements: { test: isStatementNode, array: true }, + }, + [SyntaxKind.DefaultClause]: { + statements: { test: isStatementNode, array: true }, + }, + [SyntaxKind.HeritageClause]: { + types: { test: isExpressionWithTypeArguments, array: true }, + }, + [SyntaxKind.CatchClause]: { + variableDeclaration: { test: isVariableDeclaration }, + block: { test: isBlock }, + }, + [SyntaxKind.PropertyAssignment]: { + name: { test: isPropertyName }, + initializer: { test: isExpressionNode }, + }, + [SyntaxKind.ShorthandPropertyAssignment]: { + name: { test: isIdentifierNode }, + objectAssignmentInitializer: { test: isExpressionNode, optional: true }, + }, + [SyntaxKind.EnumMember]: { + name: { test: isPropertyName }, + initializer: { test: isExpressionNode, optional: true }, + }, + [SyntaxKind.SourceFile]: { + statements: { test: isStatementNode, array: true }, + }, + }; + + /** + * Similar to `reduceLeft`, performs a reduction against each child of a node. + * NOTE: Unlike `forEachChild`, this does *not* visit every node. Only nodes added to the + * `nodeEdgeTraversalMap` above will be visited. + * + * @param node The node containing the children to reduce. + * @param f The callback function + * @param initial The initial value to supply to the reduction. + */ + export function reduceEachChild(node: Node, f: (memo: T, node: Node) => T, initial: T) { + if (node === undefined) { + return undefined; + } + + let result = initial; + const edgeTraversalPath = nodeEdgeTraversalMap[node.kind]; + if (edgeTraversalPath) { + for (const propertyName in edgeTraversalPath) { + const value = (>node)[propertyName]; + if (value !== undefined) { + const edge = edgeTraversalPath[propertyName]; + if (edge.array) { + result = reduceLeft(>value, f, result); + } + else { + result = f(result, value); + } + } + } + } + + return result; + } + + /** + * Visits a Node using the supplied visitor, possibly returning a new Node in its place. + * + * @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. + */ + export function visitNode(node: T, visitor: (node: Node) => Node, test?: (node: Node) => boolean, lift?: (node: NodeArrayNode) => T, optional?: boolean): T { + if (node === undefined) { + return undefined; + } + + const visited = visitor(node); + if (visited === node) { + return node; + } + + const lifted = liftNode(visited, lift); + if (lifted === undefined) { + Debug.assert(optional, "Node not optional."); + return undefined; + } + + if (test !== undefined) { + Debug.assert(test(visited), "Wrong node type after visit."); + } + + return visited; + } + + /** + * Visits a NodeArray using the supplied visitor, possibly returning a new NodeArray in its place. + * + * @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. + */ + export function visitNodes>(nodes: TArray, visitor: (node: Node) => Node, test?: (node: Node) => boolean): TArray { + if (nodes === undefined) { + return undefined; + } + + let updated: TArray; + for (let i = 0, len = nodes.length; i < len; i++) { + const node = nodes[i]; + if (node === undefined) { + continue; + } + + const visited = visitor(node); + if (updated !== undefined || visited === undefined || visited !== node) { + if (updated === undefined) { + updated = createNodeArray(nodes.slice(0, i), /*location*/ nodes); + } + + if (visited === undefined) { + continue; + } + + if (isNodeArrayNode(visited)) { + spreadNodeArrayNode(visited, updated, test); + } + else if (visited !== undefined) { + Debug.assert(test(visited), "Wrong node type after visit."); + updated.push(visited); + } + } + } + + if (updated && isModifiersArray(nodes)) { + let flags: NodeFlags = 0; + for (const node of updated) { + flags |= modifierToFlag(node.kind); + } + + (>updated).flags = flags; + } + + return updated || nodes; + } + + /** + * Visits each child of a Node using the supplied visitor, possibly returning a new Node of the same kind in its place. + * + * @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. + */ + export function visitEachChild(node: T, visitor: (node: Node) => Node, environment?: LexicalEnvironment): T { + if (node === undefined) { + return undefined; + } + + const isNewLexicalEnvironment = environment !== undefined && nodeStartsNewLexicalEnvironment(node); + if (isNewLexicalEnvironment) { + environment.startLexicalEnvironment(); + } + + let updated: T & Map; + const edgeTraversalPath = nodeEdgeTraversalMap[node.kind]; + if (edgeTraversalPath) { + for (const propertyName in edgeTraversalPath) { + const value = (>node)[propertyName]; + if (value !== undefined) { + const edge = edgeTraversalPath[propertyName]; + 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[propertyName] = visited; + } + + if (visited && edge.array && isModifiersArray(>visited)) { + updated.flags |= (visited).flags; + } + } + } + } + + if (updated === undefined) { + updated = node; + } + + if (isNewLexicalEnvironment) { + const declarations = environment.endLexicalEnvironment(); + if (declarations !== undefined && declarations.length > 0) { + return mergeLexicalEnvironment(updated, declarations, /*nodeIsMutable*/ updated !== node); + } + } + + return updated; + } + + /** + * Visits a node edge. + * + * @param edge The edge of the Node. + * @param value The Node or NodeArray value for the edge. + * @param visitor A callback used to visit the node. + */ + function visitEdge(edge: NodeEdge, value: Node | NodeArray, visitor: (node: Node) => Node) { + return edge.array + ? visitNodes(>value, visitor, edge.test) + : visitNode(value, visitor, edge.test, edge.lift, edge.optional); + } + + /** + * Spreads a NodeArrayNode into a NodeArray. + * + * @param source The source NodeArrayNode. + * @param dest The destination NodeArray. + * @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) { + if (element === undefined) { + continue; + } + + if (isNodeArrayNode(element)) { + spreadNodeArrayNode(element, dest, test); + } + else { + Debug.assert(test === undefined || test(element), "Wrong node type after visit."); + dest.push(element); + } + } + } + + /** + * Gets a mutable Node for updates, setting the `original` pointer on the Node. + */ + function updateNode(node: T, flags: NodeFlags) { + const updated = cloneNode(node, /*location*/ node, flags, /*parent*/ undefined, /*original*/ node); + updated.original = node; + return updated; + } + + /** + * Merge generated declarations of a lexical environment. + */ + function mergeLexicalEnvironment(node: Node, declarations: Statement[], nodeIsMutable: boolean) { + const mutableNode = nodeIsMutable ? node : cloneNode(node, /*location*/ node, node.flags, /*parent*/ undefined, /*original*/ node); + switch (node.kind) { + case SyntaxKind.SourceFile: + mergeSourceFileLexicalEnvironment(mutableNode, declarations); + break; + + case SyntaxKind.ModuleDeclaration: + mergeModuleDeclarationLexicalEnvironment(mutableNode, declarations); + break; + + 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; + } + + return mutableNode; + } + + /** + * Merge generated declarations of a lexical environment into a SourceFile. + */ + function mergeSourceFileLexicalEnvironment(node: SourceFile, declarations: Statement[]) { + node.statements = mergeStatements(node.statements, declarations); + } + + /** + * Merge generated declarations of a lexical environment into a ModuleDeclaration. + */ + function mergeModuleDeclarationLexicalEnvironment(node: ModuleDeclaration, declarations: Statement[]) { + Debug.assert(node.body.kind === SyntaxKind.ModuleBlock); + node.body = mergeLexicalEnvironment(node.body, declarations, /*nodeIsMutable*/ false); + } + + /** + * Merge generated declarations of a lexical environment into a FunctionLikeDeclaration. + */ + 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); + } + else { + node.body = createBlock([ + createReturn(node.body), + ...declarations + ]); + } + } + + /** + * Merge generated declarations of a lexical environment into a FunctionBody or ModuleBlock. + */ + function mergeBlockLexicalEnvironment(node: FunctionBody | ModuleBlock, declarations: Statement[]) { + node.statements = mergeStatements(node.statements, declarations); + } + + /** + * Merge generated declarations of a lexical environment into a NodeArray of Statement. + */ + function mergeStatements(statements: NodeArray, declarations: Statement[]) { + return createNodeArray(statements.concat(declarations), /*location*/ statements); + } + + /** + * Tries to lift a NodeArrayNode to a Node. This is primarily used to + * lift multiple statements into a single Block. + * + * @param node The visited Node. + * @param options Options used to control lift behavior. + */ + function liftNode(node: Node, lifter: (nodes: NodeArrayNode) => Node): Node { + if (node === undefined) { + return undefined; + } + else if (isNodeArrayNode(node)) { + const lift = lifter || extractSingleNode; + return lift(node); + } + else { + return node; + } + } + + /** + * Lifts a NodeArray containing only Statement nodes to a block. + * + * @param nodes The NodeArray. + */ + function liftToBlock(nodes: NodeArray) { + Debug.assert(trueForAll(nodes, isStatementNode), "Cannot lift nodes to a Block."); + return createBlock(>nodes); + } + + /** + * Extracts the single node from a NodeArray. + * + * @param nodes The NodeArray. + */ + function extractSingleNode(nodes: NodeArray) { + Debug.assert(nodes.length <= 1, "Too many nodes written to output."); + return nodes.length > 0 ? nodes[0] : undefined; + } +} \ No newline at end of file