Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(schematics): add v9 migration rules for icon and calendar #4467

Merged
merged 1 commit into from
Jan 8, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions schematics/ng-update/data/input-names.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,66 @@ export const inputNames: VersionChanges<InputNameUpgradeData> = {
}
}
]
},
{
pr : 'https://github.com/NG-ZORRO/ng-zorro-antd/pull/4375',
changes: [
{
replace : 'type',
replaceWith: 'nzType',
whitelist : {
attributes: ['nz-icon']
}
}
]
},
{
pr : 'https://github.com/NG-ZORRO/ng-zorro-antd/pull/4375',
changes: [
{
replace : 'iconfont',
replaceWith: 'nzIconfont',
whitelist : {
attributes: ['nz-icon']
}
}
]
},
{
pr : 'https://github.com/NG-ZORRO/ng-zorro-antd/pull/4375',
changes: [
{
replace : 'spin',
replaceWith: 'nzSpin',
whitelist : {
attributes: ['nz-icon']
}
}
]
},
{
pr : 'https://github.com/NG-ZORRO/ng-zorro-antd/pull/4375',
changes: [
{
replace : 'theme',
replaceWith: 'nzTheme',
whitelist : {
attributes: ['nz-icon']
}
}
]
},
{
pr : 'https://github.com/NG-ZORRO/ng-zorro-antd/pull/4375',
changes: [
{
replace : 'twoToneColor',
replaceWith: 'nzTwoToneColor',
whitelist : {
attributes: ['nz-icon']
}
}
]
}
]
};
14 changes: 11 additions & 3 deletions schematics/ng-update/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ import { Rule } from '@angular-devkit/schematics';
import { createUpgradeRule, TargetVersion } from '@angular/cdk/schematics';
import chalk from 'chalk';
import { ruleUpgradeData } from './upgrade-data';
import { CalendarTemplateRule } from './upgrade-rules/checks/calendar-input-rule';
import { DropdownClassRule } from './upgrade-rules/checks/dropdown-class-rule';
import { DropdownTemplateRule } from './upgrade-rules/checks/dropdown-template-rule';
import { IconTemplateRule } from './upgrade-rules/checks/icon-template-rule';
import { TooltipLikeTemplateRule } from './upgrade-rules/checks/tooltip-like-template-rule';

/** Entry point for the migration schematics with target of NG-ZORRO v7 */
Expand All @@ -14,11 +16,17 @@ export function updateToV7(): Rule {
/** Entry point for the migration schematics with target of NG-ZORRO v9 */
export function updateToV9(): Rule {
return createUpgradeRule(
TargetVersion.V9, [
TargetVersion.V9,
[
TooltipLikeTemplateRule,
DropdownTemplateRule,
DropdownClassRule
], ruleUpgradeData, postUpdate);
DropdownClassRule,
IconTemplateRule,
CalendarTemplateRule
],
ruleUpgradeData,
postUpdate
);
}

/** Post-update schematic to be called when update is finished. */
Expand Down
85 changes: 85 additions & 0 deletions schematics/ng-update/test-cases/v9/input-names-calendar.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { getSystemPath, normalize, virtualFs } from '@angular-devkit/core';
import { TempScopedNodeJsSyncHost } from '@angular-devkit/core/node/testing';
import { HostTree } from '@angular-devkit/schematics';
import { SchematicTestRunner, UnitTestTree } from '@angular-devkit/schematics/testing';
import * as shx from 'shelljs';

