Skip to content

Commit 6042706

Browse files
devversionAndrewKushnir
authored andcommitted
perf(migrations): speed up signal input migration by combining two analyze phases (#57318)
Instead of revisiting each source file, and each of its child nodes twice, we now visit them together using a grouped AST visitor that only traverses each source file once. This seemed to speed up migration by 6-8% locally, but is likely noticable better with large compilation scopes. PR Close #57318
1 parent 1b54697 commit 6042706

File tree

4 files changed

+82
-34
lines changed

4 files changed

+82
-34
lines changed

packages/core/schematics/migrations/signal-migration/src/passes/2_find_source_file_references.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {identifyTemplateReferences} from './references/identify_template_referen
1919
import {identifyPotentialTypeScriptReference} from './references/identify_ts_references';
2020
import {PartialDirectiveTypeInCatalystTests} from '../pattern_advisors/partial_directive_type';
2121
import {InputReferenceKind} from '../utils/input_reference';
22+
import {GroupedTsAstVisitor} from '../utils/grouped_ts_ast_visitor';
2223

2324
/**
2425
* Phase where we iterate through all source file references and
@@ -34,11 +35,11 @@ import {InputReferenceKind} from '../utils/input_reference';
3435
* - Host binding expressions.
3536
*/
3637
export function pass2_IdentifySourceFileReferences(
37-
sf: ts.SourceFile,
3838
host: MigrationHost,
3939
checker: ts.TypeChecker,
4040
reflector: ReflectionHost,
4141
templateTypeChecker: TemplateTypeChecker,
42+
groupedTsAstVisitor: GroupedTsAstVisitor,
4243
knownInputs: KnownInputs,
4344
result: MigrationResult,
4445
) {
@@ -86,8 +87,7 @@ export function pass2_IdentifySourceFileReferences(
8687
target: partialDirectiveInCatalyst.targetClass,
8788
});
8889
}
89-
90-
ts.forEachChild(node, visitor);
9190
};
92-
ts.forEachChild(sf, visitor);
91+
92+
groupedTsAstVisitor.register(visitor);
9393
}

packages/core/schematics/migrations/signal-migration/src/passes/3_check_incompatible_patterns.ts

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {getMemberName} from '../utils/class_member_names';
1515
import {InheritanceGraph} from '../utils/inheritance_graph';
1616
import {SpyOnInputPattern} from '../pattern_advisors/spy_on_pattern';
1717
import {MigrationHost} from '../migration_host';
18+
import {GroupedTsAstVisitor} from '../utils/grouped_ts_ast_visitor';
1819

