From 49a3a3f5b220ee9a6403dff072df9fc9465529cb Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Thu, 3 Sep 2015 15:19:47 -0700 Subject: [PATCH] Centralized parent navigation and node stack logic --- src/compiler/core.ts | 3 + src/compiler/emitter.ts | 443 +++++++++++------------- src/compiler/types.ts | 49 +++ src/compiler/utilities.ts | 709 ++++++++++++++++++++++++-------------- src/services/services.ts | 4 + 5 files changed, 706 insertions(+), 502 deletions(-) diff --git a/src/compiler/core.ts b/src/compiler/core.ts index a1f6565ed1f..1ca35cf2c43 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -780,6 +780,9 @@ namespace ts { end: -1, flags: 0, parent: undefined, + createParentNavigator(): ParentNavigator { + return createParentNavigator(this); + } }; return Node; }, diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index 1ac201734a7..a1f378e7184 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -153,10 +153,9 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi // tree as we descend into the branch. It is used to derive the parent // node of any node without needing parent pointers, to help with // the emit of synthesized nodes that do not maintain parent pointers. - let nodeStackSize: number = 0; - let ancestorStack: Node[] = []; - let parentNode: Node; - let currentNode: Node; + let nodeStack: NodeStack; + let pushNode: (node: Node) => void; + let popNode: () => void; let extendsEmitted = false; let decorateEmitted = false; @@ -228,52 +227,16 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi function emitSourceFile(sourceFile: SourceFile): void { currentSourceFile = sourceFile; exportFunctionForFile = undefined; - nodeStackSize = 0; - ancestorStack.length = 0; - parentNode = undefined; - currentNode = undefined; + nodeStack = createNodeStack(); + pushNode = nodeStack.pushNode; + popNode = nodeStack.popNode; emit(sourceFile); } - function pushNode(node: Node): void { - Debug.assert(node !== undefined, "Incorrect node stack behavior during push. Argument `node` is undefined.") - Debug.assert(currentNode !== node, "Incorrect node stack behavior during push. Argument `node` is already the current node."); - Debug.assert(parentNode !== node, "Incorrect node stack behavior during push. Argument `node` is already the parent node."); - nodeStackSize++; - if (nodeStackSize > 2) { - ancestorStack.push(parentNode); - } - parentNode = currentNode; - currentNode = node; - } - - function popNode(): void { - currentNode = parentNode; - parentNode = nodeStackSize > 2 ? ancestorStack.pop() : undefined; - nodeStackSize--; - } - - function peekNode(offset: number): Node { - switch (offset) { - case 0: return currentNode; - case 1: return parentNode; - default: return nodeStackSize > 2 ? ancestorStack[nodeStackSize - 1 - offset] : undefined; - } - } - function findAncestorNode(match: (node: Node) => node is T): T; function findAncestorNode(match: (node: Node) => boolean): Node; function findAncestorNode(match: (node: Node) => boolean) { - if (parentNode && match(parentNode)) { - return parentNode; - } - for (let i = ancestorStack.length; i >= 0; i--) { - let node = ancestorStack[i]; - if (match(node)) { - return node; - } - } - return undefined; + return nodeStack.findAncestorNode(match); } function isUniqueName(name: string): boolean { @@ -1098,20 +1061,28 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi // Now we emit the expressions if (node.template.kind === SyntaxKind.TemplateExpression) { - pushNode(node.template); - forEach((node.template).templateSpans, templateSpan => { - write(", "); - pushNode(templateSpan); - let needsParens = templateSpan.expression.kind === SyntaxKind.BinaryExpression - && (templateSpan.expression).operatorToken.kind === SyntaxKind.CommaToken; - emitParenthesizedIf(templateSpan.expression, needsParens); - popNode(); - }); - popNode(); + visitNodeWithPushStackBehavior(node.template, emitTemplateSpansInTemplateExpressionForDownlevelTaggedTemplate); } write("))"); } - + + function emitTemplateSpansInTemplateExpressionForDownlevelTaggedTemplate(node: TemplateExpression) { + verifyStackBehavior(StackBehavior.NodeIsOnTopOfStack, node); + forEach(node.templateSpans, emitTemplateSpanForDownlevelTaggedTemplateWithPushStackBehavior); + } + + function emitTemplateSpanForDownlevelTaggedTemplateWithPushStackBehavior(node: TemplateSpan) { + visitNodeWithPushStackBehavior(node, emitTemplateSpanForDownlevelTaggedTemplate); + } + + function emitTemplateSpanForDownlevelTaggedTemplate(node: TemplateSpan) { + verifyStackBehavior(StackBehavior.NodeIsOnTopOfStack, node); + write(", "); + let needsParens = node.expression.kind === SyntaxKind.BinaryExpression + && (node.expression).operatorToken.kind === SyntaxKind.CommaToken; + emitParenthesizedIf(node.expression, needsParens); + } + function emitTemplateExpression(node: TemplateExpression): void { verifyStackBehavior(StackBehavior.NodeIsOnTopOfStack, node); @@ -1121,135 +1092,84 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi forEachChild(node, emit); return; } + + emitTemplateExpressionBelowES6(node); + } + + function emitTemplateExpressionBelowES6(node: TemplateExpression): void { + verifyStackBehavior(StackBehavior.NodeIsOnTopOfStack, node); - let emitOuterParens = isExpression(parentNode, peekNode, 1) && templateNeedsParens(node); + let nav = nodeStack.createParentNavigator(); + nav.moveToParent(); + + let emitOuterParens = isExpression(nav) && templateNeedsParens(node); if (emitOuterParens) { write("("); } let headEmitted = false; - if (shouldEmitTemplateHead()) { - pushNode(node.head); - emitLiteral(node.head); - popNode(); - + if (shouldEmitTemplateHead(node)) { + visitNodeWithPushStackBehavior(node.head, emitLiteral); headEmitted = true; } for (let i = 0, n = node.templateSpans.length; i < n; i++) { let templateSpan = node.templateSpans[i]; - pushNode(templateSpan); - // Check if the expression has operands and binds its operands less closely than binary '+'. - // If it does, we need to wrap the expression in parentheses. Otherwise, something like - // `abc${ 1 << 2 }` - // becomes - // "abc" + 1 << 2 + "" - // which is really - // ("abc" + 1) << (2 + "") - // rather than - // "abc" + (1 << 2) + "" - let needsParens = templateSpan.expression.kind !== SyntaxKind.ParenthesizedExpression - && comparePrecedenceToBinaryPlus(templateSpan.expression) !== Comparison.GreaterThan; - if (i > 0 || headEmitted) { // If this is the first span and the head was not emitted, then this templateSpan's // expression will be the first to be emitted. Don't emit the preceding ' + ' in that // case. write(" + "); } - - emitParenthesizedIf(templateSpan.expression, needsParens); - - // Only emit if the literal is non-empty. - // The binary '+' operator is left-associative, so the first string concatenation - // with the head will force the result up to this point to be a string. - // Emitting a '+ ""' has no semantic effect for middles and tails. - if (templateSpan.literal.text.length !== 0) { - write(" + "); - pushNode(templateSpan.literal); - emitLiteral(templateSpan.literal); - popNode(); - } - popNode(); + + visitNodeWithPushStackBehavior(templateSpan, emitTemplateSpanBelowES6); } if (emitOuterParens) { write(")"); } + } + + function shouldEmitTemplateHead(node: TemplateExpression) { + verifyStackBehavior(StackBehavior.Unspecified, node); + + // If this expression has an empty head literal and the first template span has a non-empty + // literal, then emitting the empty head literal is not necessary. + // `${ foo } and ${ bar }` + // can be emitted as + // foo + " and " + bar + // This is because it is only required that one of the first two operands in the emit + // output must be a string literal, so that the other operand and all following operands + // are forced into strings. + // + // If the first template span has an empty literal, then the head must still be emitted. + // `${ foo }${ bar }` + // must still be emitted as + // "" + foo + bar - function shouldEmitTemplateHead() { - // If this expression has an empty head literal and the first template span has a non-empty - // literal, then emitting the empty head literal is not necessary. - // `${ foo } and ${ bar }` - // can be emitted as - // foo + " and " + bar - // This is because it is only required that one of the first two operands in the emit - // output must be a string literal, so that the other operand and all following operands - // are forced into strings. - // - // If the first template span has an empty literal, then the head must still be emitted. - // `${ foo }${ bar }` - // must still be emitted as - // "" + foo + bar + // There is always atleast one templateSpan in this code path, since + // NoSubstitutionTemplateLiterals are directly emitted via emitLiteral() + Debug.assert(node.templateSpans.length !== 0); - // There is always atleast one templateSpan in this code path, since - // NoSubstitutionTemplateLiterals are directly emitted via emitLiteral() - Debug.assert(node.templateSpans.length !== 0); - - return node.head.text.length !== 0 || node.templateSpans[0].literal.text.length === 0; - } - - function templateNeedsParens(template: TemplateExpression) { - verifyStackBehavior(StackBehavior.NodeIsOnTopOfStack, template); - - switch (parentNode.kind) { - case SyntaxKind.CallExpression: - case SyntaxKind.NewExpression: - return (parentNode).expression === template; - case SyntaxKind.TaggedTemplateExpression: - case SyntaxKind.ParenthesizedExpression: - return false; - default: - return comparePrecedenceToBinaryPlus(parentNode) !== Comparison.LessThan; - } - } - - /** - * Returns whether the expression has lesser, greater, - * or equal precedence to the binary '+' operator - */ - function comparePrecedenceToBinaryPlus(expression: Expression): Comparison { - // All binary expressions have lower precedence than '+' apart from '*', '/', and '%' - // which have greater precedence and '-' which has equal precedence. - // All unary operators have a higher precedence apart from yield. - // Arrow functions and conditionals have a lower precedence, - // although we convert the former into regular function expressions in ES5 mode, - // and in ES6 mode this function won't get called anyway. - // - // TODO (drosen): Note that we need to account for the upcoming 'yield' and - // spread ('...') unary operators that are anticipated for ES6. - switch (expression.kind) { - case SyntaxKind.BinaryExpression: - switch ((expression).operatorToken.kind) { - case SyntaxKind.AsteriskToken: - case SyntaxKind.SlashToken: - case SyntaxKind.PercentToken: - return Comparison.GreaterThan; - case SyntaxKind.PlusToken: - case SyntaxKind.MinusToken: - return Comparison.EqualTo; - default: - return Comparison.LessThan; - } - case SyntaxKind.YieldExpression: - case SyntaxKind.ConditionalExpression: - return Comparison.LessThan; - default: - return Comparison.GreaterThan; - } - } + return node.head.text.length !== 0 || node.templateSpans[0].literal.text.length === 0; } + function templateNeedsParens(template: TemplateExpression) { + verifyStackBehavior(StackBehavior.NodeIsOnTopOfStack, template); + + let parentNode = nodeStack.getParent(); + switch (parentNode.kind) { + case SyntaxKind.CallExpression: + case SyntaxKind.NewExpression: + return (parentNode).expression === template; + case SyntaxKind.TaggedTemplateExpression: + case SyntaxKind.ParenthesizedExpression: + return false; + default: + return comparePrecedenceToBinaryPlus(parentNode) !== Comparison.LessThan; + } + } + function emitTemplateSpan(span: TemplateSpan) { verifyStackBehavior(StackBehavior.NodeIsOnTopOfStack, span); @@ -1257,6 +1177,33 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi emit(span.literal); } + function emitTemplateSpanBelowES6(node: TemplateSpan) { + verifyStackBehavior(StackBehavior.NodeIsOnTopOfStack, node); + + // Check if the expression has operands and binds its operands less closely than binary '+'. + // If it does, we need to wrap the expression in parentheses. Otherwise, something like + // `abc${ 1 << 2 }` + // becomes + // "abc" + 1 << 2 + "" + // which is really + // ("abc" + 1) << (2 + "") + // rather than + // "abc" + (1 << 2) + "" + let needsParens = node.expression.kind !== SyntaxKind.ParenthesizedExpression + && comparePrecedenceToBinaryPlus(node.expression) !== Comparison.GreaterThan; + + emitParenthesizedIf(node.expression, needsParens); + + // Only emit if the literal is non-empty. + // The binary '+' operator is left-associative, so the first string concatenation + // with the head will force the result up to this point to be a string. + // Emitting a '+ ""' has no semantic effect for middles and tails. + if (node.literal.text.length !== 0) { + write(" + "); + visitNodeWithPushStackBehavior(node.literal, emitLiteral); + } + } + function jsxEmitReact(node: JsxElement|JsxSelfClosingElement) { verifyStackBehavior(StackBehavior.NodeIsOnTopOfStack, node); @@ -1304,7 +1251,22 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi write("true"); } } + + function emitJsxSpreadAttribute(node: JsxSpreadAttribute) { + verifyStackBehavior(StackBehavior.NodeIsOnTopOfStack, node); + emit(node.expression); + } + function emitNonEmptyJsxText(node: JsxText) { + verifyStackBehavior(StackBehavior.NodeIsOnTopOfStack, node); + let text = getTextToEmit(node); + if (text !== undefined) { + write(", \""); + write(text); + write("\""); + } + } + function emitJsxElement(openingNode: JsxOpeningLikeElement, children?: JsxChild[]) { verifyStackBehavior(StackBehavior.NodeIsOnTopOfStack, openingNode); @@ -1334,7 +1296,6 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi let haveOpenedObjectLiteral = false; for (let i = 0; i < attrs.length; i++) { - pushNode(attrs[i]); if (attrs[i].kind === SyntaxKind.JsxSpreadAttribute) { // If this is the first argument, we need to emit a {} as the first argument if (i === 0) { @@ -1348,7 +1309,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi if (i > 0) { write(", "); } - emit((attrs[i]).expression); + visitNodeWithPushStackBehavior(attrs[i], emitJsxSpreadAttribute); } else { Debug.assert(attrs[i].kind === SyntaxKind.JsxAttribute); @@ -1362,9 +1323,8 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi } write("{"); } - emitJsxAttribute(attrs[i]); + visitNodeWithPushStackBehavior(attrs[i], emitJsxAttribute); } - popNode(); } if (haveOpenedObjectLiteral) write("}"); @@ -1378,9 +1338,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi write(", "); } - pushNode(attrs[i]); - emitJsxAttribute(attrs[i]); - popNode(); + visitNodeWithPushStackBehavior(attrs[i], emitJsxAttribute); } write("}"); } @@ -1396,14 +1354,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi // Don't emit empty strings if (children[i].kind === SyntaxKind.JsxText) { - pushNode(children[i]); - let text = getTextToEmit(children[i]); - if (text !== undefined) { - write(", \""); - write(text); - write("\""); - } - popNode(); + visitNodeWithPushStackBehavior(children[i], emitNonEmptyJsxText); } else { write(", "); @@ -1416,7 +1367,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi write(")"); // closes "React.createElement(" emitTrailingComments(openingNode); } - + if (node.kind === SyntaxKind.JsxElement) { pushNode((node).openingElement); emitJsxElement((node).openingElement, (node).children); @@ -1427,7 +1378,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi emitJsxElement(node); } } - + function jsxEmitPreserve(node: JsxElement|JsxSelfClosingElement) { verifyStackBehavior(StackBehavior.NodeIsOnTopOfStack, node); @@ -1567,6 +1518,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi // // Object.defineProperty(C.prototype, _a, __decorate([dec], C.prototype, _a, Object.getOwnPropertyDescriptor(C.prototype, _a))); // + let parentNode = nodeStack.getParent(); if (parentNode && nodeIsDecorated(parentNode)) { if (!computedPropertyNamesToGeneratedNames) { computedPropertyNamesToGeneratedNames = []; @@ -1591,6 +1543,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi function isExpressionIdentifier(node: Node): boolean { verifyStackBehavior(StackBehavior.NodeIsOnTopOfStack, node); + let parentNode = nodeStack.getParent(); switch (parentNode.kind) { case SyntaxKind.ArrayLiteralExpression: case SyntaxKind.BinaryExpression: @@ -1707,6 +1660,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi verifyStackBehavior(StackBehavior.NodeIsOnTopOfStack, node); if (languageVersion < ScriptTarget.ES6) { + let parentNode = nodeStack.getParent(); switch (parentNode.kind) { case SyntaxKind.BindingElement: case SyntaxKind.ClassDeclaration: @@ -1841,6 +1795,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi function needsParenthesisForAwaitExpressionAsYield(node: AwaitExpression) { verifyStackBehavior(StackBehavior.NodeIsOnTopOfStack, node); + let parentNode = nodeStack.getParent(); if (parentNode.kind === SyntaxKind.BinaryExpression && !isAssignmentOperator((parentNode).operatorToken.kind)) { return true; } @@ -2601,11 +2556,10 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi function emitParenExpression(node: ParenthesizedExpression) { verifyStackBehavior(StackBehavior.NodeIsOnTopOfStack, node); - let parent = parentNode; - // If the node is synthesized, it means the emitter put the parentheses there, // not the user. If we didn't want them, the emitter would not have put them // there. + let parentNode = nodeStack.getParent(); if (!nodeIsSynthesized(node) && parentNode.kind !== SyntaxKind.ArrowFunction) { if (node.expression.kind === SyntaxKind.TypeAssertionExpression || node.expression.kind === SyntaxKind.AsExpression) { let operand = (node.expression).expression; @@ -2630,8 +2584,8 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi operand.kind !== SyntaxKind.DeleteExpression && operand.kind !== SyntaxKind.PostfixUnaryExpression && operand.kind !== SyntaxKind.NewExpression && - !(operand.kind === SyntaxKind.CallExpression && parent.kind === SyntaxKind.NewExpression) && - !(operand.kind === SyntaxKind.FunctionExpression && parent.kind === SyntaxKind.CallExpression)) { + !(operand.kind === SyntaxKind.CallExpression && parentNode.kind === SyntaxKind.NewExpression) && + !(operand.kind === SyntaxKind.FunctionExpression && parentNode.kind === SyntaxKind.CallExpression)) { emit(operand); return; } @@ -2674,9 +2628,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi return false; } - debugger; - - const parentNode = currentNode; + const parentNode = nodeStack.getNode(); const isVariableDeclarationOrBindingElement = parentNode && (parentNode.kind === SyntaxKind.VariableDeclaration || parentNode.kind === SyntaxKind.BindingElement); @@ -2788,21 +2740,19 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi // node from the checker, so we will be looking at the original source file. // If `node` is not on top of the stack, then we can get the combined node flags // by walking the node stack (preferred). - let followParentReferences = node !== currentNode; - let offset = 0; - let current: Node = node; - while (current) { + let navigable = node !== nodeStack.getNode() ? node : nodeStack; + let nav = navigable.createParentNavigator(); + do { + let current = nav.getNode(); if (current.kind === SyntaxKind.SourceFile) { - let nodeFlags = followParentReferences ? ts.getCombinedNodeFlags(node) : getCombinedNodeFlags(); + let nodeFlags = getCombinedNodeFlags(navigable); return !isExported || ((nodeFlags & NodeFlags.Export) !== 0); } else if (isFunctionLike(current) || current.kind === SyntaxKind.ModuleBlock) { return false; } - else { - current = followParentReferences ? current.parent : peekNode(++offset); - } } + while (nav.moveToParent()); } function emitBinaryExpression(node: BinaryExpression) { @@ -2810,6 +2760,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi if (languageVersion < ScriptTarget.ES6 && node.operatorToken.kind === SyntaxKind.EqualsToken && (node.left.kind === SyntaxKind.ObjectLiteralExpression || node.left.kind === SyntaxKind.ArrayLiteralExpression)) { + let parentNode = nodeStack.getParent(); emitDestructuring(node, parentNode.kind === SyntaxKind.ExpressionStatement); } else { @@ -2888,6 +2839,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi return; } + let parentNode = nodeStack.getParent(); emitToken(SyntaxKind.OpenBraceToken, node.pos); increaseIndent(); scopeEmitStart(parentNode); @@ -3400,7 +3352,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi verifyStackBehavior(StackBehavior.NodeIsOnTopOfStack, node); emitStart(node.name); - if (getCombinedNodeFlags() & NodeFlags.Export) { + if (getCombinedNodeFlags(nodeStack) & NodeFlags.Export) { let container = findAncestorNode(isModuleDeclaration); if (container) { write(getGeneratedNameForNode(container)); @@ -3451,6 +3403,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi emitStart(node); // emit call to exporter only for top level nodes + let parentNode = nodeStack.getParent(); if (compilerOptions.module === ModuleKind.System && parentNode === currentSourceFile) { // emit export default as // export("default", ) @@ -3510,6 +3463,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi } function emitExportSpecifierInSystemModule(specifier: ExportSpecifier): void { + verifyStackBehavior(StackBehavior.NodeIsOnTopOfStack, specifier); Debug.assert(compilerOptions.module === ModuleKind.System); writeLine(); @@ -3534,7 +3488,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi // because actual variable declarations are hoisted let canDefineTempVariablesInPlace = false; if (root.kind === SyntaxKind.VariableDeclaration) { - let isExported = getCombinedNodeFlags() & NodeFlags.Export; + let isExported = getCombinedNodeFlags(nodeStack) & NodeFlags.Export; let isSourceLevelForSystemModuleKind = shouldHoistDeclarationInSystemJsModule(root); canDefineTempVariablesInPlace = !isExported && !isSourceLevelForSystemModuleKind; } @@ -3557,8 +3511,9 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi write(", "); } + let parentNode = nodeStack.getNode(); const isVariableDeclarationOrBindingElement = - !isTempVariable && (currentNode.kind === SyntaxKind.VariableDeclaration || currentNode.kind === SyntaxKind.BindingElement); + !isTempVariable && (parentNode.kind === SyntaxKind.VariableDeclaration || parentNode.kind === SyntaxKind.BindingElement); let exportChanged = isNameOfExportedSourceLevelDeclarationInSystemExternalModule(name); @@ -3569,7 +3524,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi } if (isVariableDeclarationOrBindingElement) { - emitModuleMemberName(currentNode); + emitModuleMemberName(parentNode); } else { emit(name); @@ -3736,6 +3691,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi emitDestructuringAssignment(target, value); } else { + let parentNode = nodeStack.getParent(); if (parentNode.kind !== SyntaxKind.ParenthesizedExpression) { write("("); } @@ -3825,7 +3781,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi // NOTE: default initialization should not be added to let bindings in for-in\for-of statements if (isUninitializedLet) { - let grandparent = peekNode(2); + let grandparent = nodeStack.getGrandparent(); if (grandparent.kind !== SyntaxKind.ForInStatement && grandparent.kind !== SyntaxKind.ForOfStatement) { initializer = createVoidZero(); @@ -3834,6 +3790,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi } let exportChanged = isNameOfExportedSourceLevelDeclarationInSystemExternalModule(node.name); + visitNodeWithPushStackBehavior if (exportChanged) { write(`${exportFunctionForFile}("`); @@ -3872,19 +3829,15 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi popNode(); } - function getCombinedNodeFlags(): NodeFlags { - return ts.getCombinedNodeFlags(currentNode, peekNode, 0); - } - function getCombinedFlagsForIdentifier(name: Identifier): NodeFlags { verifyStackBehavior(StackBehavior.ParentIsOnTopOfStack, name); - let parent = currentNode; - if (!parent || (parent.kind !== SyntaxKind.VariableDeclaration && parent.kind !== SyntaxKind.BindingElement)) { + let parentNode = nodeStack.getNode(); + if (!parentNode || (parentNode.kind !== SyntaxKind.VariableDeclaration && parentNode.kind !== SyntaxKind.BindingElement)) { return 0; } - return getCombinedNodeFlags(); + return getCombinedNodeFlags(nodeStack); } function isES6ExportedDeclaration(node: Node) { @@ -3892,7 +3845,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi return !!(node.flags & NodeFlags.Export) && languageVersion >= ScriptTarget.ES6 && - parentNode.kind === SyntaxKind.SourceFile; + nodeStack.getParent().kind === SyntaxKind.SourceFile; } function emitVariableStatement(node: VariableStatement) { @@ -3931,7 +3884,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi } } - if (languageVersion < ScriptTarget.ES6 && parentNode === currentSourceFile) { + if (languageVersion < ScriptTarget.ES6 && nodeStack.getParent() === currentSourceFile) { pushNode(declarationList); forEach(declarationList.declarations, emitExportVariableAssignmentsOfChild); popNode(); @@ -4182,7 +4135,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi } emitSignatureAndBody(node); - if (languageVersion < ScriptTarget.ES6 && node.kind === SyntaxKind.FunctionDeclaration && parentNode === currentSourceFile && node.name) { + if (languageVersion < ScriptTarget.ES6 && node.kind === SyntaxKind.FunctionDeclaration && nodeStack.getParent() === currentSourceFile && node.name) { emitExportMemberAssignments((node).name); } @@ -5215,7 +5168,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi emitExportMemberAssignment(node); } - if (languageVersion < ScriptTarget.ES6 && parentNode === currentSourceFile && node.name) { + if (languageVersion < ScriptTarget.ES6 && nodeStack.getParent() === currentSourceFile && node.name) { emitExportMemberAssignments(node.name); } } @@ -5570,6 +5523,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi function emitSerializedTypeNode(node: TypeNode) { verifyStackBehavior(StackBehavior.ParentIsOnTopOfStack, node); + if (!node) { return; } @@ -5732,7 +5686,6 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi } let parameter = parameters[i]; - pushNode(parameter); if (parameter.dotDotDotToken) { let parameterType = parameters[i].type; if (parameterType.kind === SyntaxKind.ArrayType) { @@ -5745,13 +5698,12 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi parameterType = undefined; } - emitSerializedTypeNode(parameterType); + visitNodeWithPushStackBehavior(parameterType, emitSerializedTypeNode); } else { - emitSerializedTypeOfNode(parameters[i]); + visitNodeWithPushStackBehavior(parameters[i], emitSerializedTypeOfNode); } } - popNode(); } } } @@ -5878,7 +5830,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi emitEnd(node); write(";"); } - if (languageVersion < ScriptTarget.ES6 && parentNode === currentSourceFile) { + if (languageVersion < ScriptTarget.ES6 && nodeStack.getParent() === currentSourceFile) { if (compilerOptions.module === ModuleKind.System && (node.flags & NodeFlags.Export)) { // write the call to exporter for enum writeLine(); @@ -5895,7 +5847,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi function emitEnumMember(node: EnumMember) { verifyStackBehavior(StackBehavior.NodeIsOnTopOfStack, node); - let enumParent = parentNode; + let enumParent = nodeStack.getParent(); emitStart(node); write(getGeneratedNameForNode(enumParent)); write("["); @@ -6014,7 +5966,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi emitModuleMemberName(node); write(" = {}));"); emitEnd(node); - if (!isES6ExportedDeclaration(node) && node.name.kind === SyntaxKind.Identifier && parentNode === currentSourceFile) { + if (!isES6ExportedDeclaration(node) && node.name.kind === SyntaxKind.Identifier && nodeStack.getParent() === currentSourceFile) { if (compilerOptions.module === ModuleKind.System && (node.flags & NodeFlags.Export)) { writeLine(); write(`${exportFunctionForFile}("`); @@ -6505,6 +6457,14 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi write("}"); } } + + function getLocalNameForExternalImportWithPushStackBehavior(node: ImportDeclaration | ExportDeclaration | ImportEqualsDeclaration): string { + verifyStackBehavior(StackBehavior.ParentIsOnTopOfStack, node); + pushNode(node); + let result = getLocalNameForExternalImport(node); + popNode(); + return result; + } function getLocalNameForExternalImport(node: ImportDeclaration | ExportDeclaration | ImportEqualsDeclaration): string { verifyStackBehavior(StackBehavior.NodeIsOnTopOfStack, node); @@ -6559,9 +6519,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi write(", "); } - pushNode(importNode); - write(getLocalNameForExternalImport(importNode)); - popNode(); + write(getLocalNameForExternalImportWithPushStackBehavior(importNode)); } if (started) { @@ -6805,7 +6763,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi emitDeclarationName(node); - let flags = getCombinedNodeFlags(); + let flags = getCombinedNodeFlags(nodeStack); if (flags & NodeFlags.Export) { if (!exportedDeclarations) { exportedDeclarations = []; @@ -6858,8 +6816,8 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi // - it is top level block scoped // if block scoped variables are nested in some another block then // no other functions can use them except ones that are defined at least in the same block - return (getCombinedNodeFlags() & NodeFlags.BlockScoped) === 0 || - getEnclosingBlockScopeContainer(node, peekNode, /*offset*/ 0).kind === SyntaxKind.SourceFile; + return (getCombinedNodeFlags(nodeStack) & NodeFlags.BlockScoped) === 0 || + getEnclosingBlockScopeContainer(nodeStack).kind === SyntaxKind.SourceFile; } function isCurrentFileSystemExternalModule() { @@ -6938,12 +6896,12 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi let group = dependencyGroups[i]; // derive a unique name for parameter from the first named entry in the group - let parameterName = makeUniqueName(forEach(group, getLocalNameForExternalImport) || ""); + let parameterName = makeUniqueName(forEach(group, getLocalNameForExternalImportWithPushStackBehavior) || ""); write(`function (${parameterName}) {`); increaseIndent(); for(let entry of group) { - let importVariableName = getLocalNameForExternalImport(entry) || ""; + let importVariableName = getLocalNameForExternalImportWithPushStackBehavior(entry) || ""; switch (entry.kind) { case SyntaxKind.ImportDeclaration: @@ -7014,7 +6972,6 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi write("}"); decreaseIndent(); - popNode(); } write("],"); } @@ -7037,10 +6994,16 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi continue; case SyntaxKind.ExportDeclaration: if (!(statement).moduleSpecifier) { + pushNode(statement); + pushNode((statement).exportClause); for (let element of (statement).exportClause.elements) { // write call to exporter function for every export specifier in exports list + pushNode(element); emitExportSpecifierInSystemModule(element); + popNode(); } + popNode(); + popNode(); } continue; case SyntaxKind.ImportEqualsDeclaration: @@ -7090,6 +7053,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi let externalImport = externalImports[i]; pushNode(externalImport); let text = getExternalModuleNameText(externalImport); + popNode(); if (hasProperty(groupIndices, text)) { // deduplicate/group entries in dependency list by the dependency name let groupIndex = groupIndices[text]; @@ -7106,8 +7070,8 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi } write(text); - popNode(); } + write(`], function(${exportFunctionForFile}) {`); writeLine(); increaseIndent(); @@ -7498,26 +7462,21 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi emitTempDeclarations(/*newLine*/ true); } - pushNode(node.endOfFileToken); - emitLeadingComments(node.endOfFileToken); - popNode(); + visitNodeWithPushStackBehavior(node.endOfFileToken, emitLeadingComments); } - function emitNodeWithPushStackBehavior(node: Node, emitNodeWithoutStackBehavior: (node: Node, emitNode: (node: Node) => void) => void, emitNode?: (node: Node) => void): void { + function visitNodeWithPushStackBehavior(node: T, visit: (node: T) => U): U; + function visitNodeWithPushStackBehavior(node: T, pipe: (node: T, emitNode: (node: T) => U) => U, visit?: (node: T) => U): U; + function visitNodeWithPushStackBehavior(node: T, pipe: (node: T, emitNode?: (node: T) => U) => U, visit?: (node: T) => U): U { + let result: U; if (node) { verifyStackBehavior(StackBehavior.ParentIsOnTopOfStack, node); - let debugStackSize = nodeStackSize; - let debugParentNode = parentNode; - let debugCurrentNode = currentNode; - pushNode(node); - emitNodeWithoutStackBehavior(node, emitNode); + result = visit ? pipe(node, visit) : pipe(node); popNode(); - - Debug.assert(debugStackSize === nodeStackSize - && debugParentNode === parentNode - && debugCurrentNode === currentNode, "Incorrect stack behavior. Node stack after emit does not match node stack before emit."); } + + return result; } function emitNodeWithCommentsAndWithoutSourcemap(node: Node): void { @@ -7529,7 +7488,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi verifyStackBehavior(StackBehavior.ParentIsOnTopOfStack, node); if (node.flags & NodeFlags.Ambient) { - return emitNodeWithPushStackBehavior(node, emitOnlyPinnedOrTripleSlashComments); + return visitNodeWithPushStackBehavior(node, emitOnlyPinnedOrTripleSlashComments); } if (isSpecializedCommentHandling(node)) { @@ -7537,11 +7496,13 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi return emitNodeWithoutSourceMap(node); } - emitNodeWithPushStackBehavior(node, emitNodeWithCommentsAndWithoutStackBehavior, emitNodeConsideringSourcemapWithoutStackBehavior); + visitNodeWithPushStackBehavior(node, emitNodeWithCommentsAndWithoutStackBehavior, emitNodeConsideringSourcemapWithoutStackBehavior); } } function emitNodeWithCommentsAndWithoutStackBehavior(node: Node, emitNodeConsideringSourcemapWithoutStackBehavior: (node: Node) => void): void { + verifyStackBehavior(StackBehavior.Unspecified, node); + let emitComments = shouldEmitLeadingAndTrailingComments(node); if (emitComments) { emitLeadingComments(node); @@ -7556,7 +7517,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi function emitNodeWithoutSourceMap(node: Node): void { if (node) { - emitNodeWithPushStackBehavior(node, emitNodeWithoutSourceMapAndWithoutStackBehavior); + visitNodeWithPushStackBehavior(node, emitNodeWithoutSourceMapAndWithoutStackBehavior); } } @@ -7606,6 +7567,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi // then we don't want to emit comments when we emit the body. It will have already // been taken care of when we emitted the 'return' statement for the function // expression body. + let parentNode = nodeStack.getParent(); if (node.kind !== SyntaxKind.Block && parentNode && parentNode.kind === SyntaxKind.ArrowFunction && @@ -7838,6 +7800,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi verifyStackBehavior(StackBehavior.NodeIsOnTopOfStack, node); // Emit the leading comments only if the parent's pos doesn't match because parent should take care of emitting these comments + let parentNode = nodeStack.getParent(); if (parentNode) { if (parentNode.kind === SyntaxKind.SourceFile || node.pos !== parentNode.pos) { if (hasDetachedComments(node.pos)) { @@ -7856,6 +7819,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi verifyStackBehavior(StackBehavior.NodeIsOnTopOfStack, node); // Emit the trailing comments only if the parent's pos doesn't match because parent should take care of emitting these comments + let parentNode = nodeStack.getParent(); if (parentNode && !nodeIsSynthesized(parentNode)) { if (parentNode.kind === SyntaxKind.SourceFile || node.end !== parentNode.end) { return getTrailingCommentRanges(currentSourceFile.text, node.end); @@ -8001,6 +7965,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi } function verifyStackBehavior(behavior: StackBehavior, node?: Node) { + let currentNode = nodeStack.getNode(); switch (behavior) { case StackBehavior.Unspecified: return; diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 47182e39527..56ff8fadb3e 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -441,6 +441,54 @@ namespace ts { FailedAndReported = 3 } + // @internal + export interface ParentNavigable { + /** Creates an object used to navigate parent nodes of this node. */ + createParentNavigator(): ParentNavigator; + } + + // @internal + export interface ParentNavigator extends ParentNavigable { + /** Gets the root node for the current node. */ + getRoot(): Node; + /** Gets the grandparent node for the current node. */ + getGrandparent(): Node; + /** Gets the parent node for the current node. */ + getParent(): Node; + /** Gets the current node. */ + getNode(): Node; + /** Gets the SyntaxKind of the current node. */ + getKind(): SyntaxKind; + /** Moves the navigator to the parent node of the current node. */ + moveToParent(): boolean; + /** Moves the navigator to the root node of the current node. */ + moveToRoot(): boolean; + } + + // @internal + export interface NodeStack extends ParentNavigable { + /** Gets the node at the bottom of the stack. */ + getRoot(): Node; + /** Gets the node two steps down from the top of the stack. */ + getGrandparent(): Node; + /** Gets the node one step down from the top of the stack. */ + getParent(): Node; + /** Gets the node at the top of the stack. */ + getNode(): Node; + /** Gets the SyntaxKind for the node at the top of the stack. */ + getKind(): SyntaxKind; + /** Traverses the stack from top to bottom until it finds a node that matches the supplied predicate. */ + findAncestorNode(match: (node: Node) => node is T): T; + /** Traverses the stack from top to bottom until it finds a node that matches the supplied predicate. */ + findAncestorNode(match: (node: Node) => boolean): Node; + /** Pushes a node onto the stack. */ + pushNode(node: Node): void; + /** Replaces the node at the top of the stack. */ + setNode(node: Node): void; + /** Pops the top node from the stack. */ + popNode(): void; + } + export interface Node extends TextRange { kind: SyntaxKind; flags: NodeFlags; @@ -456,6 +504,7 @@ namespace ts { /* @internal */ locals?: SymbolTable; // Locals associated with node (initialized by binding) /* @internal */ nextContainer?: Node; // Next container in declaration order (initialized by binding) /* @internal */ localSymbol?: Symbol; // Local symbol declared by node (initialized by binding only for exported nodes) + /* @internal */ createParentNavigator(): ParentNavigator; } export interface NodeArray extends Array, TextRange { diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 80edb9bf7bd..718f33971c3 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -243,6 +243,249 @@ namespace ts { return getBaseFileName(moduleName).replace(/^(\d)/, "_$1").replace(/\W/g, "_"); } + /** + * Creates an object used to navigate the ancestor's of a node by following parent pointers. + * @param currentNode The current node for the navigator. + */ + export function createParentNavigator(currentNode: Node): ParentNavigator { + /** Traverses the parent pointers for the current node to find the root node. */ + function getRoot() { + let rootNode = currentNode; + while (rootNode && rootNode.parent) { + rootNode = rootNode.parent; + } + + return rootNode; + } + + /** Gets the grandparent of the current node, without moving the navigator. */ + function getGrandparent() { + let parent = getParent(); + return parent ? parent.parent : undefined; + } + + /** Gets the parent of the current node, without moving the navigator. */ + function getParent() { + return currentNode ? currentNode.parent : undefined; + } + + /** Gets the current node. */ + function getNode() { + return currentNode; + } + + /** Gets the SyntaxKind for the current node. */ + function getKind() { + return currentNode ? currentNode.kind : undefined; + } + + /** Navigates to the parent of the current node if it has one. */ + function moveToParent(): boolean { + let parent = getParent(); + if (parent) { + currentNode = parent; + return true; + } + + return false; + } + + /** Navigates to the root node for the current node. */ + function moveToRoot(): boolean { + let rootNode = getRoot(); + if (rootNode) { + currentNode = rootNode; + return true; + } + + return false; + } + + /** Creates a new ParentNavigator from the current node. */ + function createParentNavigator() { + return ts.createParentNavigator(currentNode); + } + + return { + getRoot, + getGrandparent, + getParent, + getNode, + getKind, + moveToParent, + moveToRoot, + createParentNavigator, + }; + } + + /** + * Creates a node stack used to maintain parent relationships without parent pointers. + */ + export function createNodeStack(): NodeStack { + let stackSize: number = 0; + let stack: Node[] = []; + let rootNode: Node; + let parentNode: Node; + let currentNode: Node; + + /** Gets the node at the bottom of the stack. */ + function getRoot() { + return rootNode; + } + + /** Gets the node two steps back from the top of the stack. */ + function getGrandparent() { + return peekNode(2); + } + + /** Gets the node one step back from the top of the stack. */ + function getParent() { + return parentNode; + } + + /** Gets the node at the top of the stack. */ + function getNode() { + return currentNode; + } + + /** Gets the SyntaxKind for the node at the top of the stack. */ + function getKind() { + return currentNode ? currentNode.kind : undefined; + } + + /** Pushes a node onto the stack. */ + function pushNode(node: Node): void { + stackSize++; + if (stackSize > 2) { + stack.push(parentNode); + } + else if (stackSize === 1) { + rootNode = node; + } + parentNode = currentNode; + currentNode = node; + } + + /** Pops the top node from the stack. */ + function popNode(): void { + currentNode = parentNode; + parentNode = stackSize > 2 ? stack.pop() : undefined; + stackSize--; + if (stackSize === 0) { + rootNode = undefined; + } + } + + /** Replaces the node at the top of the stack. */ + function setNode(node: Node): void { + currentNode = node; + } + + /** Peeks at a node a specified number of steps back from the top of the stack. */ + function peekNode(stackOffset: number): Node { + switch (stackOffset) { + case 0: return currentNode; + case 1: return parentNode; + default: return stackSize > 2 ? stack[stackSize - 1 - stackOffset] : undefined; + } + } + + /** Traverses the stack from top to bottom until it finds a node that matches the supplied predicate. */ + function findAncestorNode(match: (node: Node) => node is T): T; + /** Traverses the stack from top to bottom until it finds a node that matches the supplied predicate. */ + function findAncestorNode(match: (node: Node) => boolean): Node; + function findAncestorNode(match: (node: Node) => boolean) { + if (parentNode && match(parentNode)) { + return parentNode; + } + for (let i = stack.length; i >= 0; i--) { + let node = stack[i]; + if (match(node)) { + return node; + } + } + return undefined; + } + + /** Creates a parent navigator from the top of the stack. */ + function createParentNavigator() { + return createParentNavigatorFromStackOffset(0); + } + + /** Creates a parent navigator a specified number of steps back from the top of the stack. */ + function createParentNavigatorFromStackOffset(stackOffset: number): ParentNavigator { + /** Gets the node two steps back from the current stack offset. */ + function getGrandparent() { + return peekNode(stackOffset + 2); + } + + /** Gets the node one step back from the current stack offset. */ + function getParent() { + return peekNode(stackOffset + 1); + } + + /** Gets the node at the current stack offset. */ + function getNode() { + return peekNode(stackOffset); + } + + /** Gets the SyntaxKind of the node at the current stack offset. */ + function getKind() { + let node = getNode(); + return node ? node.kind : undefined; + } + + /** Navigates to the node one step back from the current stack offset. */ + function moveToParent() { + if (getParent()) { + stackOffset++; + return true; + } + + return false; + } + + /** Navigates to the node at the bottom of the stack. */ + function moveToRoot() { + if (stackSize > 0) { + stackOffset = stackSize; + return true; + } + + return false; + } + + /** Creates a new ParentNavigator from the current stack offset. */ + function createParentNavigator() { + return createParentNavigatorFromStackOffset(stackOffset); + } + + return { + getRoot, + getGrandparent, + getParent, + getNode, + getKind, + moveToRoot, + moveToParent, + createParentNavigator, + }; + } + + return { + getRoot, + getGrandparent, + getParent, + getNode, + getKind, + pushNode, + popNode, + setNode, + findAncestorNode, + createParentNavigator, + }; + } + export function isBlockOrCatchScoped(declaration: Declaration) { return (getCombinedNodeFlags(declaration) & NodeFlags.BlockScoped) !== 0 || isCatchClauseVariableDeclaration(declaration); @@ -250,38 +493,10 @@ namespace ts { // Gets the nearest enclosing block scope container that has the provided node // as a descendant, that is not the provided node. - export function getEnclosingBlockScopeContainer(node: Node): Node; - - /** - * Gets the nearest enclosing block scope container that has the provided node - * as a descendant, that is not the provided node. - * @param node The starting node - * @param getAncestorOrSelf A callback used to get the ancestor of the starting node, used - * only when traversing the ancestors of the current node in the emitter. - * @param offset The offset in the node stack used to get the ancestor of the current node - * from the emitter's node stack. - */ - export function getEnclosingBlockScopeContainer(node: Node, getAncestorOrSelf: (offset: number) => Node, offset: number): Node; - - /** - * Gets the nearest enclosing block scope container that has the provided node - * as a descendant, that is not the provided node. - * @param node The starting node - * @param getAncestorOrSelf A callback used to get the ancestor of the starting node, used - * only when traversing the ancestors of the current node in the emitter. - * @param offset The offset in the node stack used to get the ancestor of the current node - * from the emitter's node stack. - * @remarks - * The emitter tracks the parent of the current node as it descends into the source file, - * so that we can properly emit synthesized nodes that may not have parent pointers. - * We can call `getAncestorOrSelf` with an offset that specifies how far back in the node's - * ancestry to retrieve a parent, grandparent, etc. An offset of zero (0) refers to the - * current node on the top of the node stack, an offset of one (1) refers to its parent, an - * offset of two (2) refers to the grandparent, and so on. - */ - export function getEnclosingBlockScopeContainer(node: Node, getAncestorOrSelf?: (offset: number) => Node, offset?: number): Node { - let current = getAncestorOrSelf ? getAncestorOrSelf(++offset) : node.parent; - while (current) { + export function getEnclosingBlockScopeContainer(navigable: ParentNavigable): Node { + let nav = navigable.createParentNavigator(); + while (nav.moveToParent()) { + let current = nav.getNode(); if (isFunctionLike(current)) { return current; } @@ -297,12 +512,10 @@ namespace ts { case SyntaxKind.Block: // function block is not considered block-scope container // see comment in binder.ts: bind(...), case for SyntaxKind.Block - if (!isFunctionLike(getAncestorOrSelf ? getAncestorOrSelf(offset + 1) : current.parent)) { + if (!isFunctionLike(nav.getParent())) { return current; } } - - current = getAncestorOrSelf ? getAncestorOrSelf(++offset) : current.parent; } } @@ -397,56 +610,22 @@ namespace ts { return node.kind === SyntaxKind.EnumDeclaration && isConst(node); } - /** - * Returns the node flags for this node and all relevant parent nodes. - */ - export function getCombinedNodeFlags(node: Node): NodeFlags; - - /** - * Returns the node flags for this node and all relevant parent nodes. - * @param node The starting node - * @param getAncestorOrSelf A callback used to get the ancestor of the starting node, used - * only when traversing the ancestors of the current node in the emitter. - * @param offset The offset in the node stack used to get the ancestor of the current node - * from the emitter's node stack. - */ - export function getCombinedNodeFlags(node: Node, getAncestorOrSelf: (offset: number) => Node, offset: number): NodeFlags; - - /** - * Returns the node flags for this node and all relevant parent nodes. - * @param node The starting node - * @param getAncestorOrSelf A callback used to get the ancestor of the starting node, used - * only when traversing the ancestors of the current node in the emitter. - * @param offset The offset in the node stack used to get the ancestor of the current node - * from the emitter's node stack. - * @remarks - * This is done so that nodes like variable declarations and binding elements can return - * a view of their flags that includes the modifiers from their container. i.e. flags like - * export/declare aren't stored on the variable declaration directly, but on the containing - * variable statement (if it has one). Similarly, flags for let/const are store on the - * variable declaration list. By calling this function, all those flags are combined so - * that the client can treat the node as if it actually had those flags. - * - * The emitter tracks the parent of the current node as it descends into the source file, - * so that we can properly emit synthesized nodes that may not have parent pointers. - * We can call `getAncestorOrSelf` with an offset that specifies how far back in the node's - * ancestry to retrieve a parent, grandparent, etc. An offset of zero (0) refers to the - * current node on the top of the node stack, an offset of one (1) refers to its parent, an - * offset of two (2) refers to the grandparent, and so on. - */ - export function getCombinedNodeFlags(node: Node, getAncestorOrSelf?: (offset: number) => Node, offset?: number): NodeFlags { + /** Returns the node flags for this node and all relevant parent nodes. */ + export function getCombinedNodeFlags(navigable: ParentNavigable): NodeFlags { + let nav = navigable.createParentNavigator(); + let node = nav.getNode(); while (node && (node.kind === SyntaxKind.BindingElement || isBindingPattern(node))) { - node = getAncestorOrSelf ? getAncestorOrSelf(++offset) : node.parent; + node = nav.moveToParent() ? nav.getNode() : undefined; } let flags = node.flags; if (node.kind === SyntaxKind.VariableDeclaration) { - node = getAncestorOrSelf ? getAncestorOrSelf(++offset) : node.parent; + node = nav.moveToParent() ? nav.getNode() : undefined; } if (node && node.kind === SyntaxKind.VariableDeclarationList) { flags |= node.flags; - node = getAncestorOrSelf ? getAncestorOrSelf(++offset) : node.parent; + node = nav.moveToParent() ? nav.getNode() : undefined; } if (node && node.kind === SyntaxKind.VariableStatement) { @@ -456,12 +635,12 @@ namespace ts { return flags; } - export function isConst(node: Node): boolean { - return !!(getCombinedNodeFlags(node) & NodeFlags.Const); + export function isConst(navigable: ParentNavigable): boolean { + return !!(getCombinedNodeFlags(navigable) & NodeFlags.Const); } - export function isLet(node: Node): boolean { - return !!(getCombinedNodeFlags(node) & NodeFlags.Let); + export function isLet(navigable: ParentNavigable): boolean { + return !!(getCombinedNodeFlags(navigable) & NodeFlags.Let); } export function isPrologueDirective(node: Node): boolean { @@ -716,37 +895,41 @@ namespace ts { return node && node.kind === SyntaxKind.MethodDeclaration && node.parent.kind === SyntaxKind.ObjectLiteralExpression; } - export function getContainingFunction(node: Node): FunctionLikeDeclaration { - while (true) { - node = node.parent; - if (!node || isFunctionLike(node)) { + export function getContainingFunction(navigable: ParentNavigable): FunctionLikeDeclaration { + let nav = navigable.createParentNavigator(); + while (nav.moveToParent()) { + let node = nav.getNode(); + if (isFunctionLike(node)) { return node; } } + + return undefined; } - export function getContainingClass(node: Node): ClassLikeDeclaration { - while (true) { - node = node.parent; - if (!node || isClassLike(node)) { + export function getContainingClass(navigable: ParentNavigable): ClassLikeDeclaration { + let nav = navigable.createParentNavigator(); + while (nav.moveToParent()) { + let node = nav.getNode(); + if (isClassLike(node)) { return node; } } + + return undefined; } - export function getThisContainer(node: Node, includeArrowFunctions: boolean): Node { - while (true) { - node = node.parent; - if (!node) { - return undefined; - } + export function getThisContainer(navigable: ParentNavigable, includeArrowFunctions: boolean): Node { + let nav = navigable.createParentNavigator(); + while (nav.moveToParent()) { + let node = nav.getNode(); switch (node.kind) { case SyntaxKind.ComputedPropertyName: // If the grandparent node is an object literal (as opposed to a class), // then the computed property is not a 'this' container. // A computed property name in a class needs to be a this container // so that we can error on it. - if (isClassLike(node.parent.parent)) { + if (isClassLike(nav.getGrandparent())) { return node; } // If this is a computed property, then the parent should not @@ -754,19 +937,20 @@ namespace ts { // in an object literal, like a method or accessor. But in order for // such a parent to be a this container, the reference must be in // the *body* of the container. - node = node.parent; + nav.moveToParent(); break; case SyntaxKind.Decorator: // Decorators are always applied outside of the body of a class or method. - if (node.parent.kind === SyntaxKind.Parameter && isClassElement(node.parent.parent)) { + if (nav.getParent().kind === SyntaxKind.Parameter && isClassElement(nav.getGrandparent())) { // If the decorator's parent is a Parameter, we resolve the this container from // the grandparent class declaration. - node = node.parent.parent; + nav.moveToParent(); + nav.moveToParent(); } - else if (isClassElement(node.parent)) { + else if (isClassElement(nav.getParent())) { // If the decorator's parent is a class element, we resolve the 'this' container // from the parent class declaration. - node = node.parent; + nav.moveToParent(); } break; case SyntaxKind.ArrowFunction: @@ -791,17 +975,17 @@ namespace ts { } } - export function getSuperContainer(node: Node, includeFunctions: boolean): Node { - while (true) { - node = node.parent; - if (!node) return node; + export function getSuperContainer(navigable: ParentNavigable, includeFunctions: boolean): Node { + let nav = navigable.createParentNavigator(); + while (nav.moveToParent()) { + let node = nav.getNode(); switch (node.kind) { case SyntaxKind.ComputedPropertyName: // If the grandparent node is an object literal (as opposed to a class), // then the computed property is not a 'super' container. // A computed property name in a class needs to be a super container // so that we can error on it. - if (isClassLike(node.parent.parent)) { + if (isClassLike(nav.getGrandparent())) { return node; } // If this is a computed property, then the parent should not @@ -809,19 +993,20 @@ namespace ts { // in an object literal, like a method or accessor. But in order for // such a parent to be a super container, the reference must be in // the *body* of the container. - node = node.parent; + nav.moveToParent(); break; case SyntaxKind.Decorator: // Decorators are always applied outside of the body of a class or method. - if (node.parent.kind === SyntaxKind.Parameter && isClassElement(node.parent.parent)) { + if (nav.getParent().kind === SyntaxKind.Parameter && isClassElement(nav.getGrandparent())) { // If the decorator's parent is a Parameter, we resolve the this container from // the grandparent class declaration. - node = node.parent.parent; + nav.moveToParent(); + nav.moveToParent(); } - else if (isClassElement(node.parent)) { + else if (isClassElement(nav.getParent())) { // If the decorator's parent is a class element, we resolve the 'this' container // from the parent class declaration. - node = node.parent; + nav.moveToParent(); } break; case SyntaxKind.FunctionDeclaration: @@ -867,7 +1052,9 @@ namespace ts { return (node).expression; } - export function nodeCanBeDecorated(node: Node): boolean { + export function nodeCanBeDecorated(navigable: ParentNavigable): boolean { + let nav = navigable.createParentNavigator(); + let node = nav.getNode(); switch (node.kind) { case SyntaxKind.ClassDeclaration: // classes are valid targets @@ -875,17 +1062,17 @@ namespace ts { case SyntaxKind.PropertyDeclaration: // property declarations are valid if their parent is a class declaration. - return node.parent.kind === SyntaxKind.ClassDeclaration; + return nav.getParent().kind === SyntaxKind.ClassDeclaration; case SyntaxKind.Parameter: // if the parameter's parent has a body and its grandparent is a class declaration, this is a valid target; - return (node.parent).body && node.parent.parent.kind === SyntaxKind.ClassDeclaration; + return (nav.getParent()).body && nav.getGrandparent().kind === SyntaxKind.ClassDeclaration; case SyntaxKind.GetAccessor: case SyntaxKind.SetAccessor: case SyntaxKind.MethodDeclaration: // if this method has a body and its parent is a class declaration, this is a valid target. - return (node).body && node.parent.kind === SyntaxKind.ClassDeclaration; + return (node).body && nav.getParent().kind === SyntaxKind.ClassDeclaration; } return false; @@ -944,142 +1131,151 @@ namespace ts { return nodeIsDecorated(node) || childIsDecorated(node); } - /** - * Returns whether the node is part of an expression. - * @param node The node to test - */ - export function isExpression(node: Node): boolean; - - /** - * Returns whether the node is part of an expression. - * @param node The node to test - * @param getAncestorOrSelf A callback used to get the ancestor of the starting node, used - * only when traversing the ancestors of the current node in the emitter. - * @param offset The offset in the node stack used to get the ancestor of the current node - * from the emitter's node stack. - */ - export function isExpression(node: Node, getAncestorOrSelf: (offset: number) => Node, offset: number): boolean; - - /** - * Returns whether the node is part of an expression. - * @param node The node to test - * @param getAncestorOrSelf A callback used to get the ancestor of the starting node, used - * only when traversing the ancestors of the current node in the emitter. - * @param offset The offset in the node stack used to get the ancestor of the current node - * from the emitter's node stack. - * @remarks - * The emitter tracks the parent of the current node as it descends into the source file, - * so that we can properly emit synthesized nodes that may not have parent pointers. - * We can call `getAncestorOrSelf` with an offset that specifies how far back in the node's - * ancestry to retrieve a parent, grandparent, etc. An offset of zero (0) refers to the - * current node on the top of the node stack, an offset of one (1) refers to its parent, an - * offset of two (2) refers to the grandparent, and so on. - */ - export function isExpression(node: Node, getAncestorOrSelf?: (offset: number) => Node, offset?: number): boolean { - let parent: Node; - switch (node.kind) { - case SyntaxKind.ThisKeyword: - case SyntaxKind.SuperKeyword: - case SyntaxKind.NullKeyword: - case SyntaxKind.TrueKeyword: - case SyntaxKind.FalseKeyword: - case SyntaxKind.RegularExpressionLiteral: - case SyntaxKind.ArrayLiteralExpression: - case SyntaxKind.ObjectLiteralExpression: - case SyntaxKind.PropertyAccessExpression: - case SyntaxKind.ElementAccessExpression: - case SyntaxKind.CallExpression: - case SyntaxKind.NewExpression: - case SyntaxKind.TaggedTemplateExpression: - case SyntaxKind.AsExpression: - case SyntaxKind.TypeAssertionExpression: - case SyntaxKind.ParenthesizedExpression: - case SyntaxKind.FunctionExpression: - case SyntaxKind.ClassExpression: - case SyntaxKind.ArrowFunction: - case SyntaxKind.VoidExpression: - case SyntaxKind.DeleteExpression: - case SyntaxKind.TypeOfExpression: - case SyntaxKind.PrefixUnaryExpression: - case SyntaxKind.PostfixUnaryExpression: - case SyntaxKind.BinaryExpression: - case SyntaxKind.ConditionalExpression: - case SyntaxKind.SpreadElementExpression: - case SyntaxKind.TemplateExpression: - case SyntaxKind.NoSubstitutionTemplateLiteral: - case SyntaxKind.OmittedExpression: - case SyntaxKind.JsxElement: - case SyntaxKind.JsxSelfClosingElement: - case SyntaxKind.YieldExpression: - return true; - case SyntaxKind.QualifiedName: - parent = getAncestorOrSelf ? getAncestorOrSelf(++offset) : node.parent; - while (parent.kind === SyntaxKind.QualifiedName) { - node = parent; - parent = getAncestorOrSelf ? getAncestorOrSelf(++offset) : node.parent; - } - return parent.kind === SyntaxKind.TypeQuery; - - case SyntaxKind.Identifier: - parent = getAncestorOrSelf ? getAncestorOrSelf(offset + 1) : node.parent; - if (parent.kind === SyntaxKind.TypeQuery) { + /** Returns whether the node is part of an expression. */ + export function isExpression(navigable: ParentNavigable): boolean { + let nav = navigable.createParentNavigator(); + while (true) { + let node = nav.getNode(); + switch (node.kind) { + case SyntaxKind.ThisKeyword: + case SyntaxKind.SuperKeyword: + case SyntaxKind.NullKeyword: + case SyntaxKind.TrueKeyword: + case SyntaxKind.FalseKeyword: + case SyntaxKind.RegularExpressionLiteral: + case SyntaxKind.ArrayLiteralExpression: + case SyntaxKind.ObjectLiteralExpression: + case SyntaxKind.PropertyAccessExpression: + case SyntaxKind.ElementAccessExpression: + case SyntaxKind.CallExpression: + case SyntaxKind.NewExpression: + case SyntaxKind.TaggedTemplateExpression: + case SyntaxKind.AsExpression: + case SyntaxKind.TypeAssertionExpression: + case SyntaxKind.ParenthesizedExpression: + case SyntaxKind.FunctionExpression: + case SyntaxKind.ClassExpression: + case SyntaxKind.ArrowFunction: + case SyntaxKind.VoidExpression: + case SyntaxKind.DeleteExpression: + case SyntaxKind.TypeOfExpression: + case SyntaxKind.PrefixUnaryExpression: + case SyntaxKind.PostfixUnaryExpression: + case SyntaxKind.BinaryExpression: + case SyntaxKind.ConditionalExpression: + case SyntaxKind.SpreadElementExpression: + case SyntaxKind.TemplateExpression: + case SyntaxKind.NoSubstitutionTemplateLiteral: + case SyntaxKind.OmittedExpression: + case SyntaxKind.JsxElement: + case SyntaxKind.JsxSelfClosingElement: + case SyntaxKind.YieldExpression: return true; - } - // fall through - case SyntaxKind.NumericLiteral: - case SyntaxKind.StringLiteral: - parent = getAncestorOrSelf ? getAncestorOrSelf(offset + 1) : node.parent; - switch (parent.kind) { - case SyntaxKind.VariableDeclaration: - case SyntaxKind.Parameter: - case SyntaxKind.PropertyDeclaration: - case SyntaxKind.PropertySignature: - case SyntaxKind.EnumMember: - case SyntaxKind.PropertyAssignment: - case SyntaxKind.BindingElement: - return (parent).initializer === node; - case SyntaxKind.ExpressionStatement: - case SyntaxKind.IfStatement: - case SyntaxKind.DoStatement: - case SyntaxKind.WhileStatement: - case SyntaxKind.ReturnStatement: - case SyntaxKind.WithStatement: - case SyntaxKind.SwitchStatement: - case SyntaxKind.CaseClause: - case SyntaxKind.ThrowStatement: - case SyntaxKind.SwitchStatement: - return (parent).expression === node; - case SyntaxKind.ForStatement: - let forStatement = parent; - return (forStatement.initializer === node && forStatement.initializer.kind !== SyntaxKind.VariableDeclarationList) || - forStatement.condition === node || - forStatement.incrementor === node; - case SyntaxKind.ForInStatement: - case SyntaxKind.ForOfStatement: - let forInStatement = parent; - return (forInStatement.initializer === node && forInStatement.initializer.kind !== SyntaxKind.VariableDeclarationList) || - forInStatement.expression === node; - case SyntaxKind.TypeAssertionExpression: - case SyntaxKind.AsExpression: - return node === (parent).expression; - case SyntaxKind.TemplateSpan: - return node === (parent).expression; - case SyntaxKind.ComputedPropertyName: - return node === (parent).expression; - case SyntaxKind.Decorator: - case SyntaxKind.JsxExpression: - return true; - case SyntaxKind.ExpressionWithTypeArguments: - return (parent).expression === node && isExpressionWithTypeArgumentsInClassExtendsClause(parent, getAncestorOrSelf, offset + 1); - default: - if (isExpression(parent, getAncestorOrSelf, offset)) { - return true; + case SyntaxKind.QualifiedName: + while (nav.moveToParent()) { + if (nav.getKind() !== SyntaxKind.QualifiedName) { + break; } - } + } + + return nav.getKind() === SyntaxKind.TypeQuery; + + case SyntaxKind.Identifier: + if (nav.getParent().kind === SyntaxKind.TypeQuery) { + return true; + } + + // fall through + case SyntaxKind.NumericLiteral: + case SyntaxKind.StringLiteral: + nav.moveToParent(); + + let parent = nav.getNode(); + switch (parent.kind) { + case SyntaxKind.VariableDeclaration: + case SyntaxKind.Parameter: + case SyntaxKind.PropertyDeclaration: + case SyntaxKind.PropertySignature: + case SyntaxKind.EnumMember: + case SyntaxKind.PropertyAssignment: + case SyntaxKind.BindingElement: + return (parent).initializer === node; + case SyntaxKind.ExpressionStatement: + case SyntaxKind.IfStatement: + case SyntaxKind.DoStatement: + case SyntaxKind.WhileStatement: + case SyntaxKind.ReturnStatement: + case SyntaxKind.WithStatement: + case SyntaxKind.SwitchStatement: + case SyntaxKind.CaseClause: + case SyntaxKind.ThrowStatement: + case SyntaxKind.SwitchStatement: + return (parent).expression === node; + case SyntaxKind.ForStatement: + let forStatement = parent; + return (forStatement.initializer === node && forStatement.initializer.kind !== SyntaxKind.VariableDeclarationList) || + forStatement.condition === node || + forStatement.incrementor === node; + case SyntaxKind.ForInStatement: + case SyntaxKind.ForOfStatement: + let forInStatement = parent; + return (forInStatement.initializer === node && forInStatement.initializer.kind !== SyntaxKind.VariableDeclarationList) || + forInStatement.expression === node; + case SyntaxKind.TypeAssertionExpression: + case SyntaxKind.AsExpression: + return node === (parent).expression; + case SyntaxKind.TemplateSpan: + return node === (parent).expression; + case SyntaxKind.ComputedPropertyName: + return node === (parent).expression; + case SyntaxKind.Decorator: + case SyntaxKind.JsxExpression: + return true; + case SyntaxKind.ExpressionWithTypeArguments: + return (parent).expression === node && isExpressionWithTypeArgumentsInClassExtendsClause(nav); + default: + continue; + } + } + return false; } - return false; } + /** + * Returns whether the expression has lesser, greater, + * or equal precedence to the binary '+' operator + */ + export function comparePrecedenceToBinaryPlus(expression: Expression): Comparison { + // All binary expressions have lower precedence than '+' apart from '*', '/', and '%' + // which have greater precedence and '-' which has equal precedence. + // All unary operators have a higher precedence apart from yield. + // Arrow functions and conditionals have a lower precedence, + // although we convert the former into regular function expressions in ES5 mode, + // and in ES6 mode this function won't get called anyway. + // + // TODO (drosen): Note that we need to account for the upcoming 'yield' and + // spread ('...') unary operators that are anticipated for ES6. + switch (expression.kind) { + case SyntaxKind.BinaryExpression: + switch ((expression).operatorToken.kind) { + case SyntaxKind.AsteriskToken: + case SyntaxKind.SlashToken: + case SyntaxKind.PercentToken: + return Comparison.GreaterThan; + case SyntaxKind.PlusToken: + case SyntaxKind.MinusToken: + return Comparison.EqualTo; + default: + return Comparison.LessThan; + } + case SyntaxKind.YieldExpression: + case SyntaxKind.ConditionalExpression: + return Comparison.LessThan; + default: + return Comparison.GreaterThan; + } + } + export function isInstantiatedModule(node: ModuleDeclaration, preserveConstEnums: boolean) { let moduleState = getModuleInstanceState(node); return moduleState === ModuleInstanceState.Instantiated || @@ -2103,25 +2299,12 @@ namespace ts { /** * Tests whether the node is an ExpressionWithTypeArguments node that is part of the `extends` * clause of a class. - * @param node The node to test - * @param getAncestorOrSelf A callback used to get the ancestor of the starting node, used - * only when traversing the ancestors of the current node in the emitter. - * @param offset The offset in the node stack used to get the ancestor of the current node - * from the emitter's node stack. - * @remarks - * The emitter tracks the parent of the current node as it descends into the source file, - * so that we can properly emit synthesized nodes that may not have parent pointers. - * We can call `getAncestorOrSelf` with an offset that specifies how far back in the node's - * ancestry to retrieve a parent, grandparent, etc. An offset of zero (0) refers to the - * current node on the top of the node stack, an offset of one (1) refers to its parent, an - * offset of two (2) refers to the grandparent, and so on. */ - export function isExpressionWithTypeArgumentsInClassExtendsClause(node: Node, getAncestorOrSelf?: (offset: number) => Node, offset?: number): boolean { - if (node.kind === SyntaxKind.ExpressionWithTypeArguments) { - let parent = getAncestorOrSelf ? getAncestorOrSelf(offset + 1) : node.parent; - if ((parent).token === SyntaxKind.ExtendsKeyword) { - let grandparent = getAncestorOrSelf ? getAncestorOrSelf(offset + 2) : parent.parent; - return isClassLike(grandparent); + export function isExpressionWithTypeArgumentsInClassExtendsClause(navigable: ParentNavigable): boolean { + let nav = navigable.createParentNavigator(); + if (nav.getKind() === SyntaxKind.ExpressionWithTypeArguments) { + if ((nav.getParent()).token === SyntaxKind.ExtendsKeyword) { + return isClassLike(nav.getGrandparent()); } } return false; diff --git a/src/services/services.ts b/src/services/services.ts index 752fb93cd00..231edfaa31a 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -322,6 +322,10 @@ namespace ts { return child.kind < SyntaxKind.FirstNode ? child : child.getLastToken(sourceFile); } + + public createParentNavigator(): ParentNavigator { + return createParentNavigator(this); + } } class SymbolObject implements Symbol {