Skip to content

Commit e536c89

Browse files
weswighamsandersn
andauthored
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 <[email protected]> * Fix lint Co-authored-by: Nathan Shively-Sanders <[email protected]>
1 parent 01f81df commit e536c89

File tree

3 files changed

+5074
-56
lines changed

3 files changed

+5074
-56
lines changed

src/compiler/parser.ts

Lines changed: 78 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -522,6 +522,76 @@ namespace ts {
522522
}
523523
}
524524

525+
/** @internal */
526+
/**
527+
* Invokes a callback for each child of the given node. The 'cbNode' callback is invoked for all child nodes
528+
* stored in properties. If a 'cbNodes' callback is specified, it is invoked for embedded arrays; additionally,
529+
* unlike `forEachChild`, embedded arrays are flattened and the 'cbNode' callback is invoked for each element.
530+
* If a callback returns a truthy value, iteration stops and that value is returned. Otherwise, undefined is returned.
531+
*
532+
* @param node a given node to visit its children
533+
* @param cbNode a callback to be invoked for all child nodes
534+
* @param cbNodes a callback to be invoked for embedded array
535+
*
536+
* @remarks Unlike `forEachChild`, `forEachChildRecursively` handles recursively invoking the traversal on each child node found,
537+
* and while doing so, handles traversing the structure without relying on the callstack to encode the tree structure.
538+
*/
539+
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 {
540+
541+
const stack: Node[] = [rootNode];
542+
while (stack.length) {
543+
const parent = stack.pop()!;
544+
const res = visitAllPossibleChildren(parent, gatherPossibleChildren(parent));
545+
if (res) {
546+
return res;
547+
}
548+
}
549+
550+
return;
551+
552+
function gatherPossibleChildren(node: Node) {
553+
const children: (Node | NodeArray<Node>)[] = [];
554+
forEachChild(node, addWorkItem, addWorkItem); // By using a stack above and `unshift` here, we emulate a depth-first preorder traversal
555+
return children;
556+
557+
function addWorkItem(n: Node | NodeArray<Node>) {
558+
children.unshift(n);
559+
}
560+
}
561+
562+
function visitAllPossibleChildren(parent: Node, children: readonly (Node | NodeArray<Node>)[]) {
563+
for (const child of children) {
564+
if (isArray(child)) {
565+
if (cbNodes) {
566+
const res = cbNodes(child, parent);
567+
if (res) {
568+
if (res === "skip") continue;
569+
return res;
570+
}
571+
}
572+
573+
for (let i = child.length - 1; i >= 0; i--) {
574+
const realChild = child[i];
575+
const res = cbNode(realChild, parent);
576+
if (res) {
577+
if (res === "skip") continue;
578+
return res;
579+
}
580+
stack.push(realChild);
581+
}
582+
}
583+
else {
584+
stack.push(child);
585+
const res = cbNode(child, parent);
586+
if (res) {
587+
if (res === "skip") continue;
588+
return res;
589+
}
590+
}
591+
}
592+
}
593+
}
594+
525595
export function createSourceFile(fileName: string, sourceText: string, languageVersion: ScriptTarget, setParentNodes = false, scriptKind?: ScriptKind): SourceFile {
526596
performance.mark("beforeParse");
527597
let result: SourceFile;
@@ -903,31 +973,14 @@ namespace ts {
903973
// a syntax tree, and no semantic features, then the binding process is an unnecessary
904974
// overhead. This functions allows us to set all the parents, without all the expense of
905975
// binding.
906-
907-
const stack: Node[] = [rootNode];
908-
while (stack.length) {
909-
const parent = stack.pop()!;
910-
bindParentToChildren(parent, gatherChildren(parent));
911-
}
912-
913-
return;
914-
915-
function gatherChildren(node: Node) {
916-
const children: Node[] = [];
917-
forEachChild(node, n => { children.unshift(n); }); // By using a stack above and `unshift` here, we emulate a depth-first preorder traversal
918-
return children;
919-
}
920-
921-
function bindParentToChildren(parent: Node, children: readonly Node[]) {
922-
for (const child of children) {
923-
if (child.parent === parent) continue; // already bound, assume subtree is bound
924-
child.parent = parent;
925-
stack.push(child);
926-
if (hasJSDocNodes(child)) {
927-
for (const jsDoc of child.jsDoc!) {
928-
jsDoc.parent = child;
929-
stack.push(jsDoc);
930-
}
976+
forEachChildRecursively(rootNode, bindParentToChild);
977+
978+
function bindParentToChild(child: Node, parent: Node) {
979+
child.parent = parent;
980+
if (hasJSDocNodes(child)) {
981+
for (const doc of child.jsDoc!) {
982+
bindParentToChild(doc, child);
983+
forEachChildRecursively(doc, bindParentToChild);
931984
}
932985
}
933986
}

src/compiler/program.ts

Lines changed: 23 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1779,12 +1779,12 @@ namespace ts {
17791779
function getJSSyntacticDiagnosticsForFile(sourceFile: SourceFile): DiagnosticWithLocation[] {
17801780
return runWithCancellationToken(() => {
17811781
const diagnostics: DiagnosticWithLocation[] = [];
1782-
let parent: Node = sourceFile;
1783-
walk(sourceFile);
1782+
walk(sourceFile, sourceFile);
1783+
forEachChildRecursively(sourceFile, walk, walkArray);
17841784

17851785
return diagnostics;
17861786

1787-
function walk(node: Node) {
1787+
function walk(node: Node, parent: Node) {
17881788
// Return directly from the case if the given node doesnt want to visit each child
17891789
// Otherwise break to visit each child
17901790

@@ -1794,7 +1794,7 @@ namespace ts {
17941794
case SyntaxKind.MethodDeclaration:
17951795
if ((<ParameterDeclaration | PropertyDeclaration | MethodDeclaration>parent).questionToken === node) {
17961796
diagnostics.push(createDiagnosticForNode(node, Diagnostics.The_0_modifier_can_only_be_used_in_TypeScript_files, "?"));
1797-
return;
1797+
return "skip";
17981798
}
17991799
// falls through
18001800
case SyntaxKind.MethodSignature:
@@ -1808,73 +1808,68 @@ namespace ts {
18081808
// type annotation
18091809
if ((<FunctionLikeDeclaration | VariableDeclaration | ParameterDeclaration | PropertyDeclaration>parent).type === node) {
18101810
diagnostics.push(createDiagnosticForNode(node, Diagnostics.Type_annotations_can_only_be_used_in_TypeScript_files));
1811-
return;
1811+
return "skip";
18121812
}
18131813
}
18141814

18151815
switch (node.kind) {
18161816
case SyntaxKind.ImportClause:
18171817
if ((node as ImportClause).isTypeOnly) {
18181818
diagnostics.push(createDiagnosticForNode(node.parent, Diagnostics._0_declarations_can_only_be_used_in_TypeScript_files, "import type"));
1819-
return;
1819+
return "skip";
18201820
}
18211821
break;
18221822
case SyntaxKind.ExportDeclaration:
18231823
if ((node as ExportDeclaration).isTypeOnly) {
18241824
diagnostics.push(createDiagnosticForNode(node, Diagnostics._0_declarations_can_only_be_used_in_TypeScript_files, "export type"));
1825-
return;
1825+
return "skip";
18261826
}
18271827
break;
18281828
case SyntaxKind.ImportEqualsDeclaration:
18291829
diagnostics.push(createDiagnosticForNode(node, Diagnostics.import_can_only_be_used_in_TypeScript_files));
1830-
return;
1830+
return "skip";
18311831
case SyntaxKind.ExportAssignment:
18321832
if ((<ExportAssignment>node).isExportEquals) {
18331833
diagnostics.push(createDiagnosticForNode(node, Diagnostics.export_can_only_be_used_in_TypeScript_files));
1834-
return;
1834+
return "skip";
18351835
}
18361836
break;
18371837
case SyntaxKind.HeritageClause:
18381838
const heritageClause = <HeritageClause>node;
18391839
if (heritageClause.token === SyntaxKind.ImplementsKeyword) {
18401840
diagnostics.push(createDiagnosticForNode(node, Diagnostics.implements_clauses_can_only_be_used_in_TypeScript_files));
1841-
return;
1841+
return "skip";
18421842
}
18431843
break;
18441844
case SyntaxKind.InterfaceDeclaration:
18451845
const interfaceKeyword = tokenToString(SyntaxKind.InterfaceKeyword);
18461846
Debug.assertIsDefined(interfaceKeyword);
18471847
diagnostics.push(createDiagnosticForNode(node, Diagnostics._0_declarations_can_only_be_used_in_TypeScript_files, interfaceKeyword));
1848-
return;
1848+
return "skip";
18491849
case SyntaxKind.ModuleDeclaration:
18501850
const moduleKeyword = node.flags & NodeFlags.Namespace ? tokenToString(SyntaxKind.NamespaceKeyword) : tokenToString(SyntaxKind.ModuleKeyword);
18511851
Debug.assertIsDefined(moduleKeyword);
18521852
diagnostics.push(createDiagnosticForNode(node, Diagnostics._0_declarations_can_only_be_used_in_TypeScript_files, moduleKeyword));
1853-
return;
1853+
return "skip";
18541854
case SyntaxKind.TypeAliasDeclaration:
18551855
diagnostics.push(createDiagnosticForNode(node, Diagnostics.Type_aliases_can_only_be_used_in_TypeScript_files));
1856-
return;
1856+
return "skip";
18571857
case SyntaxKind.EnumDeclaration:
18581858
const enumKeyword = Debug.checkDefined(tokenToString(SyntaxKind.EnumKeyword));
18591859
diagnostics.push(createDiagnosticForNode(node, Diagnostics._0_declarations_can_only_be_used_in_TypeScript_files, enumKeyword));
1860-
return;
1860+
return "skip";
18611861
case SyntaxKind.NonNullExpression:
18621862
diagnostics.push(createDiagnosticForNode(node, Diagnostics.Non_null_assertions_can_only_be_used_in_TypeScript_files));
1863-
return;
1863+
return "skip";
18641864
case SyntaxKind.AsExpression:
18651865
diagnostics.push(createDiagnosticForNode((node as AsExpression).type, Diagnostics.Type_assertion_expressions_can_only_be_used_in_TypeScript_files));
1866-
return;
1866+
return "skip";
18671867
case SyntaxKind.TypeAssertionExpression:
18681868
Debug.fail(); // Won't parse these in a JS file anyway, as they are interpreted as JSX.
18691869
}
1870-
1871-
const prevParent = parent;
1872-
parent = node;
1873-
forEachChild(node, walk, walkArray);
1874-
parent = prevParent;
18751870
}
18761871

1877-
function walkArray(nodes: NodeArray<Node>) {
1872+
function walkArray(nodes: NodeArray<Node>, parent: Node) {
18781873
if (parent.decorators === nodes && !options.experimentalDecorators) {
18791874
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));
18801875
}
@@ -1892,14 +1887,15 @@ namespace ts {
18921887
// Check type parameters
18931888
if (nodes === (<DeclarationWithTypeParameterChildren>parent).typeParameters) {
18941889
diagnostics.push(createDiagnosticForNodeArray(nodes, Diagnostics.Type_parameter_declarations_can_only_be_used_in_TypeScript_files));
1895-
return;
1890+
return "skip";
18961891
}
18971892
// falls through
18981893

18991894
case SyntaxKind.VariableStatement:
19001895
// Check modifiers
19011896
if (nodes === parent.modifiers) {
1902-
return checkModifiers(parent.modifiers, parent.kind === SyntaxKind.VariableStatement);
1897+
checkModifiers(parent.modifiers, parent.kind === SyntaxKind.VariableStatement);
1898+
return "skip";
19031899
}
19041900
break;
19051901
case SyntaxKind.PropertyDeclaration:
@@ -1910,14 +1906,14 @@ namespace ts {
19101906
diagnostics.push(createDiagnosticForNode(modifier, Diagnostics.The_0_modifier_can_only_be_used_in_TypeScript_files, tokenToString(modifier.kind)));
19111907
}
19121908
}
1913-
return;
1909+
return "skip";
19141910
}
19151911
break;
19161912
case SyntaxKind.Parameter:
19171913
// Check modifiers of parameter declaration
19181914
if (nodes === (<ParameterDeclaration>parent).modifiers) {
19191915
diagnostics.push(createDiagnosticForNodeArray(nodes, Diagnostics.Parameter_modifiers_can_only_be_used_in_TypeScript_files));
1920-
return;
1916+
return "skip";
19211917
}
19221918
break;
19231919
case SyntaxKind.CallExpression:
@@ -1929,14 +1925,10 @@ namespace ts {
19291925
// Check type arguments
19301926
if (nodes === (<NodeWithTypeArguments>parent).typeArguments) {
19311927
diagnostics.push(createDiagnosticForNodeArray(nodes, Diagnostics.Type_arguments_can_only_be_used_in_TypeScript_files));
1932-
return;
1928+
return "skip";
19331929
}
19341930
break;
19351931
}
1936-
1937-
for (const node of nodes) {
1938-
walk(node);
1939-
}
19401932
}
19411933

19421934
function checkModifiers(modifiers: NodeArray<Modifier>, isConstValid: boolean) {

0 commit comments

Comments
 (0)