Skip to content

fix(ng-update): form-field css name incorrectly updated #12768

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

Merged
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
12 changes: 4 additions & 8 deletions src/lib/schematics/update/material/data/css-names.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ export interface MaterialCssNameData {
replaceWith: string;
/** Whitelist where this replacement is made. If omitted it is made in all files. */
whitelist?: {
/** Replace this name in CSS files. */
css?: boolean,
/** Replace this name in stylesheet files. */
stylesheet?: boolean,
/** Replace this name in HTML files. */
html?: boolean,
/** Replace this name in TypeScript strings. */
Expand All @@ -32,10 +32,6 @@ export const cssNames = transformChanges<MaterialCssNameData>([
replace: 'mat-form-field-placeholder',
replaceWith: 'mat-form-field-label'
},
{
replace: 'mat-form-field-placeholder-wrapper',
replaceWith: 'mat-form-field-label-wrapper'
},
{
replace: 'mat-input-container',
replaceWith: 'mat-form-field'
Expand Down Expand Up @@ -96,13 +92,13 @@ export const cssNames = transformChanges<MaterialCssNameData>([
},

{
pr: 'https://github.com/angular/material2/pull/10325',
pr: 'https://github.com/angular/material2/pull/10430',
changes: [
{
replace: '$mat-font-family',
replaceWith: "Roboto, 'Helvetica Neue', sans-serif",
whitelist: {
css: true
stylesheet: true
}
}
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,22 @@
*/

import {bold, red} from 'chalk';
import {ProgramAwareRuleWalker, RuleFailure, Rules} from 'tslint';
import {RuleFailure, Rules, RuleWalker} from 'tslint';
import * as ts from 'typescript';

/**
* Rule that walks through every identifier that is part of Angular Material and replaces the
* outdated name with the new one.
* Rule that looks for class name identifiers that have been removed but cannot be
* automatically migrated.
*/
export class Rule extends Rules.TypedRule {
applyWithProgram(sourceFile: ts.SourceFile, program: ts.Program): RuleFailure[] {
return this.applyWithWalker(
new CheckIdentifierMiscWalker(sourceFile, this.getOptions(), program));
export class Rule extends Rules.AbstractRule {

apply(sourceFile: ts.SourceFile): RuleFailure[] {
return this.applyWithWalker(new Walker(sourceFile, this.getOptions()));
}
}

export class CheckIdentifierMiscWalker extends ProgramAwareRuleWalker {
export class Walker extends RuleWalker {

visitIdentifier(identifier: ts.Identifier) {
if (identifier.getText() === 'MatDrawerToggleResult') {
this.addFailureAtNode(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,16 @@
import {green, red} from 'chalk';
import {RuleFailure, Rules, RuleWalker} from 'tslint';
import * as ts from 'typescript';
import {classNames} from '../material/data/class-names';
import {classNames} from '../../material/data/class-names';
import {
isMaterialExportDeclaration,
isMaterialImportDeclaration,
} from '../material/typescript-specifiers';
} from '../../material/typescript-specifiers';
import {
isExportSpecifierNode,
isImportSpecifierNode,
isNamespaceImportNode,
} from '../typescript/imports';
} from '../../typescript/imports';

/**
* Rule that walks through every identifier that is part of Angular Material and replaces the
Expand All @@ -27,11 +27,11 @@ import {
export class Rule extends Rules.AbstractRule {

apply(sourceFile: ts.SourceFile): RuleFailure[] {
return this.applyWithWalker(new SwitchIdentifiersWalker(sourceFile, this.getOptions()));
return this.applyWithWalker(new Walker(sourceFile, this.getOptions()));
}
}

export class SwitchIdentifiersWalker extends RuleWalker {
export class Walker extends RuleWalker {

/**
* List of identifier names that have been imported from `@angular/material` or `@angular/cdk`
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/**
* @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 {green, red} from 'chalk';
import {Replacement, RuleFailure, Rules, RuleWalker} from 'tslint';
import * as ts from 'typescript';
import {cssNames, MaterialCssNameData} from '../../material/data/css-names';
import {findAllSubstringIndices} from '../../typescript/literal';

/**
* Rule that walks through every string literal that is wrapped inside of a call expression.
* All string literals which include an outdated CSS class name will be migrated.
*/
export class Rule extends Rules.AbstractRule {
apply(sourceFile: ts.SourceFile): RuleFailure[] {
return this.applyWithWalker(new Walker(sourceFile, this.getOptions()));
}
}

export class Walker extends RuleWalker {

visitStringLiteral(node: ts.StringLiteral) {
if (node.parent && node.parent.kind !== ts.SyntaxKind.CallExpression) {
return;
}

const textContent = node.getFullText();

cssNames.forEach(name => {
if (name.whitelist && !name.whitelist.strings) {
return;
}

findAllSubstringIndices(textContent, name.replace)
.map(offset => node.getStart() + offset)
.map(start => new Replacement(start, name.replace.length, name.replaceWith))
.forEach(replacement => this._addFailureWithReplacement(node, replacement, name));
});
}

/** Adds a css name failure with the given replacement at the specified node. */
private _addFailureWithReplacement(node: ts.Node, replacement: Replacement,
name: MaterialCssNameData) {
this.addFailureAtNode(node, `Found deprecated CSS class "${red(name.replace)}" which has ` +
`been renamed to "${green(name.replaceWith)}"`, replacement);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/**
* @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 {green, red} from 'chalk';
import {sync as globSync} from 'glob';
import {IOptions, Replacement, RuleFailure, Rules} from 'tslint';
import * as ts from 'typescript';
import {cssNames} from '../../material/data/css-names';
import {ExternalResource} from '../../tslint/component-file';
import {ComponentWalker} from '../../tslint/component-walker';
import {
addFailureAtReplacement,
createExternalReplacementFailure,
} from '../../tslint/rule-failures';
import {findAllSubstringIndices} from '../../typescript/literal';

/**
* Rule that walks through every inline or external CSs stylesheet and updates outdated
* CSS classes.
*/
export class Rule extends Rules.AbstractRule {
apply(sourceFile: ts.SourceFile): RuleFailure[] {
return this.applyWithWalker(new Walker(sourceFile, this.getOptions()));
}
}

export class Walker extends ComponentWalker {

constructor(sourceFile: ts.SourceFile, options: IOptions) {
// In some applications, developers will have global stylesheets that are not specified in any
// Angular component. Therefore we glob up all css and scss files outside of node_modules and
// dist and check them as well.
const extraFiles = globSync('!(node_modules|dist)/**/*.+(css|scss)');
super(sourceFile, options, extraFiles);
extraFiles.forEach(styleUrl => this._reportExternalStyle(styleUrl));
}


visitInlineStylesheet(literal: ts.StringLiteral) {
this._createReplacementsForContent(literal, literal.getText())
.forEach(data => addFailureAtReplacement(this, data.failureMessage, data.replacement));
}

visitExternalStylesheet(node: ExternalResource) {
this._createReplacementsForContent(node, node.getFullText())
.map(data => createExternalReplacementFailure(node, data.failureMessage,
this.getRuleName(), data.replacement))
.forEach(failure => this.addFailure(failure));
}

/**
* Searches for outdated CSs classes in the specified content and creates replacements
* with the according messages that can be added to a rule failure.
*/
private _createReplacementsForContent(node: ts.Node, stylesheetContent: string) {
const replacements: {failureMessage: string, replacement: Replacement}[] = [];

cssNames.forEach(name => {
if (name.whitelist && !name.whitelist.stylesheet) {
return;
}

const failureMessage = `Found deprecated CSS class "${red(name.replace)}" ` +
`which has been renamed to "${green(name.replaceWith)}"`;

findAllSubstringIndices(stylesheetContent, name.replace)
.map(offset => node.getStart() + offset)
.map(start => new Replacement(start, name.replace.length, name.replaceWith))
.forEach(replacement => replacements.push({replacement, failureMessage}));
});

return replacements;
}
}
68 changes: 68 additions & 0 deletions src/lib/schematics/update/rules/css-names/cssNamesTemplateRule.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/**
* @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 {green, red} from 'chalk';
import {Replacement, RuleFailure, Rules} from 'tslint';
import * as ts from 'typescript';
import {cssNames} from '../../material/data/css-names';
import {ExternalResource} from '../../tslint/component-file';
import {ComponentWalker} from '../../tslint/component-walker';
import {
addFailureAtReplacement,
createExternalReplacementFailure,
} from '../../tslint/rule-failures';
import {findAllSubstringIndices} from '../../typescript/literal';

/**
* Rule that walks through every inline or external HTML template and updates outdated
* CSS classes.
*/
export class Rule extends Rules.AbstractRule {
apply(sourceFile: ts.SourceFile): RuleFailure[] {
return this.applyWithWalker(new Walker(sourceFile, this.getOptions()));
}
}

export class Walker extends ComponentWalker {

visitInlineTemplate(template: ts.StringLiteral) {
this._createReplacementsForContent(template, template.getText())
.forEach(data => addFailureAtReplacement(this, data.failureMessage, data.replacement));
}

visitExternalTemplate(template: ExternalResource) {
this._createReplacementsForContent(template, template.getFullText())
.map(data => createExternalReplacementFailure(template, data.failureMessage,
this.getRuleName(), data.replacement))
.forEach(failure => this.addFailure(failure));
}

/**
* Searches for outdated css names in the specified content and creates replacements
* with the according messages that can be added to a rule failure.
*/
private _createReplacementsForContent(node: ts.Node, templateContent: string) {
const replacements: {failureMessage: string, replacement: Replacement}[] = [];

cssNames.forEach(selector => {
if (selector.whitelist && !selector.whitelist.html) {
return;
}

const failureMessage = `Found deprecated CSS class "${red(selector.replace)}"` +
` which has been renamed to "${green(selector.replaceWith)}"`;

findAllSubstringIndices(templateContent, selector.replace)
.map(offset => node.getStart() + offset)
.map(start => new Replacement(start, selector.replace.length, selector.replaceWith))
.forEach(replacement => replacements.push({replacement, failureMessage}));
});

return replacements;
}
}
54 changes: 0 additions & 54 deletions src/lib/schematics/update/rules/switchStringLiteralCssNamesRule.ts

This file was deleted.

Loading