@@ -16,6 +16,12 @@ import {
16
16
unprefixedRemovedVariables
17
17
} from './config' ;
18
18
19
+ /** The result of a search for imports and namespaces in a file. */
20
+ interface DetectImportResult {
21
+ imports : string [ ] ;
22
+ namespaces : string [ ] ;
23
+ }
24
+
19
25
/**
20
26
* Migrates the content of a file to the new theming API. Note that this migration is using plain
21
27
* string manipulation, rather than the AST from PostCSS and the schematics string manipulation
@@ -40,8 +46,8 @@ export function migrateFileContent(content: string,
40
46
41
47
// Try to migrate the symbols even if there are no imports. This is used
42
48
// to cover the case where the Components symbols were used transitively.
43
- content = migrateMaterialSymbols ( content , newMaterialImportPath , materialResults . namespaces ) ;
44
- content = migrateCdkSymbols ( content , newCdkImportPath , cdkResults . namespaces ) ;
49
+ content = migrateMaterialSymbols ( content , newMaterialImportPath , materialResults ) ;
50
+ content = migrateCdkSymbols ( content , newCdkImportPath , cdkResults ) ;
45
51
content = replaceRemovedVariables ( content , removedMaterialVariables ) ;
46
52
47
53
// We can assume that the migration has taken care of any Components symbols that were
@@ -64,7 +70,7 @@ export function migrateFileContent(content: string,
64
70
* @param content File content in which to look for imports.
65
71
* @param prefix Prefix that the imports should start with.
66
72
*/
67
- function detectImports ( content : string , prefix : string ) : { imports : string [ ] , namespaces : string [ ] } {
73
+ function detectImports ( content : string , prefix : string ) : DetectImportResult {
68
74
if ( prefix [ prefix . length - 1 ] !== '/' ) {
69
75
// Some of the logic further down makes assumptions about the import depth.
70
76
throw Error ( `Prefix "${ prefix } " has to end in a slash.` ) ;
@@ -96,47 +102,49 @@ function detectImports(content: string, prefix: string): {imports: string[], nam
96
102
}
97
103
98
104
/** Migrates the Material symbls in a file. */
99
- function migrateMaterialSymbols ( content : string , importPath : string , namespaces : string [ ] ) : string {
105
+ function migrateMaterialSymbols ( content : string , importPath : string ,
106
+ detectedImports : DetectImportResult ) : string {
100
107
const initialContent = content ;
101
108
const namespace = 'mat' ;
102
109
103
110
// Migrate the mixins.
104
- content = renameSymbols ( content , materialMixins , namespaces , mixinKeyFormatter ,
111
+ content = renameSymbols ( content , materialMixins , detectedImports . namespaces , mixinKeyFormatter ,
105
112
getMixinValueFormatter ( namespace ) ) ;
106
113
107
114
// Migrate the functions.
108
- content = renameSymbols ( content , materialFunctions , namespaces , functionKeyFormatter ,
109
- getFunctionValueFormatter ( namespace ) ) ;
115
+ content = renameSymbols ( content , materialFunctions , detectedImports . namespaces ,
116
+ functionKeyFormatter , getFunctionValueFormatter ( namespace ) ) ;
110
117
111
118
// Migrate the variables.
112
- content = renameSymbols ( content , materialVariables , namespaces , variableKeyFormatter ,
113
- getVariableValueFormatter ( namespace ) ) ;
119
+ content = renameSymbols ( content , materialVariables , detectedImports . namespaces ,
120
+ variableKeyFormatter , getVariableValueFormatter ( namespace ) ) ;
114
121
115
122
if ( content !== initialContent ) {
116
123
// Add an import to the new API only if any of the APIs were being used.
117
- content = insertUseStatement ( content , importPath , namespace ) ;
124
+ content = insertUseStatement ( content , importPath , detectedImports . imports , namespace ) ;
118
125
}
119
126
120
127
return content ;
121
128
}
122
129
123
130
/** Migrates the CDK symbols in a file. */
124
- function migrateCdkSymbols ( content : string , importPath : string , namespaces : string [ ] ) : string {
131
+ function migrateCdkSymbols ( content : string , importPath : string ,
132
+ detectedImports : DetectImportResult ) : string {
125
133
const initialContent = content ;
126
134
const namespace = 'cdk' ;
127
135
128
136
// Migrate the mixins.
129
- content = renameSymbols ( content , cdkMixins , namespaces , mixinKeyFormatter ,
137
+ content = renameSymbols ( content , cdkMixins , detectedImports . namespaces , mixinKeyFormatter ,
130
138
getMixinValueFormatter ( namespace ) ) ;
131
139
132
140
// Migrate the variables.
133
- content = renameSymbols ( content , cdkVariables , namespaces , variableKeyFormatter ,
141
+ content = renameSymbols ( content , cdkVariables , detectedImports . namespaces , variableKeyFormatter ,
134
142
getVariableValueFormatter ( namespace ) ) ;
135
143
136
144
// Previously the CDK symbols were exposed through `material/theming`, but now we have a
137
145
// dedicated entrypoint for the CDK. Only add an import for it if any of the symbols are used.
138
146
if ( content !== initialContent ) {
139
- content = insertUseStatement ( content , importPath , namespace ) ;
147
+ content = insertUseStatement ( content , importPath , detectedImports . imports , namespace ) ;
140
148
}
141
149
142
150
return content ;
@@ -175,11 +183,21 @@ function renameSymbols(content: string,
175
183
}
176
184
177
185
/** Inserts an `@use` statement in a string. */
178
- function insertUseStatement ( content : string , importPath : string , namespace : string ) : string {
186
+ function insertUseStatement ( content : string , importPath : string , importsToIgnore : string [ ] ,
187
+ namespace : string ) : string {
188
+ // We want to find the first import that isn't in the list of ignored imports or find nothing,
189
+ // because the imports being replaced might be the only ones in the file and they can be further
190
+ // down. An easy way to do this is to replace the imports with a random character and run
191
+ // `indexOf` on the result. This isn't the most efficient way of doing it, but it's more compact
192
+ // and it allows us to easily deal with things like comment nodes.
193
+ const contentToSearch = importsToIgnore . reduce ( ( accumulator , current ) =>
194
+ accumulator . replace ( current , '◬' . repeat ( current . length ) ) , content ) ;
195
+
179
196
// Sass has a limitation that all `@use` declarations have to come before `@import` so we have
180
197
// to find the first import and insert before it. Technically we can get away with always
181
198
// inserting at 0, but the file may start with something like a license header.
182
- const newImportIndex = Math . max ( 0 , content . indexOf ( '@import ' ) ) ;
199
+ const newImportIndex = Math . max ( 0 , contentToSearch . indexOf ( '@import ' ) ) ;
200
+
183
201
return content . slice ( 0 , newImportIndex ) + `@use '${ importPath } ' as ${ namespace } ;\n` +
184
202
content . slice ( newImportIndex ) ;
185
203
}
0 commit comments