6
6
* found in the LICENSE file at https://angular.io/license
7
7
*/
8
8
9
- import { logging } from '@angular-devkit/core' ;
10
9
import { Rule , SchematicContext , SchematicsException , Tree } from '@angular-devkit/schematics' ;
11
10
import { dirname , relative } from 'path' ;
11
+ import { from } from 'rxjs' ;
12
12
import * as ts from 'typescript' ;
13
13
14
14
import { NgComponentTemplateVisitor } from '../../utils/ng_component_template' ;
15
15
import { getProjectTsConfigPaths } from '../../utils/project_tsconfig_paths' ;
16
+ import { getInquirer , supportsPrompt } from '../../utils/schematics_prompt' ;
16
17
import { parseTsconfigFile } from '../../utils/typescript/parse_tsconfig' ;
17
18
import { TypeScriptVisitor , visitAllNodes } from '../../utils/typescript/visit_nodes' ;
18
19
@@ -22,34 +23,83 @@ import {TimingStrategy} from './strategies/timing-strategy';
22
23
import { QueryUsageStrategy } from './strategies/usage_strategy/usage_strategy' ;
23
24
import { getTransformedQueryCallExpr } from './transform' ;
24
25
25
- type Logger = logging . LoggerApi ;
26
-
27
26
/** Entry point for the V8 static-query migration. */
28
27
export default function ( ) : Rule {
29
28
return ( tree : Tree , context : SchematicContext ) => {
30
- const projectTsConfigPaths = getProjectTsConfigPaths ( tree ) ;
31
- const basePath = process . cwd ( ) ;
32
-
33
- if ( ! projectTsConfigPaths . length ) {
34
- throw new SchematicsException (
35
- 'Could not find any tsconfig file. Cannot migrate queries ' +
36
- 'to explicit timing.' ) ;
37
- }
38
-
39
- for ( const tsconfigPath of projectTsConfigPaths ) {
40
- runStaticQueryMigration ( tree , tsconfigPath , basePath , context . logger ) ;
41
- }
29
+ // We need to cast the returned "Observable" to "any" as there is a
30
+ // RxJS version mismatch that breaks the TS compilation.
31
+ return from ( runMigration ( tree , context ) . then ( ( ) => tree ) ) as any ;
42
32
} ;
43
33
}
44
34
35
+ /** Runs the V8 migration static-query migration for all determined TypeScript projects. */
36
+ async function runMigration ( tree : Tree , context : SchematicContext ) {
37
+ const projectTsConfigPaths = getProjectTsConfigPaths ( tree ) ;
38
+ const basePath = process . cwd ( ) ;
39
+ const logger = context . logger ;
40
+
41
+ logger . info ( '------ Static Query migration ------' ) ;
42
+ logger . info ( 'In preparation for Ivy, developers can now explicitly specify the' ) ;
43
+ logger . info ( 'timing of their queries. Read more about this here:' ) ;
44
+ logger . info ( 'https://github.com/angular/angular/pull/28810' ) ;
45
+ logger . info ( '' ) ;
46
+
47
+ if ( ! projectTsConfigPaths . length ) {
48
+ throw new SchematicsException (
49
+ 'Could not find any tsconfig file. Cannot migrate queries ' +
50
+ 'to explicit timing.' ) ;
51
+ }
52
+
53
+ // In case prompts are supported, determine the desired migration strategy
54
+ // by creating a choice prompt. By default the template strategy is used.
55
+ let isUsageStrategy = false ;
56
+ if ( supportsPrompt ( ) ) {
57
+ logger . info ( 'There are two available migration strategies that can be selected:' ) ;
58
+ logger . info ( ' • Template strategy - migration tool (short-term gains, rare corrections)' ) ;
59
+ logger . info ( ' • Usage strategy - best practices (long-term gains, manual corrections)' ) ;
60
+ logger . info ( 'For an easy migration, the template strategy is recommended. The usage' ) ;
61
+ logger . info ( 'strategy can be used for best practices and a code base that will be more' ) ;
62
+ logger . info ( 'flexible to changes going forward.' ) ;
63
+ const { strategyName} = await getInquirer ( ) . prompt < { strategyName : string } > ( {
64
+ type : 'list' ,
65
+ name : 'strategyName' ,
66
+ message : 'What migration strategy do you want to use?' ,
67
+ choices : [
68
+ { name : 'Template strategy' , value : 'template' } , { name : 'Usage strategy' , value : 'usage' }
69
+ ] ,
70
+ default : 'template' ,
71
+ } ) ;
72
+ logger . info ( '' ) ;
73
+ isUsageStrategy = strategyName === 'usage' ;
74
+ } else {
75
+ // In case prompts are not supported, we still want to allow developers to opt
76
+ // into the usage strategy by specifying an environment variable. The tests also
77
+ // use the environment variable as there is no headless way to select via prompt.
78
+ isUsageStrategy = ! ! process . env [ 'NG_STATIC_QUERY_USAGE_STRATEGY' ] ;
79
+ }
80
+
81
+ const failures = [ ] ;
82
+ for ( const tsconfigPath of projectTsConfigPaths ) {
83
+ failures . push ( ...await runStaticQueryMigration ( tree , tsconfigPath , basePath , isUsageStrategy ) ) ;
84
+ }
85
+
86
+ if ( failures . length ) {
87
+ logger . info ( 'Some queries cannot be migrated automatically. Please go through' ) ;
88
+ logger . info ( 'those manually and apply the appropriate timing:' ) ;
89
+ failures . forEach ( failure => logger . warn ( `⮑ ${ failure } ` ) ) ;
90
+ }
91
+
92
+ logger . info ( '------------------------------------------------' ) ;
93
+ }
94
+
45
95
/**
46
96
* Runs the static query migration for the given TypeScript project. The schematic
47
97
* analyzes all queries within the project and sets up the query timing based on
48
98
* the current usage of the query property. e.g. a view query that is not used in any
49
99
* lifecycle hook does not need to be static and can be set up with "static: false".
50
100
*/
51
- function runStaticQueryMigration (
52
- tree : Tree , tsconfigPath : string , basePath : string , logger : Logger ) {
101
+ async function runStaticQueryMigration (
102
+ tree : Tree , tsconfigPath : string , basePath : string , isUsageStrategy : boolean ) {
53
103
const parsed = parseTsconfigFile ( tsconfigPath , dirname ( tsconfigPath ) ) ;
54
104
const host = ts . createCompilerHost ( parsed . options , true ) ;
55
105
@@ -62,7 +112,6 @@ function runStaticQueryMigration(
62
112
return buffer ? buffer . toString ( ) : undefined ;
63
113
} ;
64
114
65
- const isUsageStrategy = ! ! process . env [ 'NG_STATIC_QUERY_USAGE_STRATEGY' ] ;
66
115
const program = ts . createProgram ( parsed . fileNames , parsed . options , host ) ;
67
116
const typeChecker = program . getTypeChecker ( ) ;
68
117
const queryVisitor = new NgQueryResolveVisitor ( typeChecker ) ;
@@ -100,13 +149,13 @@ function runStaticQueryMigration(
100
149
const strategy : TimingStrategy = isUsageStrategy ?
101
150
new QueryUsageStrategy ( classMetadata , typeChecker ) :
102
151
new QueryTemplateStrategy ( tsconfigPath , classMetadata , host ) ;
103
- const detectionMessages : string [ ] = [ ] ;
152
+ const failureMessages : string [ ] = [ ] ;
104
153
105
154
// In case the strategy could not be set up properly, we just exit the
106
155
// migration. We don't want to throw an exception as this could mean
107
156
// that other migrations are interrupted.
108
157
if ( ! strategy . setup ( ) ) {
109
- return ;
158
+ return [ ] ;
110
159
}
111
160
112
161
// Walk through all source files that contain resolved queries and update
@@ -135,23 +184,15 @@ function runStaticQueryMigration(
135
184
update . remove ( queryExpr . getStart ( ) , queryExpr . getWidth ( ) ) ;
136
185
update . insertRight ( queryExpr . getStart ( ) , newText ) ;
137
186
138
- const { line, character} =
139
- ts . getLineAndCharacterOfPosition ( sourceFile , q . decorator . node . getStart ( ) ) ;
140
- detectionMessages . push ( `${ relativePath } @${ line + 1 } :${ character + 1 } : ${ message } ` ) ;
187
+ if ( message ) {
188
+ const { line, character} =
189
+ ts . getLineAndCharacterOfPosition ( sourceFile , q . decorator . node . getStart ( ) ) ;
190
+ failureMessages . push ( `${ relativePath } @${ line + 1 } :${ character + 1 } : ${ message } ` ) ;
191
+ }
141
192
} ) ;
142
193
143
194
tree . commitUpdate ( update ) ;
144
195
} ) ;
145
196
146
- if ( detectionMessages . length ) {
147
- logger . info ( '------ Static Query migration ------' ) ;
148
- logger . info ( 'In preparation for Ivy, developers can now explicitly specify the' ) ;
149
- logger . info ( 'timing of their queries. Read more about this here:' ) ;
150
- logger . info ( 'https://github.com/angular/angular/pull/28810' ) ;
151
- logger . info ( '' ) ;
152
- logger . info ( 'Some queries cannot be migrated automatically. Please go through' ) ;
153
- logger . info ( 'those manually and apply the appropriate timing:' ) ;
154
- detectionMessages . forEach ( failure => logger . warn ( `⮑ ${ failure } ` ) ) ;
155
- logger . info ( '------------------------------------------------' ) ;
156
- }
197
+ return failureMessages ;
157
198
}
0 commit comments