Skip to content

Commit

Permalink
fix(material/schematics): migrate typography hierarchy classes in tem…
Browse files Browse the repository at this point in the history
…plates

Adds a migration that will rename usages of the classes coming from the `typography-hierarchy` mixin.

(cherry picked from commit 44f3a7a)
  • Loading branch information
crisbeto committed Nov 7, 2022
1 parent e9d5349 commit 9cdb4db
Show file tree
Hide file tree
Showing 5 changed files with 218 additions and 18 deletions.
12 changes: 11 additions & 1 deletion src/material/schematics/ng-generate/mdc-migration/index.ts
Expand Up @@ -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 = [
Expand All @@ -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<string> {
const componentsToMigrate = new Set<string>(requested);
if (componentsToMigrate.has('all')) {
Expand Down Expand Up @@ -94,7 +101,10 @@ export default function (options: Schema): Rule {
const fileSystem = new DevkitFileSystem(tree);
const analyzedFiles = new Set<WorkspacePath>();
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) {
Expand Down
@@ -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]]),
);
@@ -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(
`
<h1 class="mat-display-4">Hello</h1>
<h1 class="mat-display-3">Hello</h1>
<h1 class="mat-display-2">Hello</h1>
<h1 class="mat-display-1">Hello</h1>
<h1 class="mat-headline">Hello</h1>
<h1 class="mat-title">Hello</h1>
<h1 class="mat-subheading-2">Hello</h1>
<h1 class="mat-body-2">Hello</h1>
<h1 class="mat-subheading-1">Hello</h1>
<h1 class="mat-body-1">Hello</h1>
`,
`
<h1 class="mat-headline-1">Hello</h1>
<h1 class="mat-headline-2">Hello</h1>
<h1 class="mat-headline-3">Hello</h1>
<h1 class="mat-headline-4">Hello</h1>
<h1 class="mat-headline-5">Hello</h1>
<h1 class="mat-headline-6">Hello</h1>
<h1 class="mat-subtitle-1">Hello</h1>
<h1 class="mat-subtitle-2">Hello</h1>
<h1 class="mat-body-1">Hello</h1>
<h1 class="mat-body-2">Hello</h1>
`,
);
});

it('should migrate multiple static class usages of the legacy typography levels in a single file', async () => {
await runMigrationTest(
`
<h1 class="header mat-display-4" other-attr="foo">Hello</h1><div foo="bar"></div>
<h2 class="mat-display-3">Hi</h2>
`,
`
<h1 class="header mat-headline-1" other-attr="foo">Hello</h1><div foo="bar"></div>
<h2 class="mat-headline-2">Hi</h2>
`,
);
});

it('should migrate legacy typography levels in class bindings', async () => {
await runMigrationTest(
`
<h1 [class.mat-display-4]="expr">Hello</h1>
<h1 [class.mat-display-3]="expr">Hello</h1>
<h1 [class.mat-display-2]="expr">Hello</h1>
<h1 [class.mat-display-1]="expr">Hello</h1>
<h1 [class.mat-headline]="expr">Hello</h1>
<h1 [class.mat-title]="expr">Hello</h1>
<h1 [class.mat-subheading-2]="expr">Hello</h1>
<h1 [class.mat-body-2]="expr">Hello</h1>
<h1 [class.mat-subheading-1]="expr">Hello</h1>
<h1 [class.mat-body-1]="expr">Hello</h1>
`,
`
<h1 [class.mat-headline-1]="expr">Hello</h1>
<h1 [class.mat-headline-2]="expr">Hello</h1>
<h1 [class.mat-headline-3]="expr">Hello</h1>
<h1 [class.mat-headline-4]="expr">Hello</h1>
<h1 [class.mat-headline-5]="expr">Hello</h1>
<h1 [class.mat-headline-6]="expr">Hello</h1>
<h1 [class.mat-subtitle-1]="expr">Hello</h1>
<h1 [class.mat-subtitle-2]="expr">Hello</h1>
<h1 [class.mat-body-1]="expr">Hello</h1>
<h1 [class.mat-body-2]="expr">Hello</h1>
`,
);
});

it('should migrate mixed class bindings and static class attribute', async () => {
await runMigrationTest(
`
<h1 [class.mat-display-4]="someExpr" class="foo mat-subheading-2 bar">Hello</h1>
<h1 [class.mat-display-1]="someExpr">Hello</h1>
`,
`
<h1 [class.mat-headline-1]="someExpr" class="foo mat-subtitle-1 bar">Hello</h1>
<h1 [class.mat-headline-4]="someExpr">Hello</h1>
`,
);
});
});
@@ -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)
);
},
});
}
});
}
}
Expand Up @@ -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<ComponentMigrator[], SchematicContext> {
enabled = true;
private _namespace: string;
Expand Down

0 comments on commit 9cdb4db

Please sign in to comment.