Add js-equivalent test for the binary expression stress and introduce trampoline into getJSSyntacticDiagnosticsForFile (#36724)

* Add js-equivalent test for the binary expression stress and introduce trampiline into getJSSyntacticDiagnosticsForFile

* Update src/compiler/parser.ts

Comment text update

Co-Authored-By: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com>

* Fix lint

Co-authored-by: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com>
This commit is contained in:
Wesley Wigham
2020-02-25 13:29:34 -08:00
committed by GitHub
parent 01f81dfc00
commit e536c89872
3 changed files with 5073 additions and 55 deletions

View File

@@ -522,6 +522,76 @@ namespace ts {
}
}
/** @internal */
/**
* Invokes a callback for each child of the given node. The 'cbNode' callback is invoked for all child nodes
* stored in properties. If a 'cbNodes' callback is specified, it is invoked for embedded arrays; additionally,
* unlike `forEachChild`, embedded arrays are flattened and the 'cbNode' callback is invoked for each element.
* If a callback returns a truthy value, iteration stops and that value is returned. Otherwise, undefined is returned.
*
* @param node a given node to visit its children
* @param cbNode a callback to be invoked for all child nodes
* @param cbNodes a callback to be invoked for embedded array
*
* @remarks Unlike `forEachChild`, `forEachChildRecursively` handles recursively invoking the traversal on each child node found,
* and while doing so, handles traversing the structure without relying on the callstack to encode the tree structure.
*/
export function forEachChildRecursively<T>(rootNode: Node, cbNode: (node: Node, parent: Node) => T | "skip" | undefined, cbNodes?: (nodes: NodeArray<Node>, parent: Node) => T | "skip" | undefined): T | undefined {
const stack: Node[] = [rootNode];
while (stack.length) {
const parent = stack.pop()!;
const res = visitAllPossibleChildren(parent, gatherPossibleChildren(parent));
if (res) {
return res;
}
}
return;
function gatherPossibleChildren(node: Node) {
const children: (Node | NodeArray<Node>)[] = [];
forEachChild(node, addWorkItem, addWorkItem); // By using a stack above and `unshift` here, we emulate a depth-first preorder traversal
return children;
function addWorkItem(n: Node | NodeArray<Node>) {
children.unshift(n);
}
}
function visitAllPossibleChildren(parent: Node, children: readonly (Node | NodeArray<Node>)[]) {
for (const child of children) {
if (isArray(child)) {
if (cbNodes) {
const res = cbNodes(child, parent);
if (res) {
if (res === "skip") continue;
return res;
}
}
for (let i = child.length - 1; i >= 0; i--) {
const realChild = child[i];
const res = cbNode(realChild, parent);
if (res) {
if (res === "skip") continue;
return res;
}
stack.push(realChild);
}
}
else {
stack.push(child);
const res = cbNode(child, parent);
if (res) {
if (res === "skip") continue;
return res;
}
}
}
}
}
export function createSourceFile(fileName: string, sourceText: string, languageVersion: ScriptTarget, setParentNodes = false, scriptKind?: ScriptKind): SourceFile {
performance.mark("beforeParse");
let result: SourceFile;
@@ -903,31 +973,14 @@ namespace ts {
// a syntax tree, and no semantic features, then the binding process is an unnecessary
// overhead. This functions allows us to set all the parents, without all the expense of
// binding.
forEachChildRecursively(rootNode, bindParentToChild);
const stack: Node[] = [rootNode];
while (stack.length) {
const parent = stack.pop()!;
bindParentToChildren(parent, gatherChildren(parent));
}
return;
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;
}
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);
}
function bindParentToChild(child: Node, parent: Node) {
child.parent = parent;
if (hasJSDocNodes(child)) {
for (const doc of child.jsDoc!) {
bindParentToChild(doc, child);
forEachChildRecursively(doc, bindParentToChild);
}
}
}

View File

