Skip to content

Commit abb177b

Browse files
authored
fix(material/schematics): split core imports in ng update (#25678)
Some of the imports from @angular/material/core (the ones related to MatOption) need to be migrated to import from @angular/material/legacy-core. However, the remaining symbols should still import from @angular/material/core. This PR changes the ng update script to separate them into separate import statements.
1 parent 9bd8523 commit abb177b

File tree

3 files changed

+140
-10
lines changed

3 files changed

+140
-10
lines changed

src/material/schematics/ng-update/migrations/legacy-components-v15/constants.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,3 +109,21 @@ export const CUSTOM_SASS_MIXIN_RENAMINGS: {[key: string]: string} = {
109109
export const CUSTOM_SASS_FUNCTION_RENAMINGS: {[key: string]: string} = {
110110
'define-typography-config': 'define-legacy-typography-config',
111111
};
112+
113+
export const MIGRATED_CORE_SYMBOLS: {[key: string]: string} = {
114+
'MAT_OPTGROUP': 'MAT_LEGACY_OPTGROUP',
115+
'MatOptionSelectionChange': 'MatLegacyOptionSelectionChange',
116+
'MatOptionParentComponent': 'MatLegacyOptionParentComponent',
117+
'MAT_OPTION_PARENT_COMPONENT': 'MAT_LEGACY_OPTION_PARENT_COMPONENT',
118+
'_countGroupLabelsBeforeOption': '_countGroupLabelsBeforeLegacyOption',
119+
'_getOptionScrollPosition': '_getLegacyOptionScrollPosition',
120+
'_MatOptionBase': '_MatLegacyOptionBase',
121+
'_MatOptgroupBase': '_MatLegacyOptgroupBase',
122+
'MatOptionModule': 'MatLegacyOptionModule',
123+
'MatOption': 'MatLegacyOption',
124+
'MatOptgroup': 'MatLegacyOptgroup',
125+
'MatOptionHarness': 'MatLegacyOptionHarness',
126+
'OptionHarnessFilters': 'LegacyOptionHarnessFilters',
127+
'MatOptgroupHarness': 'MatLegacyOptgroupHarness',
128+
'OptgroupHarnessFilters': 'LegacyOptgroupHarnessFilters',
129+
};

src/material/schematics/ng-update/migrations/legacy-components-v15/index.ts

Lines changed: 98 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
COMPONENT_THEME_MIXINS,
1717
CUSTOM_SASS_MIXIN_RENAMINGS,
1818
CUSTOM_SASS_FUNCTION_RENAMINGS,
19+
MIGRATED_CORE_SYMBOLS,
1920
} from './constants';
2021
import {Migration, ResolvedResource, TargetVersion, WorkspacePath} from '@angular/cdk/schematics';
2122

@@ -128,22 +129,107 @@ export class LegacyComponentsMigration extends Migration<null> {
128129
*/
129130
private _handleImportDeclaration(node: ts.ImportDeclaration): void {
130131
const moduleSpecifier = node.moduleSpecifier as ts.StringLiteral;
131-
132132
const matImportChange = this._findMatImportChange(moduleSpecifier);
133-
if (matImportChange) {
133+
const mdcImportChange = this._findMdcImportChange(moduleSpecifier);
134+
135+
if (this._isCoreImport(moduleSpecifier.text)) {
136+
this._handleCoreImportDeclaration(node);
137+
} else if (matImportChange) {
134138
this._tsReplaceAt(node, matImportChange);
135139

136140
if (node.importClause?.namedBindings && ts.isNamedImports(node.importClause.namedBindings)) {
137141
this._handleNamedImportBindings(node.importClause.namedBindings);
138142
}
143+
} else if (mdcImportChange) {
144+
this._tsReplaceAt(node, mdcImportChange);
139145
}
146+
}
140147

141-
const mdcImportChange = this._findMdcImportChange(moduleSpecifier);
142-
if (mdcImportChange) {
143-
this._tsReplaceAt(node, mdcImportChange);
148+
private _isCoreImport(importPath: string) {
149+
return ['@angular/material/core', '@angular/material/core/testing'].includes(importPath);
150+
}
151+
152+
private _handleCoreImportDeclaration(node: ts.ImportDeclaration) {
153+
const moduleSpecifier = node.moduleSpecifier as ts.StringLiteral;
154+
155+
if (node.importClause?.namedBindings && ts.isNamedImports(node.importClause.namedBindings)) {
156+
this._splitCoreImport(node, node.importClause.namedBindings);
157+
} else {
158+
this._tsReplaceAt(node, {
159+
old: moduleSpecifier.text,
160+
new: moduleSpecifier.text.replace(
161+
'@angular/material/core',
162+
'@angular/material/legacy-core',
163+
),
164+
});
144165
}
145166
}
146167

168+
private _splitCoreImport(
169+
node: ts.Node,
170+
namedBindings: ts.NamedImports | ts.ObjectBindingPattern,
171+
) {
172+
const migratedSymbols = [];
173+
const unmigratedSymbols = [];
174+
for (const element of namedBindings.elements) {
175+
if (this._isMigratedCoreSymbol(element)) {
176+
migratedSymbols.push(element);
177+
} else {
178+
unmigratedSymbols.push(element);
179+
}
180+
}
181+
const unmigratedImportDeclaration = unmigratedSymbols.length
182+
? [this._stripImports(node.getText(), migratedSymbols)]
183+
: [];
184+
const migratedImportDeclaration = migratedSymbols.length
185+
? [
186+
this._updateImportedCoreSymbols(
187+
this._stripImports(node.getText(), unmigratedSymbols).replace(
188+
'@angular/material/core',
189+
'@angular/material/legacy-core',
190+
),
191+
migratedSymbols,
192+
),
193+
]
194+
: [];
195+
this._tsReplaceAt(node, {
196+
old: node.getText(),
197+
new: [...unmigratedImportDeclaration, ...migratedImportDeclaration].join('\n'),
198+
});
199+
}
200+
201+
private _isMigratedCoreSymbol(node: ts.ImportSpecifier | ts.BindingElement): boolean {
202+
const name = node.propertyName ? node.propertyName : node.name;
203+
if (!ts.isIdentifier(name)) {
204+
return false;
205+
}
206+
207+
return !!MIGRATED_CORE_SYMBOLS[name.escapedText.toString()];
208+
}
209+
210+
private _stripImports(importString: string, remove: (ts.ImportSpecifier | ts.BindingElement)[]) {
211+
for (const symbol of remove) {
212+
importString = importString
213+
.replace(new RegExp(`,\\s*${symbol.getText()}`), '')
214+
.replace(new RegExp(`${symbol.getText()},\\s*`), '')
215+
.replace(symbol.getText(), '');
216+
}
217+
return importString;
218+
}
219+
220+
private _updateImportedCoreSymbols(
221+
importString: string,
222+
rename: (ts.ImportSpecifier | ts.BindingElement)[],
223+
) {
224+
return rename.reduce((result, symbol) => {
225+
const oldName = symbol.propertyName ? symbol.propertyName.getText() : symbol.name.getText();
226+
const newName = MIGRATED_CORE_SYMBOLS[oldName];
227+
const aliasedName = symbol.propertyName ? symbol.name.getText() : oldName;
228+
const separator = ts.isImportSpecifier(symbol) ? ' as ' : ': ';
229+
return result.replace(symbol.getText(), `${newName}${separator}${aliasedName}`);
230+
}, importString);
231+
}
232+
147233
/**
148234
* Handles updating the module specifier of
149235
* @angular/material and @angular/material-experimental import expressions.
@@ -167,8 +253,13 @@ export class LegacyComponentsMigration extends Migration<null> {
167253
private _handleDestructuredAsyncImport(
168254
node: ts.VariableDeclaration & {name: ts.ObjectBindingPattern},
169255
): void {
170-
for (let i = 0; i < node.name.elements.length; i++) {
171-
this._handleNamedBindings(node.name.elements[i]);
256+
const importPath = (node!.initializer as any).expression.arguments[0].text;
257+
if (ts.isVariableStatement(node.parent.parent) && this._isCoreImport(importPath)) {
258+
this._splitCoreImport(node.parent.parent, node.name);
259+
} else {
260+
for (let i = 0; i < node.name.elements.length; i++) {
261+
this._handleNamedBindings(node.name.elements[i]);
262+
}
172263
}
173264
}
174265

src/material/schematics/ng-update/test-cases/v15/legacy-components-v15.spec.ts

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -118,9 +118,30 @@ describe('v15 legacy components migration', () => {
118118
old: `import {MatButtonToggleModule} from '@angular/material/button-toggle';`,
119119
new: `import {MatButtonToggleModule} from '@angular/material/button-toggle';`,
120120
});
121-
await runTypeScriptMigrationTest('non-legacy symbol', {
122-
old: `import {VERSION} from '@angular/material/core`,
123-
new: `import {VERSION} from '@angular/material/legacy-core`,
121+
});
122+
123+
it('splits @angular/material/core imports', async () => {
124+
await runMultilineTypeScriptMigrationTest('core imports', {
125+
old: [
126+
`import {VERSION, MatOption} from '@angular/material/core';`,
127+
`import {CanDisable} from '@angular/material/core';`,
128+
`import {MatOptionHarness} from '@angular/material/core/testing';`,
129+
`import {VERSION as a, MatOption as b} from '@angular/material/core';`,
130+
`const {mixinDisable, MatOptgroup} = await import('@angular/material/core');`,
131+
`const {mixinDisable: c, MatOptgroup: d} = await import('@angular/material/core');`,
132+
],
133+
new: [
134+
`import {VERSION} from '@angular/material/core';`,
135+
`import {MatLegacyOption as MatOption} from '@angular/material/legacy-core';`,
136+
`import {CanDisable} from '@angular/material/core';`,
137+
`import {MatLegacyOptionHarness as MatOptionHarness} from '@angular/material/legacy-core/testing';`,
138+
`import {VERSION as a} from '@angular/material/core';`,
139+
`import {MatLegacyOption as b} from '@angular/material/legacy-core';`,
140+
`const {mixinDisable} = await import('@angular/material/core');`,
141+
`const {MatLegacyOptgroup: MatOptgroup} = await import('@angular/material/legacy-core');`,
142+
`const {mixinDisable: c} = await import('@angular/material/core');`,
143+
`const {MatLegacyOptgroup: d} = await import('@angular/material/legacy-core');`,
144+
],
124145
});
125146
});
126147
});

0 commit comments

Comments
 (0)