@@ -35,6 +35,7 @@ import {
35
35
isHostBindingReference ,
36
36
isTemplateReference ,
37
37
isTsReference ,
38
+ Reference ,
38
39
} from '../signal-migration/src/passes/reference_resolution/reference_kinds' ;
39
40
import { ReferenceResult } from '../signal-migration/src/passes/reference_resolution/reference_result' ;
40
41
import { GroupedTsAstVisitor } from '../signal-migration/src/utils/grouped_ts_ast_visitor' ;
@@ -62,11 +63,17 @@ export interface CompilationUnitData {
62
63
// potential problematic "class fields", but noting for later that those only would be
63
64
// problematic if they end up being multi-result queries.
64
65
potentialProblematicReferenceForMultiQueries : Record < ClassFieldUniqueKey , true > ;
66
+
67
+ // NOTE: Not serializable — ONLY works when we know it's not running in batch mode!
68
+ reusableAnalysisReferences : Reference < ClassFieldDescriptor > [ ] | null ;
65
69
}
66
70
67
71
export interface GlobalUnitData {
68
72
knownQueryFields : Record < ClassFieldUniqueKey , { fieldName : string ; isMulti : boolean } > ;
69
73
problematicQueries : Record < ClassFieldUniqueKey , true > ;
74
+
75
+ // NOTE: Not serializable — ONLY works when we know it's not running in batch mode!
76
+ reusableAnalysisReferences : Reference < ClassFieldDescriptor > [ ] | null ;
70
77
}
71
78
72
79
export class SignalQueriesMigration extends TsurgeComplexMigration <
@@ -79,8 +86,6 @@ export class SignalQueriesMigration extends TsurgeComplexMigration<
79
86
80
87
override async analyze ( info : ProgramInfo ) : Promise < Serializable < CompilationUnitData > > {
81
88
assert ( info . ngCompiler !== null , 'Expected queries migration to have an Angular program.' ) ;
82
- // TODO: This stage for this migration doesn't necessarily need a full
83
- // compilation unit program.
84
89
85
90
// Pre-Analyze the program and get access to the template type checker.
86
91
const { templateTypeChecker} = info . ngCompiler [ 'ensureAnalyzed' ] ( ) ;
@@ -93,13 +98,32 @@ export class SignalQueriesMigration extends TsurgeComplexMigration<
93
98
knownQueryFields : { } ,
94
99
potentialProblematicQueries : { } ,
95
100
potentialProblematicReferenceForMultiQueries : { } ,
101
+ reusableAnalysisReferences : null ,
96
102
} ;
97
103
const groupedAstVisitor = new GroupedTsAstVisitor ( sourceFiles ) ;
98
104
const referenceResult : ReferenceResult < ClassFieldDescriptor > = { references : [ ] } ;
105
+ const classesWithFilteredQueries = new WeakSet < ts . ClassLikeDeclaration > ( ) ;
106
+ const filteredQueriesForCompilationUnit = new Map < ClassFieldUniqueKey , { fieldName : string } > ( ) ;
99
107
100
108
const findQueryDefinitionsVisitor = ( node : ts . Node ) => {
101
109
const extractedQuery = extractSourceQueryDefinition ( node , reflector , evaluator , info ) ;
102
110
if ( extractedQuery !== null ) {
111
+ const descriptor = {
112
+ key : extractedQuery . id ,
113
+ node : extractedQuery . node ,
114
+ } ;
115
+ const containingFile = projectFile ( descriptor . node . getSourceFile ( ) , info ) ;
116
+
117
+ if (
118
+ this . config . shouldMigrateQuery === undefined ||
119
+ this . config . shouldMigrateQuery ( descriptor , containingFile )
120
+ ) {
121
+ classesWithFilteredQueries . add ( extractedQuery . node . parent ) ;
122
+ filteredQueriesForCompilationUnit . set ( extractedQuery . id , {
123
+ fieldName : extractedQuery . queryInfo . propertyName ,
124
+ } ) ;
125
+ }
126
+
103
127
res . knownQueryFields [ extractedQuery . id ] = {
104
128
fieldName : extractedQuery . queryInfo . propertyName ,
105
129
isMulti : extractedQuery . queryInfo . first === false ,
@@ -108,6 +132,16 @@ export class SignalQueriesMigration extends TsurgeComplexMigration<
108
132
} ;
109
133
110
134
groupedAstVisitor . register ( findQueryDefinitionsVisitor ) ;
135
+ if ( this . config . assumeNonBatch ) {
136
+ // In non-batch, we need to find queries before, so we can perform
137
+ // improved reference resolution.
138
+ this . config . reportProgressFn ?.( 20 , 'Scanning for queries..' ) ;
139
+ groupedAstVisitor . execute ( ) ;
140
+ this . config . reportProgressFn ?.( 30 , 'Scanning for references..' ) ;
141
+ } else {
142
+ this . config . reportProgressFn ?.( 20 , 'Scanning for queries and references..' ) ;
143
+ }
144
+
111
145
groupedAstVisitor . register (
112
146
createFindAllSourceFileReferencesVisitor (
113
147
info ,
@@ -116,21 +150,44 @@ export class SignalQueriesMigration extends TsurgeComplexMigration<
116
150
info . ngCompiler [ 'resourceManager' ] ,
117
151
evaluator ,
118
152
templateTypeChecker ,
119
- // Eager, rather expensive tracking of all references.
120
- // We don't know yet if something refers to a different query or not, so we
121
- // eagerly detect such and later filter those problematic references that
122
- // turned out to refer to queries.
123
- // TODO: Consider skipping this extra work when running in non-batch mode.
124
- // TODO: Also consider skipping if we know this query cannot be part.
125
153
{
126
- shouldTrackClassReference : ( _class ) => false ,
127
- attemptRetrieveDescriptorFromSymbol : ( s ) => getClassFieldDescriptorForSymbol ( s , info ) ,
154
+ // Note: We don't support cross-target migration of `Partial<T>` usages.
155
+ // This is an acceptable limitation for performance reasons.
156
+ shouldTrackClassReference : ( node ) => classesWithFilteredQueries . has ( node ) ,
157
+ attemptRetrieveDescriptorFromSymbol : ( s ) => {
158
+ const descriptor = getClassFieldDescriptorForSymbol ( s , info ) ;
159
+
160
+ // If we are executing in upgraded analysis phase mode, we know all
161
+ // of the queries since there aren't any other compilation units.
162
+ // Ignore references to non-query class fields.
163
+ if (
164
+ this . config . assumeNonBatch &&
165
+ descriptor !== null &&
166
+ ! filteredQueriesForCompilationUnit . has ( descriptor . key )
167
+ ) {
168
+ return null ;
169
+ }
170
+
171
+ // TODO: Also consider skipping if we know this cannot be a query.
172
+ // e.g. missing class decorators or some other checks.
173
+
174
+ // In batch mode, we eagerly, rather expensively, track all references.
175
+ // We don't know yet if something refers to a different query or not, so we
176
+ // eagerly detect such and later filter those problematic references that
177
+ // turned out to refer to queries (once we have the global metadata).
178
+
179
+ return descriptor ;
180
+ } ,
128
181
} ,
129
- null ,
182
+ // In non-batch mode, we know what inputs exist and can optimize the reference
183
+ // resolution significantly (for e.g. VSCode integration)— as we know what
184
+ // field names may be used to reference potential queries.
185
+ this . config . assumeNonBatch
186
+ ? new Set ( Array . from ( filteredQueriesForCompilationUnit . values ( ) ) . map ( ( f ) => f . fieldName ) )
187
+ : null ,
130
188
referenceResult ,
131
189
) . visitor ,
132
190
) ;
133
-
134
191
groupedAstVisitor . execute ( ) ;
135
192
136
193
// Determine incompatible queries based on problematic references
@@ -152,13 +209,18 @@ export class SignalQueriesMigration extends TsurgeComplexMigration<
152
209
checkForIncompatibleQueryListAccesses ( ref , res ) ;
153
210
}
154
211
212
+ if ( this . config . assumeNonBatch ) {
213
+ res . reusableAnalysisReferences = referenceResult . references ;
214
+ }
215
+
155
216
return confirmAsSerializable ( res ) ;
156
217
}
157
218
158
219
override async merge ( units : CompilationUnitData [ ] ) : Promise < Serializable < GlobalUnitData > > {
159
220
const merged : GlobalUnitData = {
160
221
knownQueryFields : { } ,
161
222
problematicQueries : { } ,
223
+ reusableAnalysisReferences : null ,
162
224
} ;
163
225
for ( const unit of units ) {
164
226
for ( const [ id , value ] of Object . entries ( unit . knownQueryFields ) ) {
@@ -167,6 +229,10 @@ export class SignalQueriesMigration extends TsurgeComplexMigration<
167
229
for ( const id of Object . keys ( unit . potentialProblematicQueries ) ) {
168
230
merged . problematicQueries [ id as ClassFieldUniqueKey ] = true ;
169
231
}
232
+ if ( unit . reusableAnalysisReferences !== null ) {
233
+ assert ( units . length === 1 , 'Expected migration to not run in batch mode' ) ;
234
+ merged . reusableAnalysisReferences = unit . reusableAnalysisReferences ;
235
+ }
170
236
}
171
237
172
238
for ( const unit of units ) {
@@ -184,7 +250,7 @@ export class SignalQueriesMigration extends TsurgeComplexMigration<
184
250
assert ( info . ngCompiler !== null , 'Expected queries migration to have an Angular program.' ) ;
185
251
186
252
// Pre-Analyze the program and get access to the template type checker.
187
- const { templateTypeChecker, metaReader} = await info . ngCompiler [ 'ensureAnalyzed' ] ( ) ;
253
+ const { templateTypeChecker, metaReader} = info . ngCompiler [ 'ensureAnalyzed' ] ( ) ;
188
254
const { program, sourceFiles} = info ;
189
255
const checker = program . getTypeChecker ( ) ;
190
256
const reflector = new TypeScriptReflectionHost ( checker ) ;
@@ -193,9 +259,9 @@ export class SignalQueriesMigration extends TsurgeComplexMigration<
193
259
const importManager = new ImportManager ( ) ;
194
260
const printer = ts . createPrinter ( ) ;
195
261
196
- const filesWithMigratedQueries = new Map < ts . SourceFile , Set < QueryFunctionName > > ( ) ;
262
+ const filesWithSourceQueries = new Map < ts . SourceFile , Set < QueryFunctionName > > ( ) ;
197
263
const filesWithIncompleteMigration = new Map < ts . SourceFile , Set < QueryFunctionName > > ( ) ;
198
- const filesWithUnrelatedQueryListImports = new WeakSet < ts . SourceFile > ( ) ;
264
+ const filesWithQueryListOutsideOfDeclarations = new WeakSet < ts . SourceFile > ( ) ;
199
265
200
266
const knownQueries = new KnownQueries ( info , globalMetadata ) ;
201
267
const referenceResult : ReferenceResult < ClassFieldDescriptor > = { references : [ ] } ;
@@ -236,12 +302,14 @@ export class SignalQueriesMigration extends TsurgeComplexMigration<
236
302
node . text === 'QueryList' &&
237
303
ts . findAncestor ( node , ts . isImportDeclaration ) === undefined
238
304
) {
239
- filesWithUnrelatedQueryListImports . add ( node . getSourceFile ( ) ) ;
305
+ filesWithQueryListOutsideOfDeclarations . add ( node . getSourceFile ( ) ) ;
240
306
}
241
307
242
308
ts . forEachChild ( node , queryWholeProgramVisitor ) ;
243
309
} ;
244
310
311
+ this . config . reportProgressFn ?.( 40 , 'Tracking query declarations..' ) ;
312
+
245
313
for ( const sf of info . fullProgramSourceFiles ) {
246
314
ts . forEachChild ( sf , queryWholeProgramVisitor ) ;
247
315
}
@@ -254,43 +322,56 @@ export class SignalQueriesMigration extends TsurgeComplexMigration<
254
322
255
323
// Find all references.
256
324
const groupedAstVisitor = new GroupedTsAstVisitor ( sourceFiles ) ;
257
- groupedAstVisitor . register (
258
- createFindAllSourceFileReferencesVisitor (
259
- info ,
260
- checker ,
261
- reflector ,
262
- info . ngCompiler [ 'resourceManager' ] ,
263
- evaluator ,
264
- templateTypeChecker ,
265
- knownQueries ,
266
- fieldNamesToConsiderForReferenceLookup ,
267
- referenceResult ,
268
- ) . visitor ,
269
- ) ;
325
+
326
+ // Re-use previous reference result if available, instead of
327
+ // looking for references which is quite expensive.
328
+ if ( globalMetadata . reusableAnalysisReferences !== null ) {
329
+ referenceResult . references = globalMetadata . reusableAnalysisReferences ;
330
+ } else {
331
+ groupedAstVisitor . register (
332
+ createFindAllSourceFileReferencesVisitor (
333
+ info ,
334
+ checker ,
335
+ reflector ,
336
+ info . ngCompiler [ 'resourceManager' ] ,
337
+ evaluator ,
338
+ templateTypeChecker ,
339
+ knownQueries ,
340
+ fieldNamesToConsiderForReferenceLookup ,
341
+ referenceResult ,
342
+ ) . visitor ,
343
+ ) ;
344
+ }
270
345
271
346
const inheritanceGraph = new InheritanceGraph ( checker ) . expensivePopulate ( info . sourceFiles ) ;
272
347
checkIncompatiblePatterns ( inheritanceGraph , checker , groupedAstVisitor , knownQueries , ( ) =>
273
348
knownQueries . getAllClassesWithQueries ( ) ,
274
349
) ;
350
+
351
+ this . config . reportProgressFn ?.( 60 , 'Checking for problematic patterns..' ) ;
275
352
groupedAstVisitor . execute ( ) ;
276
353
277
354
// Check inheritance.
355
+ this . config . reportProgressFn ?.( 70 , 'Checking for inheritance patterns..' ) ;
278
356
checkInheritanceOfKnownFields ( inheritanceGraph , metaReader , knownQueries , {
279
357
getFieldsForClass : ( n ) => knownQueries . getQueryFieldsOfClass ( n ) ?? [ ] ,
280
358
isClassWithKnownFields : ( clazz ) => knownQueries . getQueryFieldsOfClass ( clazz ) !== undefined ,
281
359
} ) ;
282
360
361
+ this . config . reportProgressFn ?.( 80 , 'Migrating queries..' ) ;
362
+
283
363
// Migrate declarations.
284
364
for ( const extractedQuery of sourceQueries ) {
285
365
const node = extractedQuery . node ;
286
366
const sf = node . getSourceFile ( ) ;
287
367
const descriptor = { key : extractedQuery . id , node : extractedQuery . node } ;
288
368
289
369
if ( ! isMigratedQuery ( descriptor ) ) {
370
+ updateFileState ( filesWithSourceQueries , sf , extractedQuery . kind ) ;
290
371
updateFileState ( filesWithIncompleteMigration , sf , extractedQuery . kind ) ;
291
372
continue ;
292
373
}
293
- updateFileState ( filesWithMigratedQueries , sf , extractedQuery . kind ) ;
374
+ updateFileState ( filesWithSourceQueries , sf , extractedQuery . kind ) ;
294
375
295
376
replacements . push (
296
377
...computeReplacementsToMigrateQuery (
@@ -329,7 +410,7 @@ export class SignalQueriesMigration extends TsurgeComplexMigration<
329
410
}
330
411
331
412
// Remove imports if possible.
332
- for ( const [ file , types ] of filesWithMigratedQueries ) {
413
+ for ( const [ file , types ] of filesWithSourceQueries ) {
333
414
let seenIncompatibleMultiQuery = false ;
334
415
335
416
for ( const type of types ) {
@@ -343,7 +424,7 @@ export class SignalQueriesMigration extends TsurgeComplexMigration<
343
424
}
344
425
}
345
426
346
- if ( ! seenIncompatibleMultiQuery && ! filesWithUnrelatedQueryListImports . has ( file ) ) {
427
+ if ( ! seenIncompatibleMultiQuery && ! filesWithQueryListOutsideOfDeclarations . has ( file ) ) {
347
428
importManager . removeImport ( file , 'QueryList' , '@angular/core' ) ;
348
429
}
349
430
}
0 commit comments