diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 321cc06f765..5e4afce98c8 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -490,6 +490,35 @@ namespace ts { return result; } + /** + * Maps an array. If the mapped value is an array, it is spread into the result. + * Avoids allocation if all elements map to themselves. + * + * @param array The array to map. + * @param mapfn The callback used to map the result into one or more values. + */ + export function sameFlatMap(array: T[], mapfn: (x: T, i: number) => T | T[]): T[] { + let result: T[]; + if (array) { + for (let i = 0; i < array.length; i++) { + const item = array[i]; + const mapped = mapfn(item, i); + if (result || item !== mapped || isArray(mapped)) { + if (!result) { + result = array.slice(0, i); + } + if (isArray(mapped)) { + addRange(result, mapped); + } + else { + result.push(mapped); + } + } + } + } + return result || array; + } + /** * Computes the first matching span of elements and returns a tuple of the first span * and the remaining elements. diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index 669c9f5dd36..76423e45bd9 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -733,6 +733,9 @@ namespace ts { // Transformation nodes case SyntaxKind.PartiallyEmittedExpression: return emitPartiallyEmittedExpression(node); + + case SyntaxKind.CommaListExpression: + return emitCommaList(node); } } @@ -2101,6 +2104,10 @@ namespace ts { emitExpression(node.expression); } + function emitCommaList(node: CommaListExpression) { + emitExpressionList(node, node.elements, ListFormat.CommaListElements); + } + /** * Emits any prologue directives at the start of a Statement list, returning the * number of prologue directives written to the output. @@ -2951,6 +2958,7 @@ namespace ts { ArrayBindingPatternElements = SingleLine | AllowTrailingComma | CommaDelimited | SpaceBetweenSiblings, ObjectLiteralExpressionProperties = PreserveLines | CommaDelimited | SpaceBetweenSiblings | SpaceBetweenBraces | Indented | Braces, ArrayLiteralExpressionElements = PreserveLines | CommaDelimited | SpaceBetweenSiblings | AllowTrailingComma | Indented | SquareBrackets, + CommaListElements = CommaDelimited | SpaceBetweenSiblings | SingleLine, CallExpressionArguments = CommaDelimited | SpaceBetweenSiblings | SingleLine | Parenthesis, NewExpressionArguments = CommaDelimited | SpaceBetweenSiblings | SingleLine | Parenthesis | OptionalIfUndefined, TemplateExpressionSpans = SingleLine | NoInterveningComments, diff --git a/src/compiler/factory.ts b/src/compiler/factory.ts index 2b781b6dbb3..42f6a1267bc 100644 --- a/src/compiler/factory.ts +++ b/src/compiler/factory.ts @@ -2077,6 +2077,30 @@ namespace ts { return node; } + function flattenCommaElements(node: Expression): Expression | Expression[] { + if (nodeIsSynthesized(node) && !isParseTreeNode(node) && !node.original && !node.emitNode && !node.id) { + if (node.kind === SyntaxKind.CommaListExpression) { + return (node).elements; + } + if (isBinaryExpression(node) && node.operatorToken.kind === SyntaxKind.CommaToken) { + return [node.left, node.right]; + } + } + return node; + } + + export function createCommaList(elements: Expression[]) { + const node = createSynthesizedNode(SyntaxKind.CommaListExpression); + node.elements = createNodeArray(sameFlatMap(elements, flattenCommaElements)); + return node; + } + + export function updateCommaList(node: CommaListExpression, elements: Expression[]) { + return node.elements !== elements + ? updateNode(createCommaList(elements), node) + : node; + } + export function createBundle(sourceFiles: SourceFile[]) { const node = createNode(SyntaxKind.Bundle); node.sourceFiles = sourceFiles; @@ -2865,7 +2889,11 @@ namespace ts { } export function inlineExpressions(expressions: Expression[]) { - return reduceLeft(expressions, createComma); + // Avoid deeply nested comma expressions as traversing them during emit can result in "Maximum call + // stack size exceeded" errors. + return expressions.length > 10 + ? createCommaList(expressions) + : reduceLeft(expressions, createComma); } export function createExpressionFromEntityName(node: EntityName | Expression): Expression { diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index cc3fb24bdb8..dd189784a17 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -362,6 +362,8 @@ namespace ts { return visitNode(cbNode, (node).expression); case SyntaxKind.MissingDeclaration: return visitNodes(cbNodes, node.decorators); + case SyntaxKind.CommaListExpression: + return visitNodes(cbNodes, (node).elements); case SyntaxKind.JsxElement: return visitNode(cbNode, (node).openingElement) || diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 40b8ce30678..67d80a92ce2 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -389,6 +389,7 @@ namespace ts { // Transformation nodes NotEmittedStatement, PartiallyEmittedExpression, + CommaListExpression, MergeDeclarationMarker, EndOfDeclarationMarker, @@ -1603,6 +1604,14 @@ namespace ts { kind: SyntaxKind.EndOfDeclarationMarker; } + /** + * A list of comma-seperated expressions. This node is only created by transformations. + */ + export interface CommaListExpression extends Expression { + kind: SyntaxKind.CommaListExpression; + elements: NodeArray; + } + /** * Marks the beginning of a merged transformed declaration. */ diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index a7384db9f52..58a1822a24d 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -2327,6 +2327,9 @@ namespace ts { case SyntaxKind.SpreadElement: return 1; + case SyntaxKind.CommaListExpression: + return 0; + default: return -1; } @@ -3915,6 +3918,7 @@ namespace ts { || kind === SyntaxKind.SpreadElement || kind === SyntaxKind.AsExpression || kind === SyntaxKind.OmittedExpression + || kind === SyntaxKind.CommaListExpression || isUnaryExpressionKind(kind); } diff --git a/src/compiler/visitor.ts b/src/compiler/visitor.ts index e434dadeaf7..517854e21ce 100644 --- a/src/compiler/visitor.ts +++ b/src/compiler/visitor.ts @@ -876,6 +876,10 @@ namespace ts { return updatePartiallyEmittedExpression(node, visitNode((node).expression, visitor, isExpression)); + case SyntaxKind.CommaListExpression: + return updateCommaList(node, + nodesVisitor((node).elements, visitor, isExpression)); + default: // No need to visit nodes with no children. return node; @@ -1389,6 +1393,10 @@ namespace ts { result = reduceNode((node).expression, cbNode, result); break; + case SyntaxKind.CommaListExpression: + result = reduceNodes((node).elements, cbNodes, result); + break; + default: break; }