describe('calendar migration', () => {
let runner: SchematicTestRunner;
let host: TempScopedNodeJsSyncHost;
let tree: UnitTestTree;
let tmpDirPath: string;
let previousWorkingDir: string;
let warnOutput: string[];
let errorOutput: string[];

beforeEach(() => {
runner = new SchematicTestRunner('test', require.resolve('../../../migration.json'));
host = new TempScopedNodeJsSyncHost();
tree = new UnitTestTree(new HostTree(host));

writeFile('/tsconfig.json', JSON.stringify({
compilerOptions: {
experimentalDecorators: true,
lib: ['es2015']
}
}));
writeFile('/angular.json', JSON.stringify({
projects: {t: {architect: {build: {options: {tsConfig: './tsconfig.json'}}}}}
}));

warnOutput = [];
errorOutput = [];
runner.logger.subscribe(logEntry => {
if (logEntry.level === 'warn') {
warnOutput.push(logEntry.message);
} else if (logEntry.level === 'error') {
errorOutput.push(logEntry.message);
}
});

previousWorkingDir = shx.pwd();
tmpDirPath = getSystemPath(host.root);

shx.cd(tmpDirPath);

writeFakeAngular();
});

afterEach(() => {
shx.cd(previousWorkingDir);
shx.rm('-r', tmpDirPath);
});

function writeFakeAngular(): void { writeFile('/node_modules/@angular/core/index.d.ts', ``); }

function writeFile(filePath: string, contents: string): void {
host.sync.write(normalize(filePath), virtualFs.stringToFileBuffer(contents));
}

// tslint:disable-next-line:no-any
async function runMigration(): Promise<any> {
await runner.runSchematicAsync('migration-v9', {}, tree).toPromise();
}

describe('Calendar', () => {

it('should properly report invalid deprecated input', async() => {
writeFile('/index.ts', `;
import {Component} from '@angular/core'
@Component({
template: \`
<nz-calendar nzCard></nz-calendar>
\`
})
export class MyComp {
}`);
await runMigration();

expect(warnOutput).toContain( 'index.ts@5:24 - Found deprecated input "nzCard" component. ' +
'Use "nzFullscreen" to instead please.');
});

});

});
103 changes: 103 additions & 0 deletions schematics/ng-update/test-cases/v9/input-names-icon.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import { getSystemPath, normalize, virtualFs } from '@angular-devkit/core';
import { TempScopedNodeJsSyncHost } from '@angular-devkit/core/node/testing';
import { HostTree } from '@angular-devkit/schematics';
import { SchematicTestRunner, UnitTestTree } from '@angular-devkit/schematics/testing';
import * as shx from 'shelljs';

describe('icon migration', () => {
let runner: SchematicTestRunner;
let host: TempScopedNodeJsSyncHost;
let tree: UnitTestTree;
let tmpDirPath: string;
let previousWorkingDir: string;
let warnOutput: string[];
let errorOutput: string[];

beforeEach(() => {
runner = new SchematicTestRunner('test', require.resolve('../../../migration.json'));
host = new TempScopedNodeJsSyncHost();
tree = new UnitTestTree(new HostTree(host));

writeFile('/tsconfig.json', JSON.stringify({
compilerOptions: {
experimentalDecorators: true,
lib: ['es2015']
}
}));
writeFile('/angular.json', JSON.stringify({
projects: {t: {architect: {build: {options: {tsConfig: './tsconfig.json'}}}}}
}));

warnOutput = [];
errorOutput = [];
runner.logger.subscribe(logEntry => {
if (logEntry.level === 'warn') {
warnOutput.push(logEntry.message);
} else if (logEntry.level === 'error') {
errorOutput.push(logEntry.message);
}
});

previousWorkingDir = shx.pwd();
tmpDirPath = getSystemPath(host.root);

shx.cd(tmpDirPath);

writeFakeAngular();
});

afterEach(() => {
shx.cd(previousWorkingDir);
shx.rm('-r', tmpDirPath);
});

function writeFakeAngular(): void { writeFile('/node_modules/@angular/core/index.d.ts', ``); }

function writeFile(filePath: string, contents: string): void {
host.sync.write(normalize(filePath), virtualFs.stringToFileBuffer(contents));
}

// tslint:disable-next-line:no-any
async function runMigration(): Promise<any> {
await runner.runSchematicAsync('migration-v9', {}, tree).toPromise();
}

describe('Icon', () => {

it('should rename deprecated input names', async() => {
writeFile('/index.ts', `
import {Component} from '@angular/core';
@Component({template: \`
<i nz-icon [iconfont]="'value'" [spin]="true" [type]="'play'" theme="o"></i>
\`})
export class MyComp {
}
`);

await runMigration();
const content = tree.readContent('/index.ts');
expect(content).toContain(`[nzIconfont]="'value'"`);
expect(content).toContain(`[nzSpin]="true"`);
expect(content).toContain(`[nzType]="'play'"`);
expect(content).toContain(`nzTheme="o"`);
});

it('should properly report invalid deprecated css selector', async() => {
writeFile('/index.ts', `;
import {Component} from '@angular/core'
@Component({
template: \`
<i class="anticon play"></i>
\`
})
export class MyComp {
}`);
await runMigration();

expect(warnOutput).toContain( 'index.ts@5:14 - Found deprecated css selector "i.anticon" component. ' +
'Use "i[nz-icon]" to instead please.');
});

});

});
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { findInputsOnElementWithTag, MigrationRule, ResolvedResource, TargetVersion } from '@angular/cdk/schematics';
import { findElementWithClassName } from '../../../utils/ng-update/elements';

