Skip to content

Commit

Permalink
feat(material/schematics): create v19 core removal schematic (#29768)
Browse files Browse the repository at this point in the history
* 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.
  • Loading branch information
wagnermaciel authored Sep 25, 2024
1 parent 6315e79 commit 4adc372
Show file tree
Hide file tree
Showing 3 changed files with 166 additions and 1 deletion.
3 changes: 2 additions & 1 deletion src/material/schematics/ng-update/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@ import {
} from '@angular/cdk/schematics';

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

const materialMigrations: NullableDevkitMigration[] = [];
const materialMigrations: NullableDevkitMigration[] = [MatCoreMigration];

/** Entry point for the migration schematics with target of Angular Material v19 */
export function updateToV19(): Rule {
Expand Down
87 changes: 87 additions & 0 deletions src/material/schematics/ng-update/migrations/mat-core-removal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/

import * as postcss from 'postcss';
import * as scss from 'postcss-scss';
import {
DevkitContext,
Migration,
ResolvedResource,
UpgradeData,
WorkspacePath,
} from '@angular/cdk/schematics';

export class MatCoreMigration extends Migration<UpgradeData, DevkitContext> {
override enabled = true;
private _namespace: string | undefined;

override init() {
// TODO: Check if mat-app-background is used in the application.
}

override visitStylesheet(stylesheet: ResolvedResource): void {
const processor = new postcss.Processor([
{
postcssPlugin: 'mat-core-removal-v19-plugin',
AtRule: {
use: node => this._getNamespace(node),
include: node => this._handleAtInclude(node, stylesheet.filePath),
},
},
]);
processor.process(stylesheet.content, {syntax: scss}).sync();
}

/** Handles updating the at-include rules of uses of the core mixin. */
private _handleAtInclude(node: postcss.AtRule, filePath: WorkspacePath): void {
if (!this._namespace || !node.source?.start || !node.source.end) {
return;
}

if (this._isMatCoreMixin(node)) {
const end = node.source.end.offset;
const start = node.source.start.offset;

const prefix = '\n' + (node.raws.before?.split('\n').pop() || '');
const snippet = prefix + node.source.input.css.slice(start, end);

const elevation = prefix + `@include ${this._namespace}.elevation-classes();`;
const background = prefix + `@include ${this._namespace}.app-background();`;

this._replaceAt(filePath, node.source.start.offset - prefix.length, {
old: snippet,
new: elevation + background,
});
}
}

/** Returns true if the given at-rule is a use of the core mixin. */
private _isMatCoreMixin(node: postcss.AtRule): boolean {
if (node.params.startsWith(`${this._namespace}.core`)) {
return true;
}
return false;
}

/** Sets the namespace if the given at-rule if it is importing from @angular/material. */
private _getNamespace(node: postcss.AtRule): void {
if (!this._namespace && node.params.startsWith('@angular/material', 1)) {
this._namespace = node.params.split(/\s+/)[2] || 'material';
}
}

/** Updates the source file with the given replacements. */
private _replaceAt(
filePath: WorkspacePath,
offset: number,
str: {old: string; new: string},
): void {
const index = this.fileSystem.read(filePath)!.indexOf(str.old, offset);
this.fileSystem.edit(filePath).remove(index, str.old.length).insertRight(index, str.new);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import {UnitTestTree} from '@angular-devkit/schematics/testing';
import {createTestCaseSetup} from '@angular/cdk/schematics/testing';
import {join} from 'path';
import {MIGRATION_PATH} from '../../paths';

const PROJECT_ROOT_DIR = '/projects/cdk-testing';
const THEME_FILE_PATH = join(PROJECT_ROOT_DIR, 'src/theme.scss');

describe('v15 legacy components migration', () => {
let tree: UnitTestTree;

/** Writes multiple lines to a file. */
let writeLines: (path: string, lines: string[]) => void;

/** Reads multiple lines from a file. */
let readLines: (path: string) => string[];

/** Runs the v15 migration on the test application. */
let runMigration: () => Promise<{logOutput: string}>;

beforeEach(async () => {
const testSetup = await createTestCaseSetup('migration-v19', MIGRATION_PATH, []);
tree = testSetup.appTree;
runMigration = testSetup.runFixers;
readLines = (path: string) => tree.readContent(path).split('\n');
writeLines = (path: string, lines: string[]) => testSetup.writeFile(path, lines.join('\n'));
});

describe('style migrations', () => {
async function runSassMigrationTest(ctx: string, opts: {old: string[]; new: string[]}) {
writeLines(THEME_FILE_PATH, opts.old);
await runMigration();
expect(readLines(THEME_FILE_PATH)).withContext(ctx).toEqual(opts.new);
}

it('should remove uses of the core mixin', async () => {
await runSassMigrationTest('', {
old: [`@use '@angular/material' as mat;`, `@include mat.core();`],
new: [
`@use '@angular/material' as mat;`,
`@include mat.elevation-classes();`,
`@include mat.app-background();`,
],
});

await runSassMigrationTest('w/ unique namespace', {
old: [`@use '@angular/material' as material;`, `@include material.core();`],
new: [
`@use '@angular/material' as material;`,
`@include material.elevation-classes();`,
`@include material.app-background();`,
],
});

await runSassMigrationTest('w/ no namespace', {
old: [`@use '@angular/material';`, `@include material.core();`],
new: [
`@use '@angular/material';`,
`@include material.elevation-classes();`,
`@include material.app-background();`,
],
});

await runSassMigrationTest('w/ unique whitespace', {
old: [
` @use '@angular/material' as material ; `,
` @include material.core( ) ; `,
],
new: [
` @use '@angular/material' as material ; `,
` @include material.elevation-classes();`,
` @include material.app-background(); `,
],
});
});
});
});

0 comments on commit 4adc372

Please sign in to comment.