From 47cdfbe55c593193852b20b3d714ad08fedb41cc Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Thu, 10 Mar 2016 11:40:57 -0800 Subject: [PATCH] Add support for array return values from visitors --- src/compiler/transformers/destructuring.ts | 10 +-- src/compiler/transformers/es6.ts | 11 ++-- src/compiler/transformers/es7.ts | 4 +- src/compiler/transformers/jsx.ts | 4 +- src/compiler/transformers/module/module.ts | 2 +- src/compiler/transformers/module/system.ts | 4 +- src/compiler/transformers/ts.ts | 28 ++++---- src/compiler/visitor.ts | 77 ++++++++++++---------- 8 files changed, 74 insertions(+), 66 deletions(-) diff --git a/src/compiler/transformers/destructuring.ts b/src/compiler/transformers/destructuring.ts index 0e1cbfe1b3a..aae8fbd8ce0 100644 --- a/src/compiler/transformers/destructuring.ts +++ b/src/compiler/transformers/destructuring.ts @@ -16,7 +16,7 @@ namespace ts { node: BinaryExpression, needsValue: boolean, recordTempVariable: (node: Identifier) => void, - visitor?: (node: Node) => Node) { + visitor?: (node: Node) => OneOrMany) { if (isEmptyObjectLiteralOrArrayLiteral(node.left)) { return node.right; @@ -79,7 +79,7 @@ namespace ts { * @param value The rhs value for the binding pattern. * @param visitor An optional visitor to use to visit expressions. */ - export function flattenParameterDestructuring(node: ParameterDeclaration, value: Expression, visitor?: (node: Node) => Node) { + export function flattenParameterDestructuring(node: ParameterDeclaration, value: Expression, visitor?: (node: Node) => OneOrMany) { const declarations: VariableDeclaration[] = []; flattenDestructuring(node, value, node, emitAssignment, emitTempVariableAssignment, visitor); @@ -110,7 +110,7 @@ namespace ts { * @param value An optional rhs value for the binding pattern. * @param visitor An optional visitor to use to visit expressions. */ - export function flattenVariableDestructuring(node: VariableDeclaration, value?: Expression, visitor?: (node: Node) => Node) { + export function flattenVariableDestructuring(node: VariableDeclaration, value?: Expression, visitor?: (node: Node) => OneOrMany) { const declarations: VariableDeclaration[] = []; flattenDestructuring(node, value, node, emitAssignment, emitTempVariableAssignment, visitor); @@ -151,7 +151,7 @@ namespace ts { node: VariableDeclaration, recordTempVariable: (name: Identifier) => void, nameSubstitution?: (name: Identifier) => Expression, - visitor?: (node: Node) => Node) { + visitor?: (node: Node) => OneOrMany) { const pendingAssignments: Expression[] = []; @@ -191,7 +191,7 @@ namespace ts { location: TextRange, emitAssignment: (name: Identifier, value: Expression, location: TextRange, original: Node) => void, emitTempVariableAssignment: (value: Expression, location: TextRange) => Identifier, - visitor?: (node: Node) => Node) { + visitor?: (node: Node) => OneOrMany) { if (value && visitor) { value = visitNode(value, visitor, isExpression); } diff --git a/src/compiler/transformers/es6.ts b/src/compiler/transformers/es6.ts index 2568becac65..8fe144a6d62 100644 --- a/src/compiler/transformers/es6.ts +++ b/src/compiler/transformers/es6.ts @@ -55,7 +55,7 @@ namespace ts { return visitEachChild(node, visitor, context); } - function visitor(node: Node): Node { + function visitor(node: Node): OneOrMany { const savedContainingNonArrowFunction = containingNonArrowFunction; const savedCurrentParent = currentParent; const savedCurrentNode = currentNode; @@ -63,17 +63,18 @@ namespace ts { const savedEnclosingBlockScopeContainerParent = enclosingBlockScopeContainerParent; onBeforeVisitNode(node); - node = visitorWorker(node); + + const visited = visitorWorker(node); containingNonArrowFunction = savedContainingNonArrowFunction; currentParent = savedCurrentParent; currentNode = savedCurrentNode; enclosingBlockScopeContainer = savedEnclosingBlockScopeContainer; enclosingBlockScopeContainerParent = savedEnclosingBlockScopeContainerParent; - return node; + return visited; } - function visitorWorker(node: Node): Node { + function visitorWorker(node: Node): OneOrMany { if (node.transformFlags & TransformFlags.ES6) { return visitJavaScript(node); } @@ -85,7 +86,7 @@ namespace ts { } } - function visitJavaScript(node: Node): Node { + function visitJavaScript(node: Node): OneOrMany { switch (node.kind) { case SyntaxKind.ClassDeclaration: return visitClassDeclaration(node); diff --git a/src/compiler/transformers/es7.ts b/src/compiler/transformers/es7.ts index 7be97566c51..fc6e878799f 100644 --- a/src/compiler/transformers/es7.ts +++ b/src/compiler/transformers/es7.ts @@ -12,7 +12,7 @@ namespace ts { return visitEachChild(node, visitor, context); } - function visitor(node: Node): Node { + function visitor(node: Node): OneOrMany { if (node.transformFlags & TransformFlags.ES7) { return visitorWorker(node); } @@ -24,7 +24,7 @@ namespace ts { } } - function visitorWorker(node: Node) { + function visitorWorker(node: Node): OneOrMany { switch (node.kind) { case SyntaxKind.BinaryExpression: return visitBinaryExpression(node); diff --git a/src/compiler/transformers/jsx.ts b/src/compiler/transformers/jsx.ts index 4dbfabb2f72..3a68f1f2e28 100644 --- a/src/compiler/transformers/jsx.ts +++ b/src/compiler/transformers/jsx.ts @@ -18,7 +18,7 @@ namespace ts { return visitEachChild(node, visitor, context); } - function visitor(node: Node): Node { + function visitor(node: Node): OneOrMany { if (node.transformFlags & TransformFlags.Jsx) { return visitorWorker(node); } @@ -30,7 +30,7 @@ namespace ts { } } - function visitorWorker(node: Node): Node { + function visitorWorker(node: Node): OneOrMany { switch (node.kind) { case SyntaxKind.JsxElement: return visitJsxElement(node); diff --git a/src/compiler/transformers/module/module.ts b/src/compiler/transformers/module/module.ts index f910e543bb1..632514f2a27 100644 --- a/src/compiler/transformers/module/module.ts +++ b/src/compiler/transformers/module/module.ts @@ -173,7 +173,7 @@ namespace ts { * * @param node The node. */ - function visitor(node: Node) { + function visitor(node: Node): OneOrMany { switch (node.kind) { case SyntaxKind.ImportDeclaration: return visitImportDeclaration(node); diff --git a/src/compiler/transformers/module/system.ts b/src/compiler/transformers/module/system.ts index 2b51fb1119d..9920878c159 100644 --- a/src/compiler/transformers/module/system.ts +++ b/src/compiler/transformers/module/system.ts @@ -408,7 +408,7 @@ namespace ts { return createArrayLiteral(setters); } - function visitSourceElement(node: Node): Node { + function visitSourceElement(node: Node): OneOrMany { switch (node.kind) { case SyntaxKind.ImportDeclaration: return visitImportDeclaration(node); @@ -427,7 +427,7 @@ namespace ts { } } - function visitNestedNode(node: Node): Node { + function visitNestedNode(node: Node): OneOrMany { switch (node.kind) { case SyntaxKind.VariableStatement: return visitVariableStatement(node); diff --git a/src/compiler/transformers/ts.ts b/src/compiler/transformers/ts.ts index 951e800f90d..401ccb17873 100644 --- a/src/compiler/transformers/ts.ts +++ b/src/compiler/transformers/ts.ts @@ -92,7 +92,7 @@ namespace ts { * * @param node The node to visit. */ - function visitWithStack(node: Node, visitor: (node: Node) => Node): Node { + function visitWithStack(node: Node, visitor: (node: Node) => OneOrMany): OneOrMany { // Save state const savedCurrentNamespace = currentNamespace; const savedCurrentScope = currentScope; @@ -102,7 +102,7 @@ namespace ts { // Handle state changes before visiting a node. onBeforeVisitNode(node); - node = visitor(node); + const visited = visitor(node); // Restore state currentNamespace = savedCurrentNamespace; @@ -110,7 +110,7 @@ namespace ts { currentParent = savedCurrentParent; currentNode = savedCurrentNode; - return node; + return visited; } /** @@ -118,7 +118,7 @@ namespace ts { * * @param node The node to visit. */ - function visitor(node: Node): Node { + function visitor(node: Node): OneOrMany { return visitWithStack(node, visitorWorker); } @@ -127,14 +127,14 @@ namespace ts { * * @param node The node to visit. */ - function visitorWorker(node: Node): Node { + function visitorWorker(node: Node): OneOrMany { if (node.transformFlags & TransformFlags.TypeScript) { // This node is explicitly marked as TypeScript, so we should transform the node. - node = visitTypeScript(node); + return visitTypeScript(node); } else if (node.transformFlags & TransformFlags.ContainsTypeScript) { // This node contains TypeScript, so we should visit its children. - node = visitEachChild(node, visitor, context); + return visitEachChild(node, visitor, context); } return node; @@ -145,7 +145,7 @@ namespace ts { * * @param node The node to visit. */ - function namespaceElementVisitor(node: Node): Node { + function namespaceElementVisitor(node: Node): OneOrMany { return visitWithStack(node, namespaceElementVisitorWorker); } @@ -154,15 +154,15 @@ namespace ts { * * @param node The node to visit. */ - function namespaceElementVisitorWorker(node: Node): Node { + function namespaceElementVisitorWorker(node: Node): OneOrMany { if (node.transformFlags & TransformFlags.TypeScript || isExported(node)) { // This node is explicitly marked as TypeScript, or is exported at the namespace // level, so we should transform the node. - node = visitTypeScript(node); + return visitTypeScript(node); } else if (node.transformFlags & TransformFlags.ContainsTypeScript) { // This node contains TypeScript, so we should visit its children. - node = visitEachChild(node, visitor, context); + return visitEachChild(node, visitor, context); } return node; @@ -173,7 +173,7 @@ namespace ts { * * @param node The node to visit. */ - function classElementVisitor(node: Node) { + function classElementVisitor(node: Node): OneOrMany { return visitWithStack(node, classElementVisitorWorker); } @@ -182,7 +182,7 @@ namespace ts { * * @param node The node to visit. */ - function classElementVisitorWorker(node: Node) { + function classElementVisitorWorker(node: Node): OneOrMany { switch (node.kind) { case SyntaxKind.Constructor: // TypeScript constructors are transformed in `transformClassDeclaration`. @@ -212,7 +212,7 @@ namespace ts { * * @param node The node to visit. */ - function visitTypeScript(node: Node): Node { + function visitTypeScript(node: Node): OneOrMany { if (hasModifier(node, ModifierFlags.Ambient)) { // TypeScript ambient declarations are elided. return undefined; diff --git a/src/compiler/visitor.ts b/src/compiler/visitor.ts index 75377fc38cd..92307b707f8 100644 --- a/src/compiler/visitor.ts +++ b/src/compiler/visitor.ts @@ -3,7 +3,7 @@ /* @internal */ namespace ts { - export type OneOrMany = T | NodeArrayNode; + export type OneOrMany = T | NodeArrayNode | T[]; /** * Describes an edge of a Node, used when traversing a syntax tree. @@ -478,7 +478,7 @@ namespace ts { * @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, optional?: boolean, lift?: (node: NodeArray) => T): T { + export function visitNode(node: T, visitor: (node: Node) => OneOrMany, test: (node: Node) => boolean, optional?: boolean, lift?: (node: NodeArray) => T): T { return visitNodeWorker(node, visitor, test, optional, lift, /*parenthesize*/ undefined, /*parentNode*/ undefined); } @@ -493,7 +493,7 @@ namespace ts { * @param parenthesize A callback used to parenthesize the node if needed. * @param parentNode A parentNode for the node. */ - function visitNodeWorker(node: Node, visitor: (node: Node) => Node, test: (node: Node) => boolean, optional: boolean, lift: (node: NodeArray) => Node, parenthesize: (node: Node, parentNode: Node) => Node, parentNode: Node): Node { + function visitNodeWorker(node: Node, visitor: (node: Node) => OneOrMany, test: (node: Node) => boolean, optional: boolean, lift: (node: NodeArray) => Node, parenthesize: (node: Node, parentNode: Node) => Node, parentNode: Node): Node { if (node === undefined) { return undefined; } @@ -503,22 +503,29 @@ namespace ts { return node; } - if (visited !== undefined && isNodeArrayNode(visited)) { - visited = (lift || extractSingleNode)((>visited).nodes); - } - - if (parenthesize !== undefined && visited !== undefined) { - visited = parenthesize(visited, parentNode); - } - if (visited === undefined) { Debug.assert(optional, "Node not optional."); return undefined; } - Debug.assert(test === undefined || test(visited), "Wrong node type after visit.", () => `Node ${formatSyntaxKind(visited.kind)} did not pass test ${(test).name}.`); - aggregateTransformFlags(visited); - return visited; + let visitedNode: Node; + if (isArray(visited)) { + visitedNode = (lift || extractSingleNode)(>visited); + } + else if (isNodeArrayNode(visited)) { + visitedNode = (lift || extractSingleNode)((>visited).nodes); + } + else { + visitedNode = visited; + } + + if (parenthesize !== undefined) { + visitedNode = parenthesize(visitedNode, parentNode); + } + + Debug.assert(test === undefined || test(visitedNode), "Wrong node type after visit.", () => `Node ${formatSyntaxKind(visitedNode.kind)} did not pass test ${(test).name}.`); + aggregateTransformFlags(visitedNode); + return visitedNode; } /** @@ -530,7 +537,7 @@ namespace ts { * @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, start?: number, count?: number): TArray { + export function visitNodes>(nodes: TArray, visitor: (node: Node) => OneOrMany, test: (node: Node) => boolean, start?: number, count?: number): TArray { return visitNodesWorker(nodes, visitor, test, /*parenthesize*/ undefined, /*parentNode*/ undefined, start, count); } @@ -543,7 +550,7 @@ namespace ts { * @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. */ - function visitNodesWorker(nodes: NodeArray, visitor: (node: Node) => Node, test: (node: Node) => boolean, parenthesize: (node: Node, parentNode: Node) => Node, parentNode: Node, start: number, count: number): NodeArray { + function visitNodesWorker(nodes: NodeArray, visitor: (node: Node) => OneOrMany, test: (node: Node) => boolean, parenthesize: (node: Node, parentNode: Node) => Node, parentNode: Node, start: number, count: number): NodeArray { if (nodes === undefined) { return undefined; } @@ -593,8 +600,8 @@ namespace ts { * @param visitor The callback used to visit each child. * @param context A lexical environment context for the visitor. */ - 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 { + export function visitEachChild(node: T, visitor: (node: Node) => OneOrMany, context: LexicalEnvironment): T; + export function visitEachChild(node: T & Map, visitor: (node: Node) => OneOrMany, context: LexicalEnvironment): T { if (node === undefined) { return undefined; } @@ -661,7 +668,7 @@ namespace ts { if (nodes) { for (let i = 0; i < nodes.length; i++) { const node = nodes[i]; - if (result || node === undefined || isNodeArrayNode(node)) { + if (result || node === undefined || isArray(node) || isNodeArrayNode(node)) { if (!result) { result = nodes.slice(0, i); } @@ -695,26 +702,26 @@ namespace ts { function addNodeWorker(to: Node[], from: OneOrMany, startOnNewLine: boolean, test: (node: Node) => boolean, parenthesize: (node: Node, parentNode: Node) => Node, parentNode: Node, isVisiting: boolean) { if (to && from) { - if (isNodeArrayNode(from)) { + if (isArray(from)) { + addNodesWorker(to, from, startOnNewLine, test, parenthesize, parentNode, isVisiting); + } + else if (isNodeArrayNode(from)) { addNodesWorker(to, from.nodes, startOnNewLine, test, parenthesize, parentNode, isVisiting); - return; } + else { + const node = parenthesize !== undefined ? parenthesize(from, parentNode) : from; + Debug.assert(test === undefined || test(node), "Wrong node type after visit.", () => `Node ${formatSyntaxKind(node.kind)} did not pass test ${(test).name}.`); - if (parenthesize !== undefined) { - from = parenthesize(from, parentNode); + if (startOnNewLine) { + node.startsOnNewLine = true; + } + + if (isVisiting) { + aggregateTransformFlags(node); + } + + to.push(node); } - - Debug.assert(test === undefined || test(from), "Wrong node type after visit.", () => `Node ${formatSyntaxKind(from.kind)} did not pass test ${(test).name}.`); - - if (startOnNewLine) { - from.startsOnNewLine = true; - } - - if (isVisiting) { - aggregateTransformFlags(from); - } - - to.push(from); } }