export class CalendarTemplateRule extends MigrationRule<null> {

ruleEnabled = this.targetVersion === TargetVersion.V9;

visitTemplate(template: ResolvedResource): void {

findInputsOnElementWithTag(template.content, 'nzCard', ['nz-calendar'])
.forEach(offset => {
this.failures.push({
filePath: template.filePath,
position: template.getCharacterAndLineOfPosition(offset),
message: `Found deprecated input "nzCard" component. Use "nzFullscreen" to instead please.`
});
})

}
}
20 changes: 20 additions & 0 deletions schematics/ng-update/upgrade-rules/checks/icon-template-rule.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { MigrationRule, ResolvedResource, TargetVersion } from '@angular/cdk/schematics';
import { findElementWithClassName } from '../../../utils/ng-update/elements';

export class IconTemplateRule extends MigrationRule<null> {

ruleEnabled = this.targetVersion === TargetVersion.V9;

visitTemplate(template: ResolvedResource): void {

findElementWithClassName(template.content, 'anticon', 'i')
.forEach(offset => {
this.failures.push({
filePath: template.filePath,
position: template.getCharacterAndLineOfPosition(offset),
message: `Found deprecated css selector "i.anticon" component. Use "i[nz-icon]" to instead please.`
});
})

}
}
25 changes: 25 additions & 0 deletions schematics/utils/ng-update/elements.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { parseFragment, DefaultTreeDocument, DefaultTreeElement } from 'parse5';

const hasClassName = (node: DefaultTreeElement, className: string) => {
return Array.isArray(node.attrs) && node.attrs.find(attr => attr.name === 'class' && attr.value.indexOf(className) !== -1)
};

export function findElementWithTag(html: string, tagName: string): number[] {
const document = parseFragment(html, {sourceCodeLocationInfo: true}) as DefaultTreeDocument;
const elements: DefaultTreeElement[] = [];
Expand All @@ -20,3 +24,24 @@ export function findElementWithTag(html: string, tagName: string): number[] {

return elements.map(element => element.sourceCodeLocation.startTag.startOffset)
}

export function findElementWithClassName(html: string, className: string, tagName: string): number[] {
const document = parseFragment(html, {sourceCodeLocationInfo: true}) as DefaultTreeDocument;
const elements: DefaultTreeElement[] = [];

const visitNodes = nodes => {
nodes.forEach(node => {
if (node.childNodes) {
visitNodes(node.childNodes);
}

if (hasClassName(node, className) && node.tagName && node.tagName.toLowerCase() === tagName.toLowerCase()) {
elements.push(node);
}
});
};

visitNodes(document.childNodes);

return elements.map(element => element.sourceCodeLocation.attrs.class.startOffset)
}