diff --git a/src/material/schematics/ng-generate/mdc-migration/index.ts b/src/material/schematics/ng-generate/mdc-migration/index.ts index 62d9c28fa7e6..a9cefd662b6d 100644 --- a/src/material/schematics/ng-generate/mdc-migration/index.ts +++ b/src/material/schematics/ng-generate/mdc-migration/index.ts @@ -21,6 +21,7 @@ import {RuntimeCodeMigration} from './rules/ts-migration/runtime-migration'; import {Schema} from './schema'; import {TemplateMigration} from './rules/template-migration'; import {ThemingStylesMigration} from './rules/theming-styles'; +import {TypographyHierarchyTemplateMigrator} from './rules/components/typography-hierarchy/typography-hierarchy-template'; /** Groups of components that must be migrated together. */ const migrationGroups = [ @@ -44,6 +45,12 @@ const migrationGroups = [ ['tooltip'], ]; +const TYPOGRAPHY_HIERARCHY_MIGRATOR: ComponentMigrator = { + component: 'typography-hierarchy', + template: new TypographyHierarchyTemplateMigrator(), + styles: null!, // TODO +}; + function getComponentsToMigrate(requested: string[]): Set { const componentsToMigrate = new Set(requested); if (componentsToMigrate.has('all')) { @@ -94,7 +101,10 @@ export default function (options: Schema): Rule { const fileSystem = new DevkitFileSystem(tree); const analyzedFiles = new Set(); const componentsToMigrate = getComponentsToMigrate(options.components); - const migrators = MIGRATORS.filter(m => componentsToMigrate.has(m.component)); + const migrators = MIGRATORS.filter(m => componentsToMigrate.has(m.component)).concat( + // The typography hierarchy should always be migrated. + TYPOGRAPHY_HIERARCHY_MIGRATOR, + ); let success = true; if (options.directory) { diff --git a/src/material/schematics/ng-generate/mdc-migration/rules/components/typography-hierarchy/constants.ts b/src/material/schematics/ng-generate/mdc-migration/rules/components/typography-hierarchy/constants.ts new file mode 100644 index 000000000000..e2a2ed61a8c7 --- /dev/null +++ b/src/material/schematics/ng-generate/mdc-migration/rules/components/typography-hierarchy/constants.ts @@ -0,0 +1,31 @@ +/** + * @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.io/license + */ + +const mappings: [string, string][] = [ + ['display-4', 'headline-1'], + ['display-3', 'headline-2'], + ['display-2', 'headline-3'], + ['display-1', 'headline-4'], + ['headline', 'headline-5'], + ['title', 'headline-6'], + ['subheading-2', 'subtitle-1'], + ['body-2', 'subtitle-2'], + ['subheading-1', 'body-1'], + ['body-1', 'body-2'], +]; + +/** + * Mapping between the renamed legacy typography levels and their new non-legacy names. Based on + * the mappings in `private-typography-to-2018-config` from `core/typography/_typography.scss`. + */ +export const RENAMED_TYPOGRAPHY_LEVELS = new Map(mappings); + +/** Mapping between the renamed typography CSS classes and their non-legacy equivalents. */ +export const RENAMED_TYPOGRAPHY_CLASSES = new Map( + mappings.map(m => ['mat-' + m[0], 'mat-' + m[1]]), +); diff --git a/src/material/schematics/ng-generate/mdc-migration/rules/components/typography-hierarchy/typography-hierarchy-template.spec.ts b/src/material/schematics/ng-generate/mdc-migration/rules/components/typography-hierarchy/typography-hierarchy-template.spec.ts new file mode 100644 index 000000000000..4536c1b0958b --- /dev/null +++ b/src/material/schematics/ng-generate/mdc-migration/rules/components/typography-hierarchy/typography-hierarchy-template.spec.ts @@ -0,0 +1,103 @@ +import {createTestApp, patchDevkitTreeToExposeTypeScript} from '@angular/cdk/schematics/testing'; +import {SchematicTestRunner, UnitTestTree} from '@angular-devkit/schematics/testing'; +import {createNewTestRunner, migrateComponents, TEMPLATE_FILE} from '../test-setup-helper'; + +describe('typography hierarchy template migrator', () => { + let runner: SchematicTestRunner; + let cliAppTree: UnitTestTree; + + async function runMigrationTest(oldFileContent: string, newFileContent: string) { + cliAppTree.overwrite(TEMPLATE_FILE, oldFileContent); + const tree = await migrateComponents([], runner, cliAppTree); + expect(tree.readContent(TEMPLATE_FILE)).toBe(newFileContent); + } + + beforeEach(async () => { + runner = createNewTestRunner(); + cliAppTree = patchDevkitTreeToExposeTypeScript(await createTestApp(runner)); + }); + + it('should migrate legacy typography levels in static attributes', async () => { + await runMigrationTest( + ` +

Hello

+

Hello

+

Hello

+

Hello

+

Hello

+

Hello

+

Hello

+

Hello

+

Hello

+

Hello

+ `, + ` +

Hello

+

Hello

+

Hello

+

Hello

+

Hello

+

Hello

+

Hello

+

Hello

+

Hello

+

Hello

+ `, + ); + }); + + it('should migrate multiple static class usages of the legacy typography levels in a single file', async () => { + await runMigrationTest( + ` +

Hello

+

Hi

+ `, + ` +

Hello

+

Hi

+ `, + ); + }); + + it('should migrate legacy typography levels in class bindings', async () => { + await runMigrationTest( + ` +

Hello

+

Hello

+

Hello

+

Hello

+

Hello

+

Hello

+

Hello

+

Hello

+

Hello

+

Hello

+ `, + ` +

Hello

+

Hello

+

Hello

+

Hello

+

Hello

+

Hello

+

Hello

+

Hello

+

Hello

+

Hello

+ `, + ); + }); + + it('should migrate mixed class bindings and static class attribute', async () => { + await runMigrationTest( + ` +

Hello

+

Hello

+ `, + ` +

Hello

+

Hello

+ `, + ); + }); +}); diff --git a/src/material/schematics/ng-generate/mdc-migration/rules/components/typography-hierarchy/typography-hierarchy-template.ts b/src/material/schematics/ng-generate/mdc-migration/rules/components/typography-hierarchy/typography-hierarchy-template.ts new file mode 100644 index 000000000000..d98194768661 --- /dev/null +++ b/src/material/schematics/ng-generate/mdc-migration/rules/components/typography-hierarchy/typography-hierarchy-template.ts @@ -0,0 +1,72 @@ +/** + * @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.io/license + */ + +import * as compiler from '@angular/compiler'; +import {TemplateMigrator} from '../../template-migrator'; +import {visitElements} from '../../tree-traversal'; +import {Update} from '../../../../../migration-utilities'; +import {RENAMED_TYPOGRAPHY_CLASSES} from './constants'; + +export class TypographyHierarchyTemplateMigrator extends TemplateMigrator { + getUpdates(ast: compiler.ParsedTemplate): Update[] { + const updates: Update[] = []; + + visitElements(ast.nodes, node => { + this._addStaticClassUpdates(node, updates); + this._addClassBindingUpdates(node, updates); + }); + + return updates; + } + + /** Migrates the legacy typography classes in a static `class` attribute. */ + private _addStaticClassUpdates(node: compiler.TmplAstElement, updates: Update[]): void { + const classAttr = node.attributes.find(attr => attr.name === 'class'); + + if (classAttr && classAttr.keySpan && classAttr.valueSpan && classAttr.value.includes('mat-')) { + const classes = classAttr.value.split(' '); + let hasChanged = false; + + classes.forEach((current, index) => { + if (RENAMED_TYPOGRAPHY_CLASSES.has(current)) { + hasChanged = true; + classes[index] = RENAMED_TYPOGRAPHY_CLASSES.get(current)!; + } + }); + + if (hasChanged) { + updates.push({ + offset: classAttr.keySpan.start.offset, + updateFn: html => + html.slice(0, classAttr.valueSpan!.start.offset) + + classes.join(' ') + + html.slice(classAttr.valueSpan!.end.offset), + }); + } + } + } + + /** Migrates the legacy typography classes in `[class.x]` bindings. */ + private _addClassBindingUpdates(node: compiler.TmplAstElement, updates: Update[]): void { + node.inputs.forEach(input => { + if (input.type === compiler.BindingType.Class && RENAMED_TYPOGRAPHY_CLASSES.has(input.name)) { + updates.push({ + offset: input.keySpan.start.offset, + updateFn: html => { + return ( + html.slice(0, input.keySpan.start.offset) + + 'class.' + + RENAMED_TYPOGRAPHY_CLASSES.get(input.name)! + + html.slice(input.keySpan.end.offset) + ); + }, + }); + } + }); + } +} diff --git a/src/material/schematics/ng-generate/mdc-migration/rules/theming-styles.ts b/src/material/schematics/ng-generate/mdc-migration/rules/theming-styles.ts index 6a9f7ce35ac3..1313475ed632 100644 --- a/src/material/schematics/ng-generate/mdc-migration/rules/theming-styles.ts +++ b/src/material/schematics/ng-generate/mdc-migration/rules/theming-styles.ts @@ -11,26 +11,10 @@ import {SchematicContext} from '@angular-devkit/schematics'; import * as postcss from 'postcss'; import * as scss from 'postcss-scss'; import {ComponentMigrator, MIGRATORS} from '.'; +import {RENAMED_TYPOGRAPHY_LEVELS} from './components/typography-hierarchy/constants'; const COMPONENTS_MIXIN_NAME = /\.([^(;]*)/; -/** - * Mapping between the renamed legacy typography levels and their new non-legacy names. Based on - * the mappings in `private-typography-to-2018-config` from `core/typography/_typography.scss`. - */ -const RENAMED_TYPOGRAPHY_LEVELS = new Map([ - ['display-4', 'headline-1'], - ['display-3', 'headline-2'], - ['display-2', 'headline-3'], - ['display-1', 'headline-4'], - ['headline', 'headline-5'], - ['title', 'headline-6'], - ['subheading-2', 'subtitle-1'], - ['body-2', 'subtitle-2'], - ['subheading-1', 'body-1'], - ['body-1', 'body-2'], -]); - export class ThemingStylesMigration extends Migration { enabled = true; private _namespace: string;