7
7
*/
8
8
9
9
import { Rule , SchematicsException , Tree } from '@angular-devkit/schematics' ;
10
+ import { dirname , relative } from 'path' ;
11
+ import * as ts from 'typescript' ;
12
+
10
13
import { getProjectTsConfigPaths } from '../../utils/project_tsconfig_paths' ;
11
- import { runStaticQueryMigration } from './migration' ;
14
+
15
+ import { analyzeNgQueryUsage } from './angular/analyze_query_usage' ;
16
+ import { NgQueryResolveVisitor } from './angular/ng_query_visitor' ;
17
+ import { getTransformedQueryCallExpr } from './transform' ;
18
+ import { parseTsconfigFile } from './typescript/tsconfig' ;
19
+
12
20
13
21
/** Entry point for the V8 static-query migration. */
14
22
export default function ( ) : Rule {
@@ -27,3 +35,63 @@ export default function(): Rule {
27
35
}
28
36
} ;
29
37
}
38
+
39
+ /**
40
+ * Runs the static query migration for the given TypeScript project. The schematic
41
+ * analyzes all queries within the project and sets up the query timing based on
42
+ * the current usage of the query property. e.g. a view query that is not used in any
43
+ * lifecycle hook does not need to be static and can be set up with "static: false".
44
+ */
45
+ function runStaticQueryMigration ( tree : Tree , tsconfigPath : string , basePath : string ) {
46
+ const parsed = parseTsconfigFile ( tsconfigPath , dirname ( tsconfigPath ) ) ;
47
+ const host = ts . createCompilerHost ( parsed . options , true ) ;
48
+
49
+ // We need to overwrite the host "readFile" method, as we want the TypeScript
50
+ // program to be based on the file contents in the virtual file tree. Otherwise
51
+ // if we run the migration for multiple tsconfig files which have intersecting
52
+ // source files, it can end up updating query definitions multiple times.
53
+ host . readFile = fileName => {
54
+ const buffer = tree . read ( relative ( basePath , fileName ) ) ;
55
+ return buffer ? buffer . toString ( ) : undefined ;
56
+ } ;
57
+
58
+ const program = ts . createProgram ( parsed . fileNames , parsed . options , host ) ;
59
+ const typeChecker = program . getTypeChecker ( ) ;
60
+ const queryVisitor = new NgQueryResolveVisitor ( typeChecker ) ;
61
+ const rootSourceFiles = program . getRootFileNames ( ) . map ( f => program . getSourceFile ( f ) ! ) ;
62
+ const printer = ts . createPrinter ( ) ;
63
+
64
+ // Analyze source files by detecting queries and class relations.
65
+ rootSourceFiles . forEach ( sourceFile => queryVisitor . visitNode ( sourceFile ) ) ;
66
+
67
+ const { resolvedQueries, classMetadata} = queryVisitor ;
68
+
69
+ // Walk through all source files that contain resolved queries and update
70
+ // the source files if needed. Note that we need to update multiple queries
71
+ // within a source file within the same recorder in order to not throw off
72
+ // the TypeScript node offsets.
73
+ resolvedQueries . forEach ( ( queries , sourceFile ) => {
74
+ const update = tree . beginUpdate ( relative ( basePath , sourceFile . fileName ) ) ;
75
+
76
+ // Compute the query usage for all resolved queries and update the
77
+ // query definitions to explicitly declare the query timing (static or dynamic)
78
+ queries . forEach ( q => {
79
+ const queryExpr = q . decorator . node . expression ;
80
+ const timing = analyzeNgQueryUsage ( q , classMetadata , typeChecker ) ;
81
+ const transformedNode = getTransformedQueryCallExpr ( q , timing ) ;
82
+
83
+ if ( ! transformedNode ) {
84
+ return ;
85
+ }
86
+
87
+ const newText = printer . printNode ( ts . EmitHint . Unspecified , transformedNode , sourceFile ) ;
88
+
89
+ // Replace the existing query decorator call expression with the updated
90
+ // call expression node.
91
+ update . remove ( queryExpr . getStart ( ) , queryExpr . getWidth ( ) ) ;
92
+ update . insertRight ( queryExpr . getStart ( ) , newText ) ;
93
+ } ) ;
94
+
95
+ tree . commitUpdate ( update ) ;
96
+ } ) ;
97
+ }
0 commit comments