diff --git a/src/compiler/factory.ts b/src/compiler/factory.ts index 0c6b6f4d89..8f388a0152 100644 --- a/src/compiler/factory.ts +++ b/src/compiler/factory.ts @@ -250,7 +250,7 @@ namespace ts { node.decorators = undefined; node.modifiers = undefined; node.typeParameters = undefined; - node.parameters = createSynthesizedNodeArray(parameters); + node.parameters = createNodeArray(parameters); node.type = undefined; node.body = body; return node; @@ -286,7 +286,7 @@ namespace ts { node.name = typeof name === "string" ? createIdentifier(name) : name; node.questionToken = undefined; node.type = undefined; - node.initializer = initializer; + node.initializer = initializer ? parenthesizeExpressionForList(initializer) : undefined; return node; } @@ -294,7 +294,7 @@ namespace ts { export function createArrayLiteral(elements?: Expression[]) { const node = createNode(SyntaxKind.ArrayLiteralExpression); - node.elements = createNodeArray(elements); + node.elements = parenthesizeListElements(createNodeArray(elements)); return node; } @@ -322,14 +322,16 @@ namespace ts { export function createCall(expression: Expression, argumentsArray: Expression[], location?: TextRange) { const node = createNode(SyntaxKind.CallExpression, location); node.expression = parenthesizeForAccess(expression); - node.arguments = createNodeArray(argumentsArray); + node.arguments = parenthesizeListElements(createNodeArray(argumentsArray)); return node; } export function createNew(expression: Expression, argumentsArray: Expression[], location?: TextRange) { const node = createNode(SyntaxKind.NewExpression, location); node.expression = parenthesizeForAccess(expression); - node.arguments = argumentsArray ? createNodeArray(argumentsArray) : undefined; + node.arguments = argumentsArray + ? parenthesizeListElements(createNodeArray(argumentsArray)) + : undefined; return node; } @@ -358,7 +360,7 @@ namespace ts { node.parameters = createNodeArray(parameters); node.type = undefined; node.equalsGreaterThanToken = createNode(SyntaxKind.EqualsGreaterThanToken); - node.body = body; + node.body = parenthesizeConciseBody(body); return node; } @@ -414,7 +416,7 @@ namespace ts { export function createSpread(expression: Expression) { const node = createNode(SyntaxKind.SpreadElementExpression); - node.expression = expression; + node.expression = parenthesizeExpressionForList(expression); return node; } @@ -424,8 +426,8 @@ namespace ts { node.modifiers = undefined; node.name = name; node.typeParameters = undefined; - node.heritageClauses = createSynthesizedNodeArray(heritageClauses); - node.members = createSynthesizedNodeArray(members); + node.heritageClauses = createNodeArray(heritageClauses); + node.members = createNodeArray(members); return node; } @@ -469,7 +471,7 @@ namespace ts { export function createVariableDeclaration(name: string | BindingPattern | Identifier, initializer?: Expression, location?: TextRange): VariableDeclaration { const node = createNode(SyntaxKind.VariableDeclaration, location); node.name = typeof name === "string" ? createIdentifier(name) : name; - node.initializer = initializer; + node.initializer = initializer !== undefined ? parenthesizeExpressionForList(initializer) : undefined; return node; } @@ -479,7 +481,7 @@ namespace ts { export function createStatement(expression: Expression, location?: TextRange): ExpressionStatement { const node = createNode(SyntaxKind.ExpressionStatement, location); - node.expression = expression; + node.expression = parenthesizeExpressionForExpressionStatement(expression); return node; } @@ -562,8 +564,8 @@ namespace ts { setModifiers(node, modifiers); node.name = name; node.typeParameters = undefined; - node.heritageClauses = createSynthesizedNodeArray(heritageClauses); - node.members = createSynthesizedNodeArray(members); + node.heritageClauses = createNodeArray(heritageClauses); + node.members = createNodeArray(members); return node; } @@ -599,7 +601,14 @@ namespace ts { export function createHeritageClause(token: SyntaxKind, types: ExpressionWithTypeArguments[], location?: TextRange) { const node = createNode(SyntaxKind.HeritageClause, location); node.token = token; - node.types = createSynthesizedNodeArray(types); + node.types = createNodeArray(types); + return node; + } + + export function createCaseClause(expression: Expression, statements: Statement[], location?: TextRange) { + const node = createNode(SyntaxKind.CaseClause, location); + node.expression = parenthesizeExpressionForList(expression); + node.statements = createNodeArray(statements); return node; } @@ -609,7 +618,7 @@ namespace ts { const node = createNode(SyntaxKind.PropertyAssignment, location); node.name = typeof name === "string" ? createIdentifier(name) : name; node.questionToken = undefined; - node.initializer = initializer; + node.initializer = initializer !== undefined ? parenthesizeExpressionForList(initializer) : undefined; return node; } @@ -1206,6 +1215,26 @@ namespace ts { : createParen(operand, /*location*/ operand); } + function parenthesizeListElements(elements: NodeArray) { + let result: Expression[]; + for (let i = 0; i < elements.length; i++) { + const element = parenthesizeExpressionForList(elements[i]); + if (result !== undefined || element !== elements[i]) { + if (result === undefined) { + result = elements.slice(0, i); + } + + result.push(element); + } + } + + if (result !== undefined) { + return createNodeArray(result, elements, elements.hasTrailingComma); + } + + return elements; + } + export function parenthesizeExpressionForList(expression: Expression) { const expressionPrecedence = getExpressionPrecedence(expression); const commaPrecedence = getOperatorPrecedence(SyntaxKind.BinaryExpression, SyntaxKind.CommaToken); @@ -1231,6 +1260,14 @@ namespace ts { return expression; } + export function parenthesizeConciseBody(body: ConciseBody): ConciseBody { + if (body.kind === SyntaxKind.ObjectLiteralExpression) { + return createParen(body, /*location*/ body); + } + + return body; + } + function getLeftmostExpression(node: Expression): Expression { while (true) { switch (node.kind) { diff --git a/src/compiler/visitor.ts b/src/compiler/visitor.ts index be5e5fd21c..2154ddbe5c 100644 --- a/src/compiler/visitor.ts +++ b/src/compiler/visitor.ts @@ -1,4 +1,5 @@ /// +/// /* @internal */ namespace ts { @@ -162,7 +163,7 @@ namespace ts { { name: "typeParameters", test: isTypeParameter }, { name: "parameters", test: isParameter }, { name: "type", test: isTypeNode, optional: true }, - { name: "body", test: isConciseBody, lift: liftToBlock }, + { name: "body", test: isConciseBody, lift: liftToBlock, parenthesize: parenthesizeConciseBody }, ], [SyntaxKind.DeleteExpression]: [ { name: "expression", test: isUnaryExpression, parenthesize: parenthesizePrefixOperand }, @@ -407,7 +408,7 @@ namespace ts { { name: "expression", test: isExpression, optional: true }, ], [SyntaxKind.CaseClause]: [ - { name: "expression", test: isExpression }, + { name: "expression", test: isExpression, parenthesize: parenthesizeExpressionForList }, { name: "statements", test: isStatement }, ], [SyntaxKind.DefaultClause]: [ @@ -477,24 +478,46 @@ namespace ts { * @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 { + return visitNodeWorker(node, visitor, test, optional, lift, /*parenthesize*/ undefined, /*parentNode*/ undefined); + } + + /** + * 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 optional A value indicating whether the Node is itself optional. + * @param lift A callback to execute to lift a NodeArrayNode into a valid Node. + * @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 { if (node === undefined) { return undefined; } - const visited = visitor(node); + let visited = visitor(node); if (visited === node) { return node; } - const lifted = liftNode(visited, lift); - if (lifted === undefined) { + if (lift !== undefined && visited !== undefined && isNodeArrayNode(visited)) { + visited = lift((>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."); aggregateTransformFlags(visited); - return visited; + return visited; } /** @@ -507,11 +530,24 @@ namespace ts { * @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 { + return visitNodesWorker(nodes, visitor, test, /*parenthesize*/ undefined, /*parentNode*/ undefined, start, count); + } + + /** + * 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. + * @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 { if (nodes === undefined) { return undefined; } - let updated: T[]; + let updated: Node[]; // Ensure start and count have valid values const length = nodes.length; @@ -531,25 +567,21 @@ namespace ts { // Visit each original node. for (let i = 0; i < count; i++) { const node = nodes[i + start]; - const visited = node && >visitor(node); + const visited = node !== undefined ? visitor(node) : undefined; if (updated !== undefined || visited === undefined || visited !== node) { if (updated === undefined) { // Ensure we have a copy of `nodes`, up to the current index. updated = nodes.slice(0, i); } - if (visited !== node) { - aggregateTransformFlags(visited); - } - - addNodeWorker(updated, visited, /*addOnNewLine*/ undefined, test); + addNodeWorker(updated, visited, /*addOnNewLine*/ undefined, test, parenthesize, parentNode, /*isVisiting*/ visited !== node); } } if (updated !== undefined) { - return (isModifiersArray(nodes) + return isModifiersArray(nodes) ? createModifiersArray(updated, nodes) - : createNodeArray(updated, nodes, nodes.hasTrailingComma)); + : createNodeArray(updated, nodes, nodes.hasTrailingComma); } return nodes; @@ -582,9 +614,17 @@ namespace ts { for (const edge of edgeTraversalPath) { const value = >node[edge.name]; if (value !== undefined) { - const visited = visitEdge(edge, value, visitor); - if (visited && isArray(visited) && isModifiersArray(visited)) { - modifiers = visited.flags; + let visited: Node | NodeArray; + if (isArray(value)) { + const visitedArray = visitNodesWorker(value, visitor, edge.test, edge.parenthesize, node, 0, value.length); + if (isModifiersArray(visitedArray)) { + modifiers = visitedArray.flags; + } + + visited = visitedArray; + } + else { + visited = visitNodeWorker(value, visitor, edge.test, edge.optional, edge.lift, edge.parenthesize, node); } if (updated !== undefined || visited !== value) { @@ -599,7 +639,7 @@ namespace ts { } if (visited !== value) { - setEdgeValue(updated, edge, visited); + updated[edge.name] = value; } } } @@ -625,39 +665,6 @@ namespace ts { 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 isArray(value) - ? visitNodes(>value, visitor, edge.test, /*start*/ undefined, /*count*/ undefined) - : visitNode(value, visitor, edge.test, edge.optional, edge.lift); - } - - /** - * Sets the value of an edge, adjusting the value as necessary for cases such as expression precedence. - */ - function setEdgeValue(parentNode: Node & Map, edge: NodeEdge, value: Node | NodeArray) { - if (value && edge.parenthesize && !isArray(value)) { - value = parenthesizeEdge(value, parentNode, edge.parenthesize, edge.test); - } - - parentNode[edge.name] = value; - } - - /** - * Applies parentheses to a node to ensure the correct precedence. - */ - function parenthesizeEdge(node: Node, parentNode: Node, parenthesize: (node: Node, parentNode: Node) => Node, test: (node: Node) => boolean) { - node = parenthesize(node, parentNode); - Debug.assert(test === undefined || test(node), "Unexpected node kind after visit."); - return node; - } - /** * Appends a node to an array. * @@ -666,7 +673,7 @@ namespace ts { * @param test The node test used to validate each node. */ export function addNode(to: T[], from: OneOrMany, startOnNewLine?: boolean) { - addNodeWorker(to, from, startOnNewLine, /*test*/ undefined); + addNodeWorker(to, from, startOnNewLine, /*test*/ undefined, /*parenthesize*/ undefined, /*parentNode*/ undefined, /*isVisiting*/ false); } /** @@ -677,29 +684,38 @@ namespace ts { * @param test The node test used to validate each node. */ export function addNodes(to: T[], from: OneOrMany[], startOnNewLine?: boolean) { - addNodesWorker(to, from, startOnNewLine, /*test*/ undefined); + addNodesWorker(to, from, startOnNewLine, /*test*/ undefined, /*parenthesize*/ undefined, /*parentNode*/ undefined, /*isVisiting*/ false); } - function addNodeWorker(to: T[], from: OneOrMany, startOnNewLine: boolean, test: (node: Node) => boolean) { + 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)) { - addNodesWorker(to, from.nodes, startOnNewLine, test); + addNodesWorker(to, from.nodes, startOnNewLine, test, parenthesize, parentNode, isVisiting); + return; } - else { - Debug.assert(test === undefined || test(from), "Wrong node type after visit."); - if (startOnNewLine) { - from.startsOnNewLine = true; - } - to.push(from); + if (parenthesize !== undefined) { + from = parenthesize(from, parentNode); } + + Debug.assert(test === undefined || test(from), "Wrong node type after visit."); + + if (startOnNewLine) { + from.startsOnNewLine = true; + } + + if (isVisiting) { + aggregateTransformFlags(from); + } + + to.push(from); } } - function addNodesWorker(to: T[], from: OneOrMany[], startOnNewLine: boolean, test: (node: Node) => boolean) { + function addNodesWorker(to: Node[], from: OneOrMany[], startOnNewLine: boolean, test: (node: Node) => boolean, parenthesize: (node: Node, parentNode: Node) => Node, parentNode: Node, isVisiting: boolean) { if (to && from) { for (const node of from) { - addNodeWorker(to, node, startOnNewLine, test); + addNodeWorker(to, node, startOnNewLine, test, parenthesize, parentNode, isVisiting); } } }