Merge pull request #15791 from Microsoft/fix13935

Adds CommaList to avoid large deeply nested comma expressions
This commit is contained in:
Ron Buckton 2017-05-12 16:49:13 -07:00 committed by GitHub
commit 50e2912cd2
7 changed files with 89 additions and 1 deletions

View File

@ -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<T>(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.

View File

@ -733,6 +733,9 @@ namespace ts {
// Transformation nodes
case SyntaxKind.PartiallyEmittedExpression:
return emitPartiallyEmittedExpression(<PartiallyEmittedExpression>node);
case SyntaxKind.CommaListExpression:
return emitCommaList(<CommaListExpression>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,

View File

@ -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 (<CommaListExpression>node).elements;
}
if (isBinaryExpression(node) && node.operatorToken.kind === SyntaxKind.CommaToken) {
return [node.left, node.right];
}
}
return node;
}
export function createCommaList(elements: Expression[]) {
const node = <CommaListExpression>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 = <Bundle>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 {

View File

@ -362,6 +362,8 @@ namespace ts {
return visitNode(cbNode, (<ExternalModuleReference>node).expression);
case SyntaxKind.MissingDeclaration:
return visitNodes(cbNodes, node.decorators);
case SyntaxKind.CommaListExpression:
return visitNodes(cbNodes, (<CommaListExpression>node).elements);
case SyntaxKind.JsxElement:
return visitNode(cbNode, (<JsxElement>node).openingElement) ||

View File

@ -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<Expression>;
}
/**
* Marks the beginning of a merged transformed declaration.
*/

View File

@ -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);
}

View File

@ -876,6 +876,10 @@ namespace ts {
return updatePartiallyEmittedExpression(<PartiallyEmittedExpression>node,
visitNode((<PartiallyEmittedExpression>node).expression, visitor, isExpression));
case SyntaxKind.CommaListExpression:
return updateCommaList(<CommaListExpression>node,
nodesVisitor((<CommaListExpression>node).elements, visitor, isExpression));
default:
// No need to visit nodes with no children.
return node;
@ -1389,6 +1393,10 @@ namespace ts {
result = reduceNode((<PartiallyEmittedExpression>node).expression, cbNode, result);
break;
case SyntaxKind.CommaListExpression:
result = reduceNodes((<CommaListExpression>node).elements, cbNodes, result);
break;
default:
break;
}