Trampolines for large binary expressions (#36248)

* WIP

* Test no longer crashes, but emit trampoline is incomplete and skips pipeline phases

* Fix lints, use non-generator trampoline in emit (still skips pipeline)

* Final version with emitBinaryExprssion work stack that is only used if possible

* Fix lints

* retarget to es2015 for testing

* Use bespoke state machine trampolines in binder and checker

* Remove now extraneous code in parser

* Adjust fixupParentReferences to use a depth first preorder traversal rather than breadth first

* Reintroduce incremental fast bail in fixupParentReferences

* Revert target to es5

* PR feedback

* Small edit for devops rebuild with updated definition

* Fix comment nits, add internally extraneous check back into transformer
This commit is contained in:
Wesley Wigham 2020-01-24 16:29:55 -08:00 committed by GitHub
parent f3cc6f6d84
commit 08e6bc20bb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 15373 additions and 129 deletions

View File

@ -1445,28 +1445,156 @@ namespace ts {
}
}
const enum BindBinaryExpressionFlowState {
BindThenBindChildren,
MaybeBindLeft,
BindToken,
BindRight,
FinishBind
}
function bindBinaryExpressionFlow(node: BinaryExpression) {
const operator = node.operatorToken.kind;
if (operator === SyntaxKind.AmpersandAmpersandToken || operator === SyntaxKind.BarBarToken || operator === SyntaxKind.QuestionQuestionToken) {
if (isTopLevelLogicalExpression(node)) {
const postExpressionLabel = createBranchLabel();
bindLogicalExpression(node, postExpressionLabel, postExpressionLabel);
currentFlow = finishFlowLabel(postExpressionLabel);
}
else {
bindLogicalExpression(node, currentTrueTarget!, currentFalseTarget!);
const workStacks: {
expr: BinaryExpression[],
state: BindBinaryExpressionFlowState[],
inStrictMode: (boolean | undefined)[],
parent: (Node | undefined)[],
subtreeFlags: (number | undefined)[]
} = {
expr: [node],
state: [BindBinaryExpressionFlowState.MaybeBindLeft],
inStrictMode: [undefined],
parent: [undefined],
subtreeFlags: [undefined]
};
let stackIndex = 0;
while (stackIndex >= 0) {
node = workStacks.expr[stackIndex];
switch (workStacks.state[stackIndex]) {
case BindBinaryExpressionFlowState.BindThenBindChildren: {
// This state is used only when recuring, to emulate the work that `bind` does before
// reaching `bindChildren`. A normal call to `bindBinaryExpressionFlow` will already have done this work.
node.parent = parent;
const saveInStrictMode = inStrictMode;
bindWorker(node);
const saveParent = parent;
parent = node;
let subtreeFlagsState: number | undefined;
// While this next part does the work of `bindChildren` before it descends into `bindChildrenWorker`
// and uses `subtreeFlagsState` to queue up the work that needs to be done once the node is bound.
if (skipTransformFlagAggregation) {
// do nothing extra
}
else if (node.transformFlags & TransformFlags.HasComputedFlags) {
skipTransformFlagAggregation = true;
subtreeFlagsState = -1;
}
else {
const savedSubtreeTransformFlags = subtreeTransformFlags;
subtreeTransformFlags = 0;
subtreeFlagsState = savedSubtreeTransformFlags;
}
advanceState(BindBinaryExpressionFlowState.MaybeBindLeft, saveInStrictMode, saveParent, subtreeFlagsState);
break;
}
case BindBinaryExpressionFlowState.MaybeBindLeft: {
const operator = node.operatorToken.kind;
// TODO: bindLogicalExpression is recursive - if we want to handle deeply nested `&&` expressions
// we'll need to handle the `bindLogicalExpression` scenarios in this state machine, too
// For now, though, since the common cases are chained `+`, leaving it recursive is fine
if (operator === SyntaxKind.AmpersandAmpersandToken || operator === SyntaxKind.BarBarToken || operator === SyntaxKind.QuestionQuestionToken) {
if (isTopLevelLogicalExpression(node)) {
const postExpressionLabel = createBranchLabel();
bindLogicalExpression(node, postExpressionLabel, postExpressionLabel);
currentFlow = finishFlowLabel(postExpressionLabel);
}
else {
bindLogicalExpression(node, currentTrueTarget!, currentFalseTarget!);
}
completeNode();
}
else {
advanceState(BindBinaryExpressionFlowState.BindToken);
maybeBind(node.left);
}
break;
}
case BindBinaryExpressionFlowState.BindToken: {
advanceState(BindBinaryExpressionFlowState.BindRight);
maybeBind(node.operatorToken);
break;
}
case BindBinaryExpressionFlowState.BindRight: {
advanceState(BindBinaryExpressionFlowState.FinishBind);
maybeBind(node.right);
break;
}
case BindBinaryExpressionFlowState.FinishBind: {
const operator = node.operatorToken.kind;
if (isAssignmentOperator(operator) && !isAssignmentTarget(node)) {
bindAssignmentTargetFlow(node.left);
if (operator === SyntaxKind.EqualsToken && node.left.kind === SyntaxKind.ElementAccessExpression) {
const elementAccess = <ElementAccessExpression>node.left;
if (isNarrowableOperand(elementAccess.expression)) {
currentFlow = createFlowMutation(FlowFlags.ArrayMutation, currentFlow, node);
}
}
}
completeNode();
break;
}
default: return Debug.fail(`Invalid state ${workStacks.state[stackIndex]} for bindBinaryExpressionFlow`);
}
}
else {
bindEachChild(node);
if (isAssignmentOperator(operator) && !isAssignmentTarget(node)) {
bindAssignmentTargetFlow(node.left);
if (operator === SyntaxKind.EqualsToken && node.left.kind === SyntaxKind.ElementAccessExpression) {
const elementAccess = <ElementAccessExpression>node.left;
if (isNarrowableOperand(elementAccess.expression)) {
currentFlow = createFlowMutation(FlowFlags.ArrayMutation, currentFlow, node);
}
/**
* Note that `advanceState` sets the _current_ head state, and that `maybeBind` potentially pushes on a new
* head state; so `advanceState` must be called before any `maybeBind` during a state's execution.
*/
function advanceState(state: BindBinaryExpressionFlowState, isInStrictMode?: boolean, parent?: Node, subtreeFlags?: number) {
workStacks.state[stackIndex] = state;
if (isInStrictMode !== undefined) {
workStacks.inStrictMode[stackIndex] = isInStrictMode;
}
if (parent !== undefined) {
workStacks.parent[stackIndex] = parent;
}
if (subtreeFlags !== undefined) {
workStacks.subtreeFlags[stackIndex] = subtreeFlags;
}
}
function completeNode() {
if (workStacks.inStrictMode[stackIndex] !== undefined) {
if (workStacks.subtreeFlags[stackIndex] === -1) {
skipTransformFlagAggregation = false;
subtreeTransformFlags |= node.transformFlags & ~getTransformFlagsSubtreeExclusions(node.kind);
}
else if (workStacks.subtreeFlags[stackIndex] !== undefined) {
subtreeTransformFlags = workStacks.subtreeFlags[stackIndex]! | computeTransformFlagsForNode(node, subtreeTransformFlags);
}
inStrictMode = workStacks.inStrictMode[stackIndex]!;
parent = workStacks.parent[stackIndex]!;
}
stackIndex--;
}
/**
* If `node` is a BinaryExpression, adds it to the local work stack, otherwise recursively binds it
*/
function maybeBind(node: Node) {
if (node && isBinaryExpression(node)) {
stackIndex++;
workStacks.expr[stackIndex] = node;
workStacks.state[stackIndex] = BindBinaryExpressionFlowState.BindThenBindChildren;
workStacks.inStrictMode[stackIndex] = undefined;
workStacks.parent[stackIndex] = undefined;
workStacks.subtreeFlags[stackIndex] = undefined;
}
else {
bind(node);
}
}
}

View File

@ -27136,12 +27136,89 @@ namespace ts {
return (target.flags & TypeFlags.Nullable) !== 0 || isTypeComparableTo(source, target);
}
const enum CheckBinaryExpressionState {
MaybeCheckLeft,
CheckRight,
FinishCheck
}
function checkBinaryExpression(node: BinaryExpression, checkMode?: CheckMode) {
if (isInJSFile(node) && getAssignedExpandoInitializer(node)) {
return checkExpression(node.right, checkMode);
const workStacks: {
expr: BinaryExpression[],
state: CheckBinaryExpressionState[],
leftType: (Type | undefined)[]
} = {
expr: [node],
state: [CheckBinaryExpressionState.MaybeCheckLeft],
leftType: [undefined]
};
let stackIndex = 0;
let lastResult: Type | undefined;
while (stackIndex >= 0) {
node = workStacks.expr[stackIndex];
switch (workStacks.state[stackIndex]) {
case CheckBinaryExpressionState.MaybeCheckLeft: {
if (isInJSFile(node) && getAssignedExpandoInitializer(node)) {
finishInvocation(checkExpression(node.right, checkMode));
break;
}
checkGrammarNullishCoalesceWithLogicalExpression(node);
const operator = node.operatorToken.kind;
if (operator === SyntaxKind.EqualsToken && (node.left.kind === SyntaxKind.ObjectLiteralExpression || node.left.kind === SyntaxKind.ArrayLiteralExpression)) {
finishInvocation(checkDestructuringAssignment(node.left, checkExpression(node.right, checkMode), checkMode, node.right.kind === SyntaxKind.ThisKeyword));
break;
}
advanceState(CheckBinaryExpressionState.CheckRight);
maybeCheckExpression(node.left);
break;
}
case CheckBinaryExpressionState.CheckRight: {
const leftType = lastResult!;
workStacks.leftType[stackIndex] = leftType;
const operator = node.operatorToken.kind;
if (operator === SyntaxKind.AmpersandAmpersandToken || operator === SyntaxKind.BarBarToken || operator === SyntaxKind.QuestionQuestionToken) {
checkTruthinessOfType(leftType, node.left);
}
advanceState(CheckBinaryExpressionState.FinishCheck);
maybeCheckExpression(node.right);
break;
}
case CheckBinaryExpressionState.FinishCheck: {
const leftType = workStacks.leftType[stackIndex]!;
const rightType = lastResult!;
finishInvocation(checkBinaryLikeExpressionWorker(node.left, node.operatorToken, node.right, leftType, rightType, node));
break;
}
default: return Debug.fail(`Invalid state ${workStacks.state[stackIndex]} for checkBinaryExpression`);
}
}
return lastResult!;
function finishInvocation(result: Type) {
lastResult = result;
stackIndex--;
}
/**
* Note that `advanceState` sets the _current_ head state, and that `maybeCheckExpression` potentially pushes on a new
* head state; so `advanceState` must be called before any `maybeCheckExpression` during a state's execution.
*/
function advanceState(nextState: CheckBinaryExpressionState) {
workStacks.state[stackIndex] = nextState;
}
function maybeCheckExpression(node: Expression) {
if (isBinaryExpression(node)) {
stackIndex++;
workStacks.expr[stackIndex] = node;
workStacks.state[stackIndex] = CheckBinaryExpressionState.MaybeCheckLeft;
workStacks.leftType[stackIndex] = undefined;
}
else {
lastResult = checkExpression(node, checkMode);
}
}
checkGrammarNullishCoalesceWithLogicalExpression(node);
return checkBinaryLikeExpression(node.left, node.operatorToken, node.right, checkMode, node);
}
function checkGrammarNullishCoalesceWithLogicalExpression(node: BinaryExpression) {
@ -27156,6 +27233,8 @@ namespace ts {
}
}
// Note that this and `checkBinaryExpression` above should behave mostly the same, except this elides some
// expression-wide checks and does not use a work stack to fold nested binary expressions into the same callstack frame
function checkBinaryLikeExpression(left: Expression, operatorToken: Node, right: Expression, checkMode?: CheckMode, errorNode?: Node): Type {
const operator = operatorToken.kind;
if (operator === SyntaxKind.EqualsToken && (left.kind === SyntaxKind.ObjectLiteralExpression || left.kind === SyntaxKind.ArrayLiteralExpression)) {
@ -27169,7 +27248,19 @@ namespace ts {
leftType = checkExpression(left, checkMode);
}
let rightType = checkExpression(right, checkMode);
const rightType = checkExpression(right, checkMode);
return checkBinaryLikeExpressionWorker(left, operatorToken, right, leftType, rightType, errorNode);
}
function checkBinaryLikeExpressionWorker(
left: Expression,
operatorToken: Node,
right: Expression,
leftType: Type,
rightType: Type,
errorNode?: Node
): Type {
const operator = operatorToken.kind;
switch (operator) {
case SyntaxKind.AsteriskToken:
case SyntaxKind.AsteriskAsteriskToken:
@ -30774,14 +30865,17 @@ namespace ts {
checkSourceElement(node.statement);
}
function checkTruthinessExpression(node: Expression, checkMode?: CheckMode) {
const type = checkExpression(node, checkMode);
function checkTruthinessOfType(type: Type, node: Node) {
if (type.flags & TypeFlags.Void) {
error(node, Diagnostics.An_expression_of_type_void_cannot_be_tested_for_truthiness);
}
return type;
}
function checkTruthinessExpression(node: Expression, checkMode?: CheckMode) {
return checkTruthinessOfType(checkExpression(node, checkMode), node);
}
function checkForStatement(node: ForStatement) {
// Grammar checking
if (!checkGrammarStatementInAmbientContext(node)) {

View File

@ -379,6 +379,7 @@ namespace ts {
// transform hooks
onEmitNode: transform.emitNodeWithNotification,
isEmitNotificationEnabled: transform.isEmitNotificationEnabled,
substituteNode: transform.substituteNode,
});
@ -437,6 +438,7 @@ namespace ts {
// transform hooks
onEmitNode: declarationTransform.emitNodeWithNotification,
isEmitNotificationEnabled: declarationTransform.isEmitNotificationEnabled,
substituteNode: declarationTransform.substituteNode,
});
const declBlocked = (!!declarationTransform.diagnostics && !!declarationTransform.diagnostics.length) || !!host.isEmitBlocked(declarationFilePath) || !!compilerOptions.noEmit;
@ -819,6 +821,7 @@ namespace ts {
const {
hasGlobalName,
onEmitNode = noEmitNotification,
isEmitNotificationEnabled,
substituteNode = noEmitSubstitution,
onBeforeEmitNodeArray,
onAfterEmitNodeArray,
@ -1160,7 +1163,7 @@ namespace ts {
lastNode = node;
lastSubstitution = undefined;
const pipelinePhase = getPipelinePhase(PipelinePhase.Notification, node);
const pipelinePhase = getPipelinePhase(PipelinePhase.Notification, emitHint, node);
pipelinePhase(emitHint, node);
Debug.assert(lastNode === node);
@ -1172,16 +1175,16 @@ namespace ts {
return substitute || node;
}
function getPipelinePhase(phase: PipelinePhase, node: Node) {
function getPipelinePhase(phase: PipelinePhase, emitHint: EmitHint, node: Node) {
switch (phase) {
case PipelinePhase.Notification:
if (onEmitNode !== noEmitNotification) {
if (onEmitNode !== noEmitNotification && (!isEmitNotificationEnabled || isEmitNotificationEnabled(node))) {
return pipelineEmitWithNotification;
}
// falls through
case PipelinePhase.Substitution:
if (substituteNode !== noEmitSubstitution) {
if (substituteNode !== noEmitSubstitution && (lastSubstitution = substituteNode(emitHint, node)) !== node) {
return pipelineEmitWithSubstitution;
}
// falls through
@ -1206,13 +1209,13 @@ namespace ts {
}
}
function getNextPipelinePhase(currentPhase: PipelinePhase, node: Node) {
return getPipelinePhase(currentPhase + 1, node);
function getNextPipelinePhase(currentPhase: PipelinePhase, emitHint: EmitHint, node: Node) {
return getPipelinePhase(currentPhase + 1, emitHint, node);
}
function pipelineEmitWithNotification(hint: EmitHint, node: Node) {
Debug.assert(lastNode === node);
const pipelinePhase = getNextPipelinePhase(PipelinePhase.Notification, node);
const pipelinePhase = getNextPipelinePhase(PipelinePhase.Notification, hint, node);
onEmitNode(hint, node, pipelinePhase);
Debug.assert(lastNode === node);
}
@ -1653,9 +1656,8 @@ namespace ts {
function pipelineEmitWithSubstitution(hint: EmitHint, node: Node) {
Debug.assert(lastNode === node || lastSubstitution === node);
const pipelinePhase = getNextPipelinePhase(PipelinePhase.Substitution, node);
lastSubstitution = substituteNode(hint, node);
pipelinePhase(hint, lastSubstitution);
const pipelinePhase = getNextPipelinePhase(PipelinePhase.Substitution, hint, node);
pipelinePhase(hint, lastSubstitution!);
Debug.assert(lastNode === node || lastSubstitution === node);
}
@ -2425,19 +2427,84 @@ namespace ts {
writeTokenText(node.operator, writeOperator);
}
function emitBinaryExpression(node: BinaryExpression) {
const isCommaOperator = node.operatorToken.kind !== SyntaxKind.CommaToken;
const indentBeforeOperator = needsIndentation(node, node.left, node.operatorToken);
const indentAfterOperator = needsIndentation(node, node.operatorToken, node.right);
const enum EmitBinaryExpressionState {
EmitLeft,
EmitRight,
FinishEmit
}
emitExpression(node.left);
increaseIndentIf(indentBeforeOperator, isCommaOperator);
emitLeadingCommentsOfPosition(node.operatorToken.pos);
writeTokenNode(node.operatorToken, node.operatorToken.kind === SyntaxKind.InKeyword ? writeKeyword : writeOperator);
emitTrailingCommentsOfPosition(node.operatorToken.end, /*prefixSpace*/ true); // Binary operators should have a space before the comment starts
increaseIndentIf(indentAfterOperator, /*writeSpaceIfNotIndenting*/ true);
emitExpression(node.right);
decreaseIndentIf(indentBeforeOperator, indentAfterOperator);
/**
* emitBinaryExpression includes an embedded work stack to attempt to handle as many nested binary expressions
* as possible without creating any additional stack frames. This can only be done when the emit pipeline does
* not require notification/substitution/comment/sourcemap decorations.
*/
function emitBinaryExpression(node: BinaryExpression) {
const nodeStack = [node];
const stateStack = [EmitBinaryExpressionState.EmitLeft];
let stackIndex = 0;
while (stackIndex >= 0) {
node = nodeStack[stackIndex];
switch (stateStack[stackIndex]) {
case EmitBinaryExpressionState.EmitLeft: {
maybePipelineEmitExpression(node.left);
break;
}
case EmitBinaryExpressionState.EmitRight: {
const isCommaOperator = node.operatorToken.kind !== SyntaxKind.CommaToken;
const indentBeforeOperator = needsIndentation(node, node.left, node.operatorToken);
const indentAfterOperator = needsIndentation(node, node.operatorToken, node.right);
increaseIndentIf(indentBeforeOperator, isCommaOperator);
emitLeadingCommentsOfPosition(node.operatorToken.pos);
writeTokenNode(node.operatorToken, node.operatorToken.kind === SyntaxKind.InKeyword ? writeKeyword : writeOperator);
emitTrailingCommentsOfPosition(node.operatorToken.end, /*prefixSpace*/ true); // Binary operators should have a space before the comment starts
increaseIndentIf(indentAfterOperator, /*writeSpaceIfNotIndenting*/ true);
maybePipelineEmitExpression(node.right);
break;
}
case EmitBinaryExpressionState.FinishEmit: {
const indentBeforeOperator = needsIndentation(node, node.left, node.operatorToken);
const indentAfterOperator = needsIndentation(node, node.operatorToken, node.right);
decreaseIndentIf(indentBeforeOperator, indentAfterOperator);
stackIndex--;
break;
}
default: return Debug.fail(`Invalid state ${stateStack[stackIndex]} for emitBinaryExpressionWorker`);
}
}
function maybePipelineEmitExpression(next: Expression) {
// Advance the state of this unit of work,
stateStack[stackIndex]++;
// Then actually do the work of emitting the node `next` returned by the prior state
// The following section should be identical to `pipelineEmit` save it assumes EmitHint.Expression and offloads
// binary expression handling, where possible, to the contained work queue
// #region trampolinePipelineEmit
const savedLastNode = lastNode;
const savedLastSubstitution = lastSubstitution;
lastNode = next;
lastSubstitution = undefined;
const pipelinePhase = getPipelinePhase(PipelinePhase.Notification, EmitHint.Expression, next);
if (pipelinePhase === pipelineEmitWithHint && isBinaryExpression(next)) {
// If the target pipeline phase is emit directly, and the next node's also a binary expression,
// skip all the intermediate indirection and push the expression directly onto the work stack
stackIndex++;
stateStack[stackIndex] = EmitBinaryExpressionState.EmitLeft;
nodeStack[stackIndex] = next;
}
else {
pipelinePhase(EmitHint.Expression, next);
}
Debug.assert(lastNode === next);
lastNode = savedLastNode;
lastSubstitution = savedLastSubstitution;
// #endregion trampolinePipelineEmit
}
}
function emitConditionalExpression(node: ConditionalExpression) {
@ -4773,7 +4840,7 @@ namespace ts {
forEach(getSyntheticLeadingComments(node), emitLeadingSynthesizedComment);
exitComment();
const pipelinePhase = getNextPipelinePhase(PipelinePhase.Comments, node);
const pipelinePhase = getNextPipelinePhase(PipelinePhase.Comments, hint, node);
if (emitFlags & EmitFlags.NoNestedComments) {
commentsDisabled = true;
pipelinePhase(hint, node);
@ -5046,7 +5113,7 @@ namespace ts {
function pipelineEmitWithSourceMap(hint: EmitHint, node: Node) {
Debug.assert(lastNode === node || lastSubstitution === node);
const pipelinePhase = getNextPipelinePhase(PipelinePhase.SourceMaps, node);
const pipelinePhase = getNextPipelinePhase(PipelinePhase.SourceMaps, hint, node);
if (isUnparsedSource(node) || isUnparsedPrepend(node)) {
pipelinePhase(hint, node);
}

View File

@ -904,28 +904,31 @@ namespace ts {
// overhead. This functions allows us to set all the parents, without all the expense of
// binding.
let parent: Node = rootNode;
forEachChild(rootNode, visitNode);
const stack: Node[] = [rootNode];
while (stack.length) {
const parent = stack.pop()!;
bindParentToChildren(parent, gatherChildren(parent));
}
return;
function visitNode(n: Node): void {
// walk down setting parents that differ from the parent we think it should be. This
// allows us to quickly bail out of setting parents for subtrees during incremental
// parsing
if (n.parent !== parent) {
n.parent = parent;
function gatherChildren(node: Node) {
const children: Node[] = [];
forEachChild(node, n => { children.unshift(n); }); // By using a stack above and `unshift` here, we emulate a depth-first preorder traversal
return children;
}
const saveParent = parent;
parent = n;
forEachChild(n, visitNode);
if (hasJSDocNodes(n)) {
for (const jsDoc of n.jsDoc!) {
jsDoc.parent = n;
parent = jsDoc;
forEachChild(jsDoc, visitNode);
function bindParentToChildren(parent: Node, children: readonly Node[]) {
for (const child of children) {
if (child.parent === parent) continue; // already bound, assume subtree is bound
child.parent = parent;
stack.push(child);
if (hasJSDocNodes(child)) {
for (const jsDoc of child.jsDoc!) {
jsDoc.parent = child;
stack.push(jsDoc);
}
}
parent = saveParent;
}
}
}

View File

@ -225,6 +225,7 @@ namespace ts {
transformed,
substituteNode,
emitNodeWithNotification,
isEmitNotificationEnabled,
dispose,
diagnostics
};
@ -288,6 +289,8 @@ namespace ts {
function emitNodeWithNotification(hint: EmitHint, node: Node, emitCallback: (hint: EmitHint, node: Node) => void) {
Debug.assert(state < TransformationState.Disposed, "Cannot invoke TransformationResult callbacks after the result is disposed.");
if (node) {
// TODO: Remove check and unconditionally use onEmitNode when API is breakingly changed
// (see https://github.com/microsoft/TypeScript/pull/36248/files/5062623f39120171b98870c71344b3242eb03d23#r369766739)
if (isEmitNotificationEnabled(node)) {
onEmitNode(hint, node, emitCallback);
}

View File

@ -5972,6 +5972,13 @@ namespace ts {
*/
emitNodeWithNotification(hint: EmitHint, node: Node, emitCallback: (hint: EmitHint, node: Node) => void): void;
/**
* Indicates if a given node needs an emit notification
*
* @param node The node to emit.
*/
isEmitNotificationEnabled?(node: Node): boolean;
/**
* Clean up EmitNode entries on any parse-tree nodes.
*/
@ -6167,6 +6174,12 @@ namespace ts {
* ```
*/
onEmitNode?(hint: EmitHint, node: Node | undefined, emitCallback: (hint: EmitHint, node: Node | undefined) => void): void;
/**
* A hook used to check if an emit notification is required for a node.
* @param node The node to emit.
*/
isEmitNotificationEnabled?(node: Node | undefined): boolean;
/**
* A hook used by the Printer to perform just-in-time substitution of a node. This is
* primarily used by node transformations that need to substitute one node for another,

View File

@ -509,7 +509,7 @@ namespace ts {
}
function isJSDocTypeExpressionOrChild(node: Node): boolean {
return node.kind === SyntaxKind.JSDocTypeExpression || (node.parent && isJSDocTypeExpressionOrChild(node.parent));
return !!findAncestor(node, isJSDocTypeExpression);
}
export function getTextOfNodeFromSourceText(sourceText: string, node: Node, includeTrivia = false): string {

View File

@ -69,56 +69,63 @@ namespace Utils {
export const canonicalizeForHarness = ts.createGetCanonicalFileName(/*caseSensitive*/ false); // This is done so tests work on windows _and_ linux
export function assertInvariants(node: ts.Node | undefined, parent: ts.Node | undefined): void {
if (node) {
assert.isFalse(node.pos < 0, "node.pos < 0");
assert.isFalse(node.end < 0, "node.end < 0");
assert.isFalse(node.end < node.pos, "node.end < node.pos");
assert.equal(node.parent, parent, "node.parent !== parent");
export function assertInvariants(node: ts.Node | undefined, parent: ts.Node | undefined) {
const queue: [ts.Node | undefined, ts.Node | undefined][] = [[node, parent]];
for (const [node, parent] of queue) {
assertInvariantsWorker(node, parent);
}
if (parent) {
// Make sure each child is contained within the parent.
assert.isFalse(node.pos < parent.pos, "node.pos < parent.pos");
assert.isFalse(node.end > parent.end, "node.end > parent.end");
}
function assertInvariantsWorker(node: ts.Node | undefined, parent: ts.Node | undefined): void {
if (node) {
assert.isFalse(node.pos < 0, "node.pos < 0");
assert.isFalse(node.end < 0, "node.end < 0");
assert.isFalse(node.end < node.pos, "node.end < node.pos");
assert.equal(node.parent, parent, "node.parent !== parent");
ts.forEachChild(node, child => {
assertInvariants(child, node);
});
if (parent) {
// Make sure each child is contained within the parent.
assert.isFalse(node.pos < parent.pos, "node.pos < parent.pos");
assert.isFalse(node.end > parent.end, "node.end > parent.end");
}
// Make sure each of the children is in order.
let currentPos = 0;
ts.forEachChild(node,
child => {
assert.isFalse(child.pos < currentPos, "child.pos < currentPos");
currentPos = child.end;
},
array => {
assert.isFalse(array.pos < node.pos, "array.pos < node.pos");
assert.isFalse(array.end > node.end, "array.end > node.end");
assert.isFalse(array.pos < currentPos, "array.pos < currentPos");
for (const item of array) {
assert.isFalse(item.pos < currentPos, "array[i].pos < currentPos");
currentPos = item.end;
}
currentPos = array.end;
ts.forEachChild(node, child => {
queue.push([child, node]);
});
const childNodesAndArrays: any[] = [];
ts.forEachChild(node, child => { childNodesAndArrays.push(child); }, array => { childNodesAndArrays.push(array); });
// Make sure each of the children is in order.
let currentPos = 0;
ts.forEachChild(node,
child => {
assert.isFalse(child.pos < currentPos, "child.pos < currentPos");
currentPos = child.end;
},
array => {
assert.isFalse(array.pos < node.pos, "array.pos < node.pos");
assert.isFalse(array.end > node.end, "array.end > node.end");
assert.isFalse(array.pos < currentPos, "array.pos < currentPos");
for (const childName in node) {
if (childName === "parent" || childName === "nextContainer" || childName === "modifiers" || childName === "externalModuleIndicator" ||
// for now ignore jsdoc comments
childName === "jsDocComment" || childName === "checkJsDirective" || childName === "commonJsModuleIndicator") {
continue;
}
const child = (<any>node)[childName];
if (isNodeOrArray(child)) {
assert.isFalse(childNodesAndArrays.indexOf(child) < 0,
"Missing child when forEach'ing over node: " + (<any>ts).SyntaxKind[node.kind] + "-" + childName);
for (const item of array) {
assert.isFalse(item.pos < currentPos, "array[i].pos < currentPos");
currentPos = item.end;
}
currentPos = array.end;
});
const childNodesAndArrays: any[] = [];
ts.forEachChild(node, child => { childNodesAndArrays.push(child); }, array => { childNodesAndArrays.push(array); });
for (const childName in node) {
if (childName === "parent" || childName === "nextContainer" || childName === "modifiers" || childName === "externalModuleIndicator" ||
// for now ignore jsdoc comments
childName === "jsDocComment" || childName === "checkJsDirective" || childName === "commonJsModuleIndicator") {
continue;
}
const child = (<any>node)[childName];
if (isNodeOrArray(child)) {
assert.isFalse(childNodesAndArrays.indexOf(child) < 0,
"Missing child when forEach'ing over node: " + (<any>ts).SyntaxKind[node.kind] + "-" + childName);
}
}
}
}

View File

@ -21,6 +21,19 @@ namespace Harness {
type?: string;
}
function* forEachASTNode(node: ts.Node) {
const work = [node];
while (work.length) {
const elem = work.pop()!;
yield elem;
const resChildren: ts.Node[] = [];
// push onto work queue in reverse order to maintain preorder traversal
ts.forEachChild(elem, c => { resChildren.unshift(c); });
work.push(...resChildren);
}
}
export class TypeWriterWalker {
currentSourceFile!: ts.SourceFile;
@ -53,19 +66,15 @@ namespace Harness {
}
private *visitNode(node: ts.Node, isSymbolWalk: boolean): IterableIterator<TypeWriterResult> {
if (ts.isExpressionNode(node) || node.kind === ts.SyntaxKind.Identifier || ts.isDeclarationName(node)) {
const result = this.writeTypeOrSymbol(node, isSymbolWalk);
if (result) {
yield result;
}
}
const children: ts.Node[] = [];
ts.forEachChild(node, child => void children.push(child));
for (const child of children) {
const gen = this.visitNode(child, isSymbolWalk);
for (let {done, value} = gen.next(); !done; { done, value } = gen.next()) {
yield value;
const gen = forEachASTNode(node);
let res = gen.next();
for (; !res.done; res = gen.next()) {
const {value: node} = res;
if (ts.isExpressionNode(node) || node.kind === ts.SyntaxKind.Identifier || ts.isDeclarationName(node)) {
const result = this.writeTypeOrSymbol(node, isSymbolWalk);
if (result) {
yield result;
}
}
}
}

View File

@ -944,6 +944,11 @@ console.log(blabla);`
path: `${tscWatch.projectRoot}/tsconfig.json`,
content: "{}"
};
// Move things from staging to node_modules without triggering watch
const moduleFile: File = {
path: `${tscWatch.projectRoot}/node_modules/@angular/core/index.d.ts`,
content: `export const y = 10;`
};
const projectFiles = [main, libFile, config];
const host = createServerHost(projectFiles);
const session = createSession(host, { canUseEvents: true });
@ -983,11 +988,6 @@ console.log(blabla);`
verifyWhileNpmInstall({ timeouts: 0, semantic: moduleNotFoundErr });
filesAndFoldersToAdd = [];
// Move things from staging to node_modules without triggering watch
const moduleFile: File = {
path: `${tscWatch.projectRoot}/node_modules/@angular/core/index.d.ts`,
content: `export const y = 10;`
};
host.ensureFileOrFolder(moduleFile, /*ignoreWatchInvokedWithTriggerAsFileCreate*/ true, /*ignoreParentWatch*/ true);
// Since we added/removed in .staging no timeout
verifyWhileNpmInstall({ timeouts: 0, semantic: moduleNotFoundErr });

View File

@ -3054,6 +3054,12 @@ declare namespace ts {
* @param emitCallback A callback used to emit the node.
*/
emitNodeWithNotification(hint: EmitHint, node: Node, emitCallback: (hint: EmitHint, node: Node) => void): void;
/**
* Indicates if a given node needs an emit notification
*
* @param node The node to emit.
*/
isEmitNotificationEnabled?(node: Node): boolean;
/**
* Clean up EmitNode entries on any parse-tree nodes.
*/
@ -3126,6 +3132,11 @@ declare namespace ts {
* ```
*/
onEmitNode?(hint: EmitHint, node: Node | undefined, emitCallback: (hint: EmitHint, node: Node | undefined) => void): void;
/**
* A hook used to check if an emit notification is required for a node.
* @param node The node to emit.
*/
isEmitNotificationEnabled?(node: Node | undefined): boolean;
/**
* A hook used by the Printer to perform just-in-time substitution of a node. This is
* primarily used by node transformations that need to substitute one node for another,

View File

@ -3054,6 +3054,12 @@ declare namespace ts {
* @param emitCallback A callback used to emit the node.
*/
emitNodeWithNotification(hint: EmitHint, node: Node, emitCallback: (hint: EmitHint, node: Node) => void): void;
/**
* Indicates if a given node needs an emit notification
*
* @param node The node to emit.
*/
isEmitNotificationEnabled?(node: Node): boolean;
/**
* Clean up EmitNode entries on any parse-tree nodes.
*/
@ -3126,6 +3132,11 @@ declare namespace ts {
* ```
*/
onEmitNode?(hint: EmitHint, node: Node | undefined, emitCallback: (hint: EmitHint, node: Node | undefined) => void): void;
/**
* A hook used to check if an emit notification is required for a node.
* @param node The node to emit.
*/
isEmitNotificationEnabled?(node: Node | undefined): boolean;
/**
* A hook used by the Printer to perform just-in-time substitution of a node. This is
* primarily used by node transformations that need to substitute one node for another,

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long