Skip to content

Commit 4adc372

Browse files
authoredSep 25, 2024··
feat(material/schematics): create v19 core removal schematic (#29768)
* Removes uses of the core mixin and replaces them with two mixins. One to generate the mat-app-background class, and one to generate the mat-elevation classes.
1 parent 6315e79 commit 4adc372

File tree

3 files changed

+166
-1
lines changed

3 files changed

+166
-1
lines changed
 

‎src/material/schematics/ng-update/index.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,9 @@ import {
1414
} from '@angular/cdk/schematics';
1515

1616
import {materialUpgradeData} from './upgrade-data';
17+
import {MatCoreMigration} from './migrations/mat-core-removal';
1718

18-
const materialMigrations: NullableDevkitMigration[] = [];
19+
const materialMigrations: NullableDevkitMigration[] = [MatCoreMigration];
1920

2021
/** Entry point for the migration schematics with target of Angular Material v19 */
2122
export function updateToV19(): Rule {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.dev/license
7+
*/
8+
9+
import * as postcss from 'postcss';
10+
import * as scss from 'postcss-scss';
11+
import {
12+
DevkitContext,
13+
Migration,
14+
ResolvedResource,
15+
UpgradeData,
16+
WorkspacePath,
17+
} from '@angular/cdk/schematics';
18+
19+
export class MatCoreMigration extends Migration<UpgradeData, DevkitContext> {
20+
override enabled = true;
21+
private _namespace: string | undefined;
22+
23+
override init() {
24+
// TODO: Check if mat-app-background is used in the application.
25+
}
26+
27+
override visitStylesheet(stylesheet: ResolvedResource): void {
28+
const processor = new postcss.Processor([
29+
{
30+
postcssPlugin: 'mat-core-removal-v19-plugin',
31+
AtRule: {
32+
use: node => this._getNamespace(node),
33+
include: node => this._handleAtInclude(node, stylesheet.filePath),
34+
},
35+
},
36+
]);
37+
processor.process(stylesheet.content, {syntax: scss}).sync();
38+
}
39+
40+
/** Handles updating the at-include rules of uses of the core mixin. */
41+
private _handleAtInclude(node: postcss.AtRule, filePath: WorkspacePath): void {
42+
if (!this._namespace || !node.source?.start || !node.source.end) {
43+
return;
44+
}
45+
46+
if (this._isMatCoreMixin(node)) {
47+
const end = node.source.end.offset;
48+
const start = node.source.start.offset;
49+
50+
const prefix = '\n' + (node.raws.before?.split('\n').pop() || '');
51+
const snippet = prefix + node.source.input.css.slice(start, end);
52+
53+
const elevation = prefix + `@include ${this._namespace}.elevation-classes();`;
54+
const background = prefix + `@include ${this._namespace}.app-background();`;
55+
56+
this._replaceAt(filePath, node.source.start.offset - prefix.length, {
57+
old: snippet,
58+
new: elevation + background,
59+
});
60+
}
61+
}
62+
63+
/** Returns true if the given at-rule is a use of the core mixin. */
64+
private _isMatCoreMixin(node: postcss.AtRule): boolean {
65+
if (node.params.startsWith(`${this._namespace}.core`)) {
66+
return true;
67+
}
68+
return false;
69+
}
70+
71+
/** Sets the namespace if the given at-rule if it is importing from @angular/material. */
72+
private _getNamespace(node: postcss.AtRule): void {
73+
if (!this._namespace && node.params.startsWith('@angular/material', 1)) {
74+
this._namespace = node.params.split(/\s+/)[2] || 'material';
75+
}
76+
}
77+
78+
/** Updates the source file with the given replacements. */
79+
private _replaceAt(
80+
filePath: WorkspacePath,
81+
offset: number,
82+
str: {old: string; new: string},
83+
): void {
84+
const index = this.fileSystem.read(filePath)!.indexOf(str.old, offset);
85+
this.fileSystem.edit(filePath).remove(index, str.old.length).insertRight(index, str.new);
86+
}
87+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import {UnitTestTree} from '@angular-devkit/schematics/testing';
2+
import {createTestCaseSetup} from '@angular/cdk/schematics/testing';
3+
import {join} from 'path';
4+
import {MIGRATION_PATH} from '../../paths';
5+
6+
const PROJECT_ROOT_DIR = '/projects/cdk-testing';
7+
const THEME_FILE_PATH = join(PROJECT_ROOT_DIR, 'src/theme.scss');
8+
9+
describe('v15 legacy components migration', () => {
10+
let tree: UnitTestTree;
11+
12+
/** Writes multiple lines to a file. */
13+
let writeLines: (path: string, lines: string[]) => void;
14+
15+
/** Reads multiple lines from a file. */
16+
let readLines: (path: string) => string[];
17+
18+
/** Runs the v15 migration on the test application. */
19+
let runMigration: () => Promise<{logOutput: string}>;
20+
21+
beforeEach(async () => {
22+
const testSetup = await createTestCaseSetup('migration-v19', MIGRATION_PATH, []);
23+
tree = testSetup.appTree;
24+
runMigration = testSetup.runFixers;
25+
readLines = (path: string) => tree.readContent(path).split('\n');
26+
writeLines = (path: string, lines: string[]) => testSetup.writeFile(path, lines.join('\n'));
27+
});
28+
29+
describe('style migrations', () => {
30+
async function runSassMigrationTest(ctx: string, opts: {old: string[]; new: string[]}) {
31+
writeLines(THEME_FILE_PATH, opts.old);
32+
await runMigration();
33+
expect(readLines(THEME_FILE_PATH)).withContext(ctx).toEqual(opts.new);
34+
}
35+
36+
it('should remove uses of the core mixin', async () => {
37+
await runSassMigrationTest('', {
38+
old: [`@use '@angular/material' as mat;`, `@include mat.core();`],
39+
new: [
40+
`@use '@angular/material' as mat;`,
41+
`@include mat.elevation-classes();`,
42+
`@include mat.app-background();`,
43+
],
44+
});
45+
46+
await runSassMigrationTest('w/ unique namespace', {
47+
old: [`@use '@angular/material' as material;`, `@include material.core();`],
48+
new: [
49+
`@use '@angular/material' as material;`,
50+
`@include material.elevation-classes();`,
51+
`@include material.app-background();`,
52+
],
53+
});
54+
55+
await runSassMigrationTest('w/ no namespace', {
56+
old: [`@use '@angular/material';`, `@include material.core();`],
57+
new: [
58+
`@use '@angular/material';`,
59+
`@include material.elevation-classes();`,
60+
`@include material.app-background();`,
61+
],
62+
});
63+
64+
await runSassMigrationTest('w/ unique whitespace', {
65+
old: [
66+
` @use '@angular/material' as material ; `,
67+
` @include material.core( ) ; `,
68+
],
69+
new: [
70+
` @use '@angular/material' as material ; `,
71+
` @include material.elevation-classes();`,
72+
` @include material.app-background(); `,
73+
],
74+
});
75+
});
76+
});
77+
});

0 commit comments

Comments
 (0)
Please sign in to comment.