Added visitor API

This commit is contained in:
Ron Buckton
2016-02-03 18:11:32 -08:00
parent 98b8af62e7
commit c3faf0f34e
7 changed files with 1192 additions and 17 deletions

View File

@@ -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",

View File

@@ -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<T>(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<T>(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(<U>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(<U>result, array[pos]);
pos--;

56
src/compiler/factory.ts Normal file
View File

@@ -0,0 +1,56 @@
/// <reference path="types.ts"/>
/// <reference path="utilities.ts"/>
/* @internal */
namespace ts {
export function createNodeArray<T extends Node>(elements?: T[], location?: TextRange): NodeArray<T>;
export function createNodeArray<T extends Node, TArray extends NodeArray<T>>(elements: TArray, location?: TextRange): TArray;
export function createNodeArray<T extends Node, TArray extends NodeArray<T>>(elements?: T[], location?: TextRange): TArray {
const array = <TArray>(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<T extends Node>(elements?: (T | NodeArrayNode<T>)[]): NodeArrayNode<T> {
const array = <NodeArrayNode<T>>createNodeArray(elements);
array.kind = SyntaxKind.NodeArrayNode;
return array;
}
export function createReturn(expression?: Expression): ReturnStatement {
const node = <ReturnStatement>createSynthesizedNode(SyntaxKind.ReturnStatement);
node.expression = expression;
return node;
}
export function createStatement(expression: Expression): ExpressionStatement {
const node = <ExpressionStatement>createSynthesizedNode(SyntaxKind.ExpressionStatement);
node.expression = expression;
return node;
}
export function createVariableStatement(declarationList: VariableDeclarationList): VariableStatement {
const node = <VariableStatement>createSynthesizedNode(SyntaxKind.VariableStatement);
node.declarationList = declarationList;
return node;
}
export function createVariableDeclarationList(declarations: VariableDeclaration[]): VariableDeclarationList {
const node = <VariableDeclarationList>createSynthesizedNode(SyntaxKind.VariableDeclarationList);
node.declarations = createNodeArray(declarations);
return node;
}
export function createBlock(statements: Statement[]): Block {
const block = <Block>createSynthesizedNode(SyntaxKind.Block);
block.statements = createNodeArray(statements);
return block;
}
}

View File

@@ -17,6 +17,8 @@
"utilities.ts",
"binder.ts",
"checker.ts",
"factory.ts",
"visitor.ts",
"emitter.ts",
"program.ts",
"commandLineParser.ts",

View File

@@ -341,6 +341,7 @@ namespace ts {
// Synthesized list
SyntaxList,
NodeArrayNode,
// Enum value count
Count,
// Markers
@@ -444,7 +445,8 @@ namespace ts {
decorators?: NodeArray<Decorator>; // 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<T> extends Node, NodeArray<T | NodeArrayNode<T>> {
}
export interface ModifiersArray extends NodeArray<Modifier> {
flags: number;
}
@@ -1287,13 +1293,15 @@ namespace ts {
statements: NodeArray<Statement>;
}
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)

View File

@@ -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<T extends Node>(node: T, location?: TextRange, flags?: NodeFlags, parent?: Node): T {
export function cloneNode<T extends Node>(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 {
: <T>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<Diagnostic[]> = {};
@@ -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<Node>): nodes is ModifiersArray {
return !isNodeArrayNode(nodes) && typeof (<ModifiersArray>nodes).flags === "number";
}
export function isNodeArrayNode<T extends Node>(value: Node | NodeArray<T | NodeArrayNode<T>>): value is NodeArrayNode<T> {
return (<Node>value).kind === SyntaxKind.NodeArrayNode;
}
export function getLocalSymbolForExportDefault(symbol: Symbol) {
return symbol && symbol.valueDeclaration && (symbol.valueDeclaration.flags & NodeFlags.Default) ? symbol.valueDeclaration.localSymbol : undefined;
}

778
src/compiler/visitor.ts Normal file
View File

@@ -0,0 +1,778 @@
/// <reference path="checker.ts" />
/// <reference path="factory.ts" />
/* @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>) => Node;
};
/**
* Describes the shape of a Node.
*/
type NodeTraversalPath = Map<NodeEdge>;
/**
* 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<NodeTraversalPath> = {
[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<T>(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 = (<Map<any>>node)[propertyName];
if (value !== undefined) {
const edge = edgeTraversalPath[propertyName];
if (edge.array) {
result = reduceLeft(<NodeArray<Node>>value, f, result);
}
else {
result = f(result, <Node>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<T extends Node>(node: T, visitor: (node: Node) => Node, test?: (node: Node) => boolean, lift?: (node: NodeArrayNode<T>) => 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 <T>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<T extends Node, TArray extends NodeArray<T>>(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 = <TArray>createNodeArray(nodes.slice(0, i), /*location*/ nodes);
}
if (visited === undefined) {
continue;
}
if (isNodeArrayNode<T>(visited)) {
spreadNodeArrayNode(visited, updated, test);
}
else if (visited !== undefined) {
Debug.assert(test(visited), "Wrong node type after visit.");
updated.push(<T>visited);
}
}
}
if (updated && isModifiersArray(nodes)) {
let flags: NodeFlags = 0;
for (const node of updated) {
flags |= modifierToFlag(node.kind);
}
(<ModifiersArray><NodeArray<Node>>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<T extends Node>(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<any>;
const edgeTraversalPath = nodeEdgeTraversalMap[node.kind];
if (edgeTraversalPath) {
for (const propertyName in edgeTraversalPath) {
const value = (<Map<any>>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(<NodeArray<Node>>visited)) {
updated.flags |= (<ModifiersArray>visited).flags;
}
}
}
}
if (updated === undefined) {
updated = node;
}
if (isNewLexicalEnvironment) {
const declarations = environment.endLexicalEnvironment();
if (declarations !== undefined && declarations.length > 0) {
return <T>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<Node>, visitor: (node: Node) => Node) {
return edge.array
? visitNodes(<NodeArray<Node>>value, visitor, edge.test)
: visitNode(<Node>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<T extends Node>(source: NodeArrayNode<T>, dest: NodeArray<T>, test: (node: Node) => boolean) {
for (const element of source) {
if (element === undefined) {
continue;
}
if (isNodeArrayNode<T>(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<T extends Node>(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(<SourceFile>mutableNode, declarations);
break;
case SyntaxKind.ModuleDeclaration:
mergeModuleDeclarationLexicalEnvironment(<ModuleDeclaration>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(<FunctionLikeDeclaration>mutableNode, declarations);
break;
case SyntaxKind.ModuleBlock:
case SyntaxKind.Block:
mergeBlockLexicalEnvironment(<Block>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 = <ModuleBlock>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 = <Block>mergeLexicalEnvironment(node.body, declarations, /*nodeIsMutable*/ false);
}
else {
node.body = createBlock([
createReturn(<Expression>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<Statement>, 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): 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<Node>) {
Debug.assert(trueForAll(nodes, isStatementNode), "Cannot lift nodes to a Block.");
return createBlock(<NodeArray<Statement>>nodes);
}
/**
* Extracts the single node from a NodeArray.
*
* @param nodes The NodeArray.
*/
function extractSingleNode(nodes: NodeArray<Node>) {
Debug.assert(nodes.length <= 1, "Too many nodes written to output.");
return nodes.length > 0 ? nodes[0] : undefined;
}
}