102 lines
3.1 KiB
TypeScript
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);
|
|
}
|
|
}
|