@@ -10,11 +10,13 @@ import {Migration} from '@angular/cdk/schematics';
10
10
import { SchematicContext } from '@angular-devkit/schematics' ;
11
11
import { ComponentMigrator } from '../index' ;
12
12
import * as ts from 'typescript' ;
13
+ import { ThemingStylesMigration } from '../theming-styles' ;
13
14
14
15
export class RuntimeCodeMigration extends Migration < ComponentMigrator [ ] , SchematicContext > {
15
16
enabled = true ;
16
17
17
18
private _printer = ts . createPrinter ( { newLine : ts . NewLineKind . LineFeed } ) ;
19
+ private _stylesMigration : ThemingStylesMigration ;
18
20
19
21
override visitNode ( node : ts . Node ) : void {
20
22
if ( this . _isImportExpression ( node ) ) {
@@ -24,9 +26,86 @@ export class RuntimeCodeMigration extends Migration<ComponentMigrator[], Schemat
24
26
} else if ( ts . isImportDeclaration ( node ) ) {
25
27
// Note: TypeScript enforces the `moduleSpecifier` to be a string literal in its syntax.
26
28
this . _migrateModuleSpecifier ( node . moduleSpecifier as ts . StringLiteral ) ;
29
+ } else if ( this . _isComponentDecorator ( node ) ) {
30
+ this . _migrateTemplatesAndStyles ( node ) ;
27
31
}
28
32
}
29
33
34
+ private _migrateTemplatesAndStyles ( node : ts . Node ) : void {
35
+ if ( node . getChildCount ( ) > 0 ) {
36
+ if ( node . kind === ts . SyntaxKind . PropertyAssignment ) {
37
+ // The first child node will always be the identifier for a property
38
+ // assignment node
39
+ const identifier = node . getChildAt ( 0 ) ;
40
+ if ( identifier . getText ( ) === 'styles' ) {
41
+ this . _migrateStyles ( node ) ;
42
+ }
43
+ } else {
44
+ node . forEachChild ( child => this . _migrateTemplatesAndStyles ( child ) ) ;
45
+ }
46
+ }
47
+ }
48
+
49
+ private _migrateStyles ( node : ts . Node ) {
50
+ // Create styles migration if no styles have been migrated yet. Needs to be
51
+ // additionally created because the migrations run in isolation.
52
+ if ( ! this . _stylesMigration ) {
53
+ this . _stylesMigration = new ThemingStylesMigration (
54
+ this . program ,
55
+ this . typeChecker ,
56
+ this . targetVersion ,
57
+ this . context ,
58
+ this . upgradeData ,
59
+ this . fileSystem ,
60
+ this . logger ,
61
+ ) ;
62
+ }
63
+
64
+ node . forEachChild ( childNode => {
65
+ if ( childNode . kind === ts . SyntaxKind . ArrayLiteralExpression ) {
66
+ childNode . forEachChild ( stringLiteralNode => {
67
+ if ( stringLiteralNode . kind === ts . SyntaxKind . StringLiteral ) {
68
+ let nodeText = stringLiteralNode . getText ( ) ;
69
+ const trimmedNodeText = nodeText . trimStart ( ) . trimEnd ( ) ;
70
+ // Remove quotation marks from string since not valid CSS to migrate
71
+ const nodeTextWithoutQuotes = trimmedNodeText . substring ( 1 , trimmedNodeText . length - 1 ) ;
72
+ let migratedStyles = this . _stylesMigration . migrateStyles ( nodeTextWithoutQuotes ) ;
73
+ const migratedStylesLines = migratedStyles . split ( '\n' ) ;
74
+ const isMultiline = migratedStylesLines . length > 1 ;
75
+
76
+ // If migrated text is now multiline, update quotes to avoid
77
+ // compilation errors
78
+ if ( isMultiline ) {
79
+ nodeText = nodeText . replace ( trimmedNodeText , '`' + nodeTextWithoutQuotes + '`' ) ;
80
+ }
81
+
82
+ this . _printAndUpdateNode (
83
+ stringLiteralNode . getSourceFile ( ) ,
84
+ stringLiteralNode ,
85
+ ts . factory . createRegularExpressionLiteral (
86
+ nodeText . replace (
87
+ nodeTextWithoutQuotes ,
88
+ migratedStylesLines
89
+ . map ( ( line , index ) => {
90
+ // Only need to worry about indentation when adding new lines
91
+ if ( isMultiline && index !== 0 && line != '\n' ) {
92
+ const leadingWidth = stringLiteralNode . getLeadingTriviaWidth ( ) ;
93
+ if ( leadingWidth > 0 ) {
94
+ line = ' ' . repeat ( leadingWidth - 1 ) + line ;
95
+ }
96
+ }
97
+ return line ;
98
+ } )
99
+ . join ( '\n' ) ,
100
+ ) ,
101
+ ) ,
102
+ ) ;
103
+ }
104
+ } ) ;
105
+ }
106
+ } ) ;
107
+ }
108
+
30
109
private _migrateModuleSpecifier ( specifierLiteral : ts . StringLiteralLike ) {
31
110
const sourceFile = specifierLiteral . getSourceFile ( ) ;
32
111
@@ -43,6 +122,11 @@ export class RuntimeCodeMigration extends Migration<ComponentMigrator[], Schemat
43
122
}
44
123
}
45
124
125
+ /** Gets whether the specified node is a component decorator for a class */
126
+ private _isComponentDecorator ( node : ts . Node ) : boolean {
127
+ return node . kind === ts . SyntaxKind . Decorator && node . getText ( ) . startsWith ( '@Component' ) ;
128
+ }
129
+
46
130
/** Gets whether the specified node is an import expression. */
47
131
private _isImportExpression (
48
132
node : ts . Node ,
0 commit comments