1920
/**
2021
* Phase where problematic patterns are detected and advise
@@ -28,9 +29,9 @@ import {MigrationHost} from '../migration_host';
2829
*/
2930
export function pass3__checkIncompatiblePatterns(
3031
host: MigrationHost,
31-
files: readonly ts.SourceFile[],
3232
inheritanceGraph: InheritanceGraph,
3333
checker: ts.TypeChecker,
34+
groupedTsAstVisitor: GroupedTsAstVisitor,
3435
knownInputs: KnownInputs,
3536
) {
3637
const inputClassSymbolsToClass = new Map<ts.Symbol, ts.ClassDeclaration>();
@@ -51,7 +52,6 @@ export function pass3__checkIncompatiblePatterns(
5152

5253
const incompatibilityPatterns = [new SpyOnInputPattern(host, checker, knownInputs)];
5354

54-
let insidePropertyDeclaration: ts.PropertyDeclaration | null = null;
5555
const visitor = (node: ts.Node) => {
5656
// Check for manual class instantiations.
5757
if (ts.isNewExpression(node) && ts.isIdentifier(unwrapExpression(node.expression))) {
@@ -71,6 +71,7 @@ export function pass3__checkIncompatiblePatterns(
7171
// Detect possible problematic patterns.
7272
incompatibilityPatterns.forEach((p) => p.detect(node));
7373

74+
const insidePropertyDeclaration = groupedTsAstVisitor.state.insidePropertyDeclaration;
7475
// Check for problematic class references inside property declarations.
7576
// These are likely problematic, causing type conflicts, if the containing
7677
// class inherits a non-input member with the same name.
@@ -109,16 +110,7 @@ export function pass3__checkIncompatiblePatterns(
109110
);
110111
}
111112
}
112-
113-
if (ts.isPropertyDeclaration(node)) {
114-
const oldPropertyDeclaration = insidePropertyDeclaration;
115-
insidePropertyDeclaration = node;
116-
ts.forEachChild(node, visitor);
117-
insidePropertyDeclaration = oldPropertyDeclaration;
118-
} else {
119-
ts.forEachChild(node, visitor);
120-
}
121113
};
122114

123-
files.forEach((f) => ts.forEachChild(f, visitor));
115+
groupedTsAstVisitor.register(visitor);
124116
}

packages/core/schematics/migrations/signal-migration/src/phase_analysis.ts

Lines changed: 25 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {pass3__checkIncompatiblePatterns} from './passes/3_check_incompatible_pa
1515
import {pass2_IdentifySourceFileReferences} from './passes/2_find_source_file_references';
1616
import {MigrationResult} from './result';
1717
import {InheritanceGraph} from './utils/inheritance_graph';
18+
import {GroupedTsAstVisitor} from './utils/grouped_ts_ast_visitor';
1819

1920
/**
2021
* Executes the analysis phase of the migration.
@@ -59,26 +60,32 @@ export function executeAnalysisPhase(
5960
),
6061
);
6162

62-
// Pass 2
63-
sourceFiles.forEach((sf) =>
64-
pass2_IdentifySourceFileReferences(
65-
sf,
66-
host,
67-
typeChecker,
68-
reflector,
69-
templateTypeChecker,
70-
knownInputs,
71-
result,
72-
),
73-
);
74-
75-
// Source files is fine. We will resolve into declaration files if a source
76-
// file depends on such.
63+
// A graph starting with source files is sufficient. We will resolve into
64+
// declaration files if a source file depends on such.
7765
const inheritanceGraph = new InheritanceGraph(typeChecker).expensivePopulate(sourceFiles);
66+
const pass2And3SourceFileVisitor = new GroupedTsAstVisitor(sourceFiles);
67+
68+
// Register pass 2. Find all source file references.
69+
pass2_IdentifySourceFileReferences(
70+
host,
71+
typeChecker,
72+
reflector,
73+
templateTypeChecker,
74+
pass2And3SourceFileVisitor,
75+
knownInputs,
76+
result,
77+
);
78+
// Register pass 3. Check incompatible patterns pass.
79+
pass3__checkIncompatiblePatterns(
80+
host,
81+
inheritanceGraph,
82+
typeChecker,
83+
pass2And3SourceFileVisitor,
84+
knownInputs,
85+
);
7886

79-
// Check pass
80-
// TODO: Combine with source file iterations above for speed up??
81-
pass3__checkIncompatiblePatterns(host, sourceFiles, inheritanceGraph, typeChecker, knownInputs);
87+
// Perform Pass 2 and Pass 3, efficiently in one pass.
88+
pass2And3SourceFileVisitor.execute();
8289

8390
return {inheritanceGraph};
8491
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
import ts from 'typescript';
10+
11+
/**
12+
* Class that allows for efficient grouping of TypeScript node AST
13+
* traversal.
14+
*
15+
* Allows visitors to execute in a single pass when visiting all
16+
* children of source files.
17+
*/
18+
export class GroupedTsAstVisitor {
19+
private visitors: Array<(node: ts.Node) => void> = [];
20+
21+
constructor(private files: readonly ts.SourceFile[]) {}
22+
23+
state = {
24+
insidePropertyDeclaration: null as ts.PropertyDeclaration | null,
25+
};
26+
27+
register(visitor: (node: ts.Node) => void) {
28+
this.visitors.push(visitor);
29+
}
30+
31+
execute() {
32+
const visitor = (node: ts.Node) => {
33+
for (const v of this.visitors) {
34+
v(node);
35+
}
36+
if (ts.isPropertyDeclaration(node)) {
37+
this.state.insidePropertyDeclaration = node;
38+
ts.forEachChild(node, visitor);
39+
this.state.insidePropertyDeclaration = null;
40+
} else {
41+
ts.forEachChild(node, visitor);
42+
}
43+
};
44+
45+
for (const file of this.files) {
46+
ts.forEachChild(file, visitor);
47+
}
48+
}
49+
}

0 commit comments

Comments
 (0)