sass-references/angular-material/material/schematics/ng-update/migrations/mat-core-removal.ts

102 lines
3.1 KiB
TypeScript

/**
* @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';
const MATERIAL_IMPORT_PATH = '@angular/material';
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 {
// Avoid parsing the template Material isn't used.
if (!stylesheet.content.includes(MATERIAL_IMPORT_PATH)) {
return;
}
try {
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();
} catch (e) {
this.logger.warn(
`Failed to migrate usages of mat.core in ${stylesheet.filePath} due to error:`,
);
this.logger.warn(e + '');
}
}
/** 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(MATERIAL_IMPORT_PATH, 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);
}
}