@@ -1779,12 +1779,12 @@ namespace ts {
function getJSSyntacticDiagnosticsForFile(sourceFile: SourceFile): DiagnosticWithLocation[] {
return runWithCancellationToken(() => {
const diagnostics: DiagnosticWithLocation[] = [];
let parent: Node = sourceFile;
walk(sourceFile);
walk(sourceFile, sourceFile);
forEachChildRecursively(sourceFile, walk, walkArray);
return diagnostics;
function walk(node: Node) {
function walk(node: Node, parent: Node) {
// Return directly from the case if the given node doesnt want to visit each child
// Otherwise break to visit each child
@@ -1794,7 +1794,7 @@ namespace ts {
case SyntaxKind.MethodDeclaration:
if ((<ParameterDeclaration | PropertyDeclaration | MethodDeclaration>parent).questionToken === node) {
diagnostics.push(createDiagnosticForNode(node, Diagnostics.The_0_modifier_can_only_be_used_in_TypeScript_files, "?"));
return;
return "skip";
}
// falls through
case SyntaxKind.MethodSignature:
@@ -1808,7 +1808,7 @@ namespace ts {
// type annotation
if ((<FunctionLikeDeclaration | VariableDeclaration | ParameterDeclaration | PropertyDeclaration>parent).type === node) {
diagnostics.push(createDiagnosticForNode(node, Diagnostics.Type_annotations_can_only_be_used_in_TypeScript_files));
return;
return "skip";
}
}
@@ -1816,65 +1816,60 @@ namespace ts {
case SyntaxKind.ImportClause:
if ((node as ImportClause).isTypeOnly) {
diagnostics.push(createDiagnosticForNode(node.parent, Diagnostics._0_declarations_can_only_be_used_in_TypeScript_files, "import type"));
return;
return "skip";
}
break;
case SyntaxKind.ExportDeclaration:
if ((node as ExportDeclaration).isTypeOnly) {
diagnostics.push(createDiagnosticForNode(node, Diagnostics._0_declarations_can_only_be_used_in_TypeScript_files, "export type"));
return;
return "skip";
}
break;
case SyntaxKind.ImportEqualsDeclaration:
diagnostics.push(createDiagnosticForNode(node, Diagnostics.import_can_only_be_used_in_TypeScript_files));
return;
return "skip";
case SyntaxKind.ExportAssignment:
if ((<ExportAssignment>node).isExportEquals) {
diagnostics.push(createDiagnosticForNode(node, Diagnostics.export_can_only_be_used_in_TypeScript_files));
return;
return "skip";
}
break;
case SyntaxKind.HeritageClause:
const heritageClause = <HeritageClause>node;
if (heritageClause.token === SyntaxKind.ImplementsKeyword) {
diagnostics.push(createDiagnosticForNode(node, Diagnostics.implements_clauses_can_only_be_used_in_TypeScript_files));
return;
return "skip";
}
break;
case SyntaxKind.InterfaceDeclaration:
const interfaceKeyword = tokenToString(SyntaxKind.InterfaceKeyword);
Debug.assertIsDefined(interfaceKeyword);
diagnostics.push(createDiagnosticForNode(node, Diagnostics._0_declarations_can_only_be_used_in_TypeScript_files, interfaceKeyword));
return;
return "skip";
case SyntaxKind.ModuleDeclaration:
const moduleKeyword = node.flags & NodeFlags.Namespace ? tokenToString(SyntaxKind.NamespaceKeyword) : tokenToString(SyntaxKind.ModuleKeyword);
Debug.assertIsDefined(moduleKeyword);
diagnostics.push(createDiagnosticForNode(node, Diagnostics._0_declarations_can_only_be_used_in_TypeScript_files, moduleKeyword));
return;
return "skip";
case SyntaxKind.TypeAliasDeclaration:
diagnostics.push(createDiagnosticForNode(node, Diagnostics.Type_aliases_can_only_be_used_in_TypeScript_files));
return;
return "skip";
case SyntaxKind.EnumDeclaration:
const enumKeyword = Debug.checkDefined(tokenToString(SyntaxKind.EnumKeyword));
diagnostics.push(createDiagnosticForNode(node, Diagnostics._0_declarations_can_only_be_used_in_TypeScript_files, enumKeyword));
return;
return "skip";
case SyntaxKind.NonNullExpression:
diagnostics.push(createDiagnosticForNode(node, Diagnostics.Non_null_assertions_can_only_be_used_in_TypeScript_files));
return;
return "skip";
case SyntaxKind.AsExpression:
diagnostics.push(createDiagnosticForNode((node as AsExpression).type, Diagnostics.Type_assertion_expressions_can_only_be_used_in_TypeScript_files));
return;
return "skip";
case SyntaxKind.TypeAssertionExpression:
Debug.fail(); // Won't parse these in a JS file anyway, as they are interpreted as JSX.
}
const prevParent = parent;
parent = node;
forEachChild(node, walk, walkArray);
parent = prevParent;
}
function walkArray(nodes: NodeArray<Node>) {
function walkArray(nodes: NodeArray<Node>, parent: Node) {
if (parent.decorators === nodes && !options.experimentalDecorators) {
diagnostics.push(createDiagnosticForNode(parent, Diagnostics.Experimental_support_for_decorators_is_a_feature_that_is_subject_to_change_in_a_future_release_Set_the_experimentalDecorators_option_in_your_tsconfig_or_jsconfig_to_remove_this_warning));
}
@@ -1892,14 +1887,15 @@ namespace ts {
// Check type parameters
if (nodes === (<DeclarationWithTypeParameterChildren>parent).typeParameters) {
diagnostics.push(createDiagnosticForNodeArray(nodes, Diagnostics.Type_parameter_declarations_can_only_be_used_in_TypeScript_files));
return;
return "skip";
}
// falls through
case SyntaxKind.VariableStatement:
// Check modifiers
if (nodes === parent.modifiers) {
return checkModifiers(parent.modifiers, parent.kind === SyntaxKind.VariableStatement);
checkModifiers(parent.modifiers, parent.kind === SyntaxKind.VariableStatement);
return "skip";
}
break;
case SyntaxKind.PropertyDeclaration:
@@ -1910,14 +1906,14 @@ namespace ts {
diagnostics.push(createDiagnosticForNode(modifier, Diagnostics.The_0_modifier_can_only_be_used_in_TypeScript_files, tokenToString(modifier.kind)));
}
}
return;
return "skip";
}
break;
case SyntaxKind.Parameter:
// Check modifiers of parameter declaration
if (nodes === (<ParameterDeclaration>parent).modifiers) {
diagnostics.push(createDiagnosticForNodeArray(nodes, Diagnostics.Parameter_modifiers_can_only_be_used_in_TypeScript_files));
return;
return "skip";
}
break;
case SyntaxKind.CallExpression:
@@ -1929,14 +1925,10 @@ namespace ts {
// Check type arguments
if (nodes === (<NodeWithTypeArguments>parent).typeArguments) {
diagnostics.push(createDiagnosticForNodeArray(nodes, Diagnostics.Type_arguments_can_only_be_used_in_TypeScript_files));
return;
return "skip";
}
break;
}
for (const node of nodes) {
walk(node);
}
}
function checkModifiers(modifiers: NodeArray<Modifier>, isConstValid: boolean) {