From 52cb1cb1ac3ff87f0b64073e8f79d95eba2d56f9 Mon Sep 17 00:00:00 2001 From: Paul Gschwendtner Date: Thu, 9 Aug 2018 23:11:49 +0200 Subject: [PATCH] refactor(schematics): cleanup attribute selector rules (#12507) --- src/lib/schematics/update/material/color.ts | 2 +- .../update/material/extra-stylsheets.ts | 9 -- .../attributeSelectorsStringLiteralRule.ts | 55 ++++++++++++ .../attributeSelectorsStylesheetRule.ts | 78 ++++++++++++++++ .../attributeSelectorsTemplateRule.ts | 64 +++++++++++++ .../update/rules/checkTemplateMiscRule.ts | 16 ++-- ...itchStringLiteralAttributeSelectorsRule.ts | 52 ----------- .../rules/switchStringLiteralCssNamesRule.ts | 4 +- ...switchStringLiteralElementSelectorsRule.ts | 4 +- .../switchStylesheetAttributeSelectorsRule.ts | 90 ------------------- .../rules/switchStylesheetCssNamesRule.ts | 19 ++-- .../switchStylesheetElementSelectorsRule.ts | 4 +- .../rules/switchStylesheetInputNamesRule.ts | 19 ++-- .../rules/switchStylesheetOutputNamesRule.ts | 19 ++-- .../switchTemplateAttributeSelectorsRule.ts | 78 ---------------- .../rules/switchTemplateCssNamesRule.ts | 19 ++-- .../switchTemplateElementSelectorsRule.ts | 19 ++-- .../rules/switchTemplateExportAsNamesRule.ts | 19 ++-- .../rules/switchTemplateInputNamesRule.ts | 4 +- .../rules/switchTemplateOutputNamesRule.ts | 4 +- .../schematics/update/tslint/rule-failures.ts | 27 ++++++ .../schematics/update/typescript/literal.ts | 6 +- src/lib/schematics/update/update.ts | 13 ++- 23 files changed, 316 insertions(+), 308 deletions(-) delete mode 100644 src/lib/schematics/update/material/extra-stylsheets.ts create mode 100644 src/lib/schematics/update/rules/attribute-selectors/attributeSelectorsStringLiteralRule.ts create mode 100644 src/lib/schematics/update/rules/attribute-selectors/attributeSelectorsStylesheetRule.ts create mode 100644 src/lib/schematics/update/rules/attribute-selectors/attributeSelectorsTemplateRule.ts delete mode 100644 src/lib/schematics/update/rules/switchStringLiteralAttributeSelectorsRule.ts delete mode 100644 src/lib/schematics/update/rules/switchStylesheetAttributeSelectorsRule.ts delete mode 100644 src/lib/schematics/update/rules/switchTemplateAttributeSelectorsRule.ts create mode 100644 src/lib/schematics/update/tslint/rule-failures.ts diff --git a/src/lib/schematics/update/material/color.ts b/src/lib/schematics/update/material/color.ts index 98d52b37589..c8be943a043 100644 --- a/src/lib/schematics/update/material/color.ts +++ b/src/lib/schematics/update/material/color.ts @@ -16,7 +16,7 @@ const colorFns = { export function color(message: string): string { // 'r{{text}}' with red 'text', 'g{{text}}' with green 'text', and 'b{{text}}' with bold 'text'. - return message.replace(/(.)\{\{(.*?)\}\}/g, (_m, fnName, text) => { + return message.replace(/(.){{(.*?)}}/g, (_m, fnName, text) => { const fn = colorFns[fnName]; return fn ? fn(text) : text; }); diff --git a/src/lib/schematics/update/material/extra-stylsheets.ts b/src/lib/schematics/update/material/extra-stylsheets.ts deleted file mode 100644 index 80c79a5fedc..00000000000 --- a/src/lib/schematics/update/material/extra-stylsheets.ts +++ /dev/null @@ -1,9 +0,0 @@ -/** - * @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 - */ - -export const EXTRA_STYLESHEETS_GLOB_KEY = 'MD_EXTRA_STYLESHEETS_GLOB'; diff --git a/src/lib/schematics/update/rules/attribute-selectors/attributeSelectorsStringLiteralRule.ts b/src/lib/schematics/update/rules/attribute-selectors/attributeSelectorsStringLiteralRule.ts new file mode 100644 index 00000000000..f2200522d87 --- /dev/null +++ b/src/lib/schematics/update/rules/attribute-selectors/attributeSelectorsStringLiteralRule.ts @@ -0,0 +1,55 @@ +/** + * @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 { + attributeSelectors, + MaterialAttributeSelectorData, +} from '../../material/data/attribute-selectors'; +import {findAllSubstringIndices} from '../../typescript/literal'; +import * as ts from 'typescript'; + +/** + * Rule that walks through every string literal that is part of a call expression and + * switches deprecated attribute selectors to the updated selector. + */ +export class Rule extends Rules.AbstractRule { + apply(sourceFile: ts.SourceFile): RuleFailure[] { + return this.applyWithWalker(new Walker(sourceFile, this.getOptions())); + } +} + +class Walker extends RuleWalker { + + visitStringLiteral(literal: ts.StringLiteral) { + if (literal.parent && literal.parent.kind !== ts.SyntaxKind.CallExpression) { + return; + } + + const literalText = literal.getFullText(); + + attributeSelectors.forEach(selector => { + findAllSubstringIndices(literalText, selector.replace) + .map(offset => literal.getStart() + offset) + .map(start => new Replacement(start, selector.replace.length, selector.replaceWith)) + .forEach(replacement => this._addFailureWithReplacement(literal, replacement, selector)); + }); + } + + /** Adds an attribute selector failure with the given replacement at the specified node. */ + private _addFailureWithReplacement(node: ts.Node, replacement: Replacement, + selector: MaterialAttributeSelectorData) { + + this.addFailureAtNode( + node, + `Found deprecated attribute selector "${red('[' + selector.replace + ']')}" which ` + + `has been renamed to "${green('[' + selector.replaceWith + ']')}"`, + replacement); + } +} diff --git a/src/lib/schematics/update/rules/attribute-selectors/attributeSelectorsStylesheetRule.ts b/src/lib/schematics/update/rules/attribute-selectors/attributeSelectorsStylesheetRule.ts new file mode 100644 index 00000000000..47c52083e39 --- /dev/null +++ b/src/lib/schematics/update/rules/attribute-selectors/attributeSelectorsStylesheetRule.ts @@ -0,0 +1,78 @@ +/** + * @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 {attributeSelectors} from '../../material/data/attribute-selectors'; +import {ExternalResource} from '../../tslint/component-file'; +import {ComponentWalker} from '../../tslint/component-walker'; +import { + addFailureAtReplacement, + createExternalReplacementFailure, +} from '../../tslint/rule-failures'; +import {findAllSubstringIndices} from '../../typescript/literal'; +import * as ts from 'typescript'; + +/** + * Rule that walks through every stylesheet in the application and updates outdated + * attribute selectors to the updated selector. + */ +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 attribute selectors 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}[] = []; + + attributeSelectors.forEach(selector => { + const currentSelector = `[${selector.replace}]`; + const updatedSelector = `[${selector.replaceWith}]`; + + const failureMessage = `Found deprecated attribute selector "${red(currentSelector)}"` + + ` which has been renamed to "${green(updatedSelector)}"`; + + findAllSubstringIndices(stylesheetContent, currentSelector) + .map(offset => node.getStart() + offset) + .map(start => new Replacement(start, currentSelector.length, updatedSelector)) + .forEach(replacement => replacements.push({replacement, failureMessage})); + }); + + return replacements; + } +} diff --git a/src/lib/schematics/update/rules/attribute-selectors/attributeSelectorsTemplateRule.ts b/src/lib/schematics/update/rules/attribute-selectors/attributeSelectorsTemplateRule.ts new file mode 100644 index 00000000000..e11500d1528 --- /dev/null +++ b/src/lib/schematics/update/rules/attribute-selectors/attributeSelectorsTemplateRule.ts @@ -0,0 +1,64 @@ +/** + * @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 {attributeSelectors} from '../../material/data/attribute-selectors'; +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 component template and switches outdated attribute + * selectors to the updated selector. + */ +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 attribute selectors 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}[] = []; + + attributeSelectors.forEach(selector => { + const failureMessage = `Found deprecated attribute selector "[${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; + } +} diff --git a/src/lib/schematics/update/rules/checkTemplateMiscRule.ts b/src/lib/schematics/update/rules/checkTemplateMiscRule.ts index 7cdbc7e5fd5..3c6c5d16694 100644 --- a/src/lib/schematics/update/rules/checkTemplateMiscRule.ts +++ b/src/lib/schematics/update/rules/checkTemplateMiscRule.ts @@ -12,7 +12,7 @@ import * as ts from 'typescript'; import {findInputsOnElementWithTag, findOutputsOnElementWithTag} from '../html/angular'; import {ExternalResource} from '../tslint/component-file'; import {ComponentWalker} from '../tslint/component-walker'; -import {findAll} from '../typescript/literal'; +import {findAllSubstringIndices} from '../typescript/literal'; /** * Rule that walks through every component decorator and updates their inline or external @@ -48,12 +48,14 @@ export class CheckTemplateMiscWalker extends ComponentWalker { {start: number, end: number, message: string}[] { let failures: {message: string, start: number, end: number}[] = []; - failures = failures.concat(findAll(templateContent, 'cdk-focus-trap').map(offset => ({ - start: offset, - end: offset + 'cdk-focus-trap'.length, - message: `Found deprecated element selector "${red('cdk-focus-trap')}" which has been` + - ` changed to an attribute selector "${green('[cdkTrapFocus]')}"` - }))); + failures = failures.concat( + findAllSubstringIndices(templateContent, 'cdk-focus-trap').map(offset => ({ + start: offset, + end: offset + 'cdk-focus-trap'.length, + message: `Found deprecated element selector "${red('cdk-focus-trap')}" which has been` + + ` changed to an attribute selector "${green('[cdkTrapFocus]')}"` + })) + ); failures = failures.concat( findOutputsOnElementWithTag(templateContent, 'selectionChange', ['mat-list-option']) diff --git a/src/lib/schematics/update/rules/switchStringLiteralAttributeSelectorsRule.ts b/src/lib/schematics/update/rules/switchStringLiteralAttributeSelectorsRule.ts deleted file mode 100644 index 8d7c63a90ce..00000000000 --- a/src/lib/schematics/update/rules/switchStringLiteralAttributeSelectorsRule.ts +++ /dev/null @@ -1,52 +0,0 @@ -/** - * @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 {attributeSelectors} from '../material/data/attribute-selectors'; -import {findAll} from '../typescript/literal'; - -/** - * Rule that walks through every string literal, which includes the outdated Material name and - * is part of a call expression. Those string literals will be changed to the new name. - */ -export class Rule extends Rules.AbstractRule { - apply(sourceFile: ts.SourceFile): RuleFailure[] { - return this.applyWithWalker( - new SwitchStringLiteralAttributeSelectorsWalker(sourceFile, this.getOptions())); - } -} - -export class SwitchStringLiteralAttributeSelectorsWalker extends RuleWalker { - visitStringLiteral(stringLiteral: ts.StringLiteral) { - if (stringLiteral.parent && stringLiteral.parent.kind !== ts.SyntaxKind.CallExpression) { - return; - } - - let stringLiteralText = stringLiteral.getFullText(); - - attributeSelectors.forEach(selector => { - this.createReplacementsForOffsets(stringLiteral, selector, - findAll(stringLiteralText, selector.replace)).forEach(replacement => { - this.addFailureAtNode( - stringLiteral, - `Found deprecated attribute selector "${red('[' + selector.replace + ']')}" which has` + - ` been renamed to "${green('[' + selector.replaceWith + ']')}"`, - replacement); - }); - }); - } - - private createReplacementsForOffsets(node: ts.Node, - update: {replace: string, replaceWith: string}, - offsets: number[]): Replacement[] { - return offsets.map(offset => this.createReplacement( - node.getStart() + offset, update.replace.length, update.replaceWith)); - } -} diff --git a/src/lib/schematics/update/rules/switchStringLiteralCssNamesRule.ts b/src/lib/schematics/update/rules/switchStringLiteralCssNamesRule.ts index 08f2fbe55d3..f0b966ce909 100644 --- a/src/lib/schematics/update/rules/switchStringLiteralCssNamesRule.ts +++ b/src/lib/schematics/update/rules/switchStringLiteralCssNamesRule.ts @@ -10,7 +10,7 @@ import {green, red} from 'chalk'; import {Replacement, RuleFailure, Rules, RuleWalker} from 'tslint'; import * as ts from 'typescript'; import {cssNames} from '../material/data/css-names'; -import {findAll} from '../typescript/literal'; +import {findAllSubstringIndices} from '../typescript/literal'; /** * Rule that walks through every string literal, which includes the outdated Material name and @@ -34,7 +34,7 @@ export class SwitchStringLiteralCssNamesWalker extends RuleWalker { cssNames.forEach(name => { if (!name.whitelist || name.whitelist.strings) { this.createReplacementsForOffsets(stringLiteral, name, - findAll(stringLiteralText, name.replace)).forEach(replacement => { + findAllSubstringIndices(stringLiteralText, name.replace)).forEach(replacement => { this.addFailureAtNode( stringLiteral, `Found deprecated CSS class "${red(name.replace)}" which has been renamed to` + diff --git a/src/lib/schematics/update/rules/switchStringLiteralElementSelectorsRule.ts b/src/lib/schematics/update/rules/switchStringLiteralElementSelectorsRule.ts index f2bacc1f807..b28ad79a8c8 100644 --- a/src/lib/schematics/update/rules/switchStringLiteralElementSelectorsRule.ts +++ b/src/lib/schematics/update/rules/switchStringLiteralElementSelectorsRule.ts @@ -10,7 +10,7 @@ import {green, red} from 'chalk'; import {Replacement, RuleFailure, Rules, RuleWalker} from 'tslint'; import * as ts from 'typescript'; import {elementSelectors} from '../material/data/element-selectors'; -import {findAll} from '../typescript/literal'; +import {findAllSubstringIndices} from '../typescript/literal'; /** * Rule that walks through every string literal, which includes the outdated Material name and @@ -33,7 +33,7 @@ export class SwitchStringLiteralElementSelectorsWalker extends RuleWalker { elementSelectors.forEach(selector => { this.createReplacementsForOffsets(stringLiteral, selector, - findAll(stringLiteralText, selector.replace)).forEach(replacement => { + findAllSubstringIndices(stringLiteralText, selector.replace)).forEach(replacement => { this.addFailureAtNode( stringLiteral, `Found deprecated element selector "${red(selector.replace)}" which has been` + diff --git a/src/lib/schematics/update/rules/switchStylesheetAttributeSelectorsRule.ts b/src/lib/schematics/update/rules/switchStylesheetAttributeSelectorsRule.ts deleted file mode 100644 index 66561be1933..00000000000 --- a/src/lib/schematics/update/rules/switchStylesheetAttributeSelectorsRule.ts +++ /dev/null @@ -1,90 +0,0 @@ -/** - * @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 {attributeSelectors} from '../material/data/attribute-selectors'; -import {ExternalResource} from '../tslint/component-file'; -import {ComponentWalker} from '../tslint/component-walker'; -import {findAll} from '../typescript/literal'; -import * as ts from 'typescript'; - -/** - * Rule that walks through every component decorator and updates their inline or external - * stylesheets. - */ -export class Rule extends Rules.AbstractRule { - apply(sourceFile: ts.SourceFile): RuleFailure[] { - return this.applyWithWalker( - new SwitchStylesheetAtributeSelectorsWalker(sourceFile, this.getOptions())); - } -} - -export class SwitchStylesheetAtributeSelectorsWalker 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(stylesheet: ts.StringLiteral) { - this.replaceNamesInStylesheet(stylesheet, stylesheet.getText()).forEach(replacement => { - const fix = replacement.replacement; - const ruleFailure = new RuleFailure(stylesheet.getSourceFile(), fix.start, fix.end, - replacement.message, this.getRuleName(), fix); - this.addFailure(ruleFailure); - }); - } - - visitExternalStylesheet(stylesheet: ExternalResource) { - this.replaceNamesInStylesheet(stylesheet, stylesheet.getFullText()).forEach(replacement => { - const fix = replacement.replacement; - const ruleFailure = new RuleFailure(stylesheet, fix.start + 1, fix.end + 1, - replacement.message, this.getRuleName(), fix); - this.addFailure(ruleFailure); - }); - } - - /** - * Replaces the outdated name in the stylesheet with the new one and returns an updated - * stylesheet. - */ - private replaceNamesInStylesheet(node: ts.Node, stylesheetContent: string): - {message: string, replacement: Replacement}[] { - const replacements: {message: string, replacement: Replacement}[] = []; - - attributeSelectors.forEach(selector => { - const bracketedSelector = { - replace: `[${selector.replace}]`, - replaceWith: `[${selector.replaceWith}]` - }; - this.createReplacementsForOffsets(node, bracketedSelector, - findAll(stylesheetContent, bracketedSelector.replace)).forEach(replacement => { - replacements.push({ - message: `Found deprecated attribute selector "${red(bracketedSelector.replace)}"` + - ` which has been renamed to "${green(bracketedSelector.replaceWith)}"`, - replacement - }); - }); - }); - - return replacements; - } - - private createReplacementsForOffsets(node: ts.Node, - update: {replace: string, replaceWith: string}, - offsets: number[]): Replacement[] { - return offsets.map(offset => this.createReplacement( - node.getStart() + offset, update.replace.length, update.replaceWith)); - } -} diff --git a/src/lib/schematics/update/rules/switchStylesheetCssNamesRule.ts b/src/lib/schematics/update/rules/switchStylesheetCssNamesRule.ts index 45d04c569e3..1ff4f664180 100644 --- a/src/lib/schematics/update/rules/switchStylesheetCssNamesRule.ts +++ b/src/lib/schematics/update/rules/switchStylesheetCssNamesRule.ts @@ -13,7 +13,7 @@ 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 {findAll} from '../typescript/literal'; +import {findAllSubstringIndices} from '../typescript/literal'; /** * Rule that walks through every component decorator and updates their inline or external @@ -64,14 +64,15 @@ export class SwitchStylesheetCssNamesWalker extends ComponentWalker { cssNames.forEach(name => { if (!name.whitelist || name.whitelist.css) { - this.createReplacementsForOffsets(node, name, findAll(stylesheetContent, name.replace)) - .forEach(replacement => { - replacements.push({ - message: `Found CSS class "${red(name.replace)}" which has been renamed to` + - ` "${green(name.replaceWith)}"`, - replacement - }); - }); + const foundOffsets = findAllSubstringIndices(stylesheetContent, name.replace); + + this.createReplacementsForOffsets(node, name, foundOffsets).forEach(replacement => { + replacements.push({ + message: `Found CSS class "${red(name.replace)}" which has been renamed to` + + ` "${green(name.replaceWith)}"`, + replacement + }); + }); } }); diff --git a/src/lib/schematics/update/rules/switchStylesheetElementSelectorsRule.ts b/src/lib/schematics/update/rules/switchStylesheetElementSelectorsRule.ts index a1eda922382..705cacbeec4 100644 --- a/src/lib/schematics/update/rules/switchStylesheetElementSelectorsRule.ts +++ b/src/lib/schematics/update/rules/switchStylesheetElementSelectorsRule.ts @@ -12,7 +12,7 @@ import {IOptions, Replacement, RuleFailure, Rules} from 'tslint'; import {elementSelectors} from '../material/data/element-selectors'; import {ExternalResource} from '../tslint/component-file'; import {ComponentWalker} from '../tslint/component-walker'; -import {findAll} from '../typescript/literal'; +import {findAllSubstringIndices} from '../typescript/literal'; import * as ts from 'typescript'; /** @@ -65,7 +65,7 @@ export class SwitchStylesheetElementSelectorsWalker extends ComponentWalker { elementSelectors.forEach(selector => { this.createReplacementsForOffsets(node, selector, - findAll(stylesheetContent, selector.replace)).forEach(replacement => { + findAllSubstringIndices(stylesheetContent, selector.replace)).forEach(replacement => { replacements.push({ message: `Found deprecated element selector "${red(selector.replace)}" which has` + ` been renamed to "${green(selector.replaceWith)}"`, diff --git a/src/lib/schematics/update/rules/switchStylesheetInputNamesRule.ts b/src/lib/schematics/update/rules/switchStylesheetInputNamesRule.ts index 264fea4b1da..03f3fb3dac9 100644 --- a/src/lib/schematics/update/rules/switchStylesheetInputNamesRule.ts +++ b/src/lib/schematics/update/rules/switchStylesheetInputNamesRule.ts @@ -12,7 +12,7 @@ import {IOptions, Replacement, RuleFailure, Rules} from 'tslint'; import {inputNames} from '../material/data/input-names'; import {ExternalResource} from '../tslint/component-file'; import {ComponentWalker} from '../tslint/component-walker'; -import {findAll} from '../typescript/literal'; +import {findAllSubstringIndices} from '../typescript/literal'; import * as ts from 'typescript'; /** @@ -66,14 +66,15 @@ export class SwitchStylesheetInputNamesWalker extends ComponentWalker { inputNames.forEach(name => { if (!name.whitelist || name.whitelist.css) { const bracketedName = {replace: `[${name.replace}]`, replaceWith: `[${name.replaceWith}]`}; - this.createReplacementsForOffsets(node, name, - findAll(stylesheetContent, bracketedName.replace)).forEach(replacement => { - replacements.push({ - message: `Found deprecated @Input() "${red(name.replace)}" which has been renamed` + - ` to "${green(name.replaceWith)}"`, - replacement - }); - }); + const foundOffsets = findAllSubstringIndices(stylesheetContent, bracketedName.replace); + + this.createReplacementsForOffsets(node, name, foundOffsets).forEach(replacement => { + replacements.push({ + message: `Found deprecated @Input() "${red(name.replace)}" which has been renamed` + + ` to "${green(name.replaceWith)}"`, + replacement + }); + }); } }); diff --git a/src/lib/schematics/update/rules/switchStylesheetOutputNamesRule.ts b/src/lib/schematics/update/rules/switchStylesheetOutputNamesRule.ts index ecb68faba59..868fc67cfd0 100644 --- a/src/lib/schematics/update/rules/switchStylesheetOutputNamesRule.ts +++ b/src/lib/schematics/update/rules/switchStylesheetOutputNamesRule.ts @@ -12,7 +12,7 @@ import {IOptions, Replacement, RuleFailure, Rules} from 'tslint'; import {outputNames} from '../material/data/output-names'; import {ExternalResource} from '../tslint/component-file'; import {ComponentWalker} from '../tslint/component-walker'; -import {findAll} from '../typescript/literal'; +import {findAllSubstringIndices} from '../typescript/literal'; import * as ts from 'typescript'; /** @@ -66,14 +66,15 @@ export class SwitchStylesheetOutputNamesWalker extends ComponentWalker { outputNames.forEach(name => { if (!name.whitelist || name.whitelist.css) { const bracketedName = {replace: `[${name.replace}]`, replaceWith: `[${name.replaceWith}]`}; - this.createReplacementsForOffsets(node, name, - findAll(stylesheetContent, bracketedName.replace)).forEach(replacement => { - replacements.push({ - message: `Found deprecated @Output() "${red(name.replace)}" which has been` + - ` renamed to "${green(name.replaceWith)}"`, - replacement - }); - }); + const foundOffsets = findAllSubstringIndices(stylesheetContent, bracketedName.replace); + + this.createReplacementsForOffsets(node, name, foundOffsets).forEach(replacement => { + replacements.push({ + message: `Found deprecated @Output() "${red(name.replace)}" which has been` + + ` renamed to "${green(name.replaceWith)}"`, + replacement + }); + }); } }); diff --git a/src/lib/schematics/update/rules/switchTemplateAttributeSelectorsRule.ts b/src/lib/schematics/update/rules/switchTemplateAttributeSelectorsRule.ts deleted file mode 100644 index 28f568ca405..00000000000 --- a/src/lib/schematics/update/rules/switchTemplateAttributeSelectorsRule.ts +++ /dev/null @@ -1,78 +0,0 @@ -/** - * @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 {attributeSelectors} from '../material/data/attribute-selectors'; -import {ExternalResource} from '../tslint/component-file'; -import {ComponentWalker} from '../tslint/component-walker'; -import {findAll} from '../typescript/literal'; - -/** - * Rule that walks through every component decorator and updates their inline or external - * templates. - */ -export class Rule extends Rules.AbstractRule { - apply(sourceFile: ts.SourceFile): RuleFailure[] { - return this.applyWithWalker( - new SwitchTemplateAttributeSelectorsWalker(sourceFile, this.getOptions())); - } -} - -export class SwitchTemplateAttributeSelectorsWalker extends ComponentWalker { - visitInlineTemplate(template: ts.StringLiteral) { - this.replaceNamesInTemplate(template, template.getText()).forEach(replacement => { - const fix = replacement.replacement; - const ruleFailure = new RuleFailure(template.getSourceFile(), fix.start, fix.end, - replacement.message, this.getRuleName(), fix); - this.addFailure(ruleFailure); - }); - } - - visitExternalTemplate(template: ExternalResource) { - this.replaceNamesInTemplate(template, template.getFullText()).forEach(replacement => { - const fix = replacement.replacement; - const ruleFailure = new RuleFailure(template, fix.start + 1, fix.end + 1, - replacement.message, this.getRuleName(), fix); - this.addFailure(ruleFailure); - }); - } - - /** - * Replaces the outdated name in the template with the new one and returns an updated template. - */ - private replaceNamesInTemplate(node: ts.Node, templateContent: string): - {message: string, replacement: Replacement}[] { - const replacements: {message: string, replacement: Replacement}[] = []; - - attributeSelectors.forEach(selector => { - // Being more aggressive with that replacement here allows us to also handle inline - // style elements. Normally we would check if the selector is surrounded by the HTML tag - // characters. - this.createReplacementsForOffsets(node, selector, findAll(templateContent, selector.replace)) - .forEach(replacement => { - replacements.push({ - message: `Found deprecated attribute selector` + - ` "${red('[' + selector.replace + ']')}" which has been renamed to` + - ` "${green('[' + selector.replaceWith + ']')}"`, - replacement - }); - }); - }); - - return replacements; - } - - private createReplacementsForOffsets(node: ts.Node, - update: {replace: string, replaceWith: string}, - offsets: number[]): Replacement[] { - return offsets.map(offset => this.createReplacement( - node.getStart() + offset, update.replace.length, update.replaceWith)); - } -} diff --git a/src/lib/schematics/update/rules/switchTemplateCssNamesRule.ts b/src/lib/schematics/update/rules/switchTemplateCssNamesRule.ts index adeb4e34910..c9e2e8c7c85 100644 --- a/src/lib/schematics/update/rules/switchTemplateCssNamesRule.ts +++ b/src/lib/schematics/update/rules/switchTemplateCssNamesRule.ts @@ -12,7 +12,7 @@ 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 {findAll} from '../typescript/literal'; +import {findAllSubstringIndices} from '../typescript/literal'; /** * Rule that walks through every component decorator and updates their inline or external @@ -52,14 +52,15 @@ export class SwitchTemplateCaaNamesWalker extends ComponentWalker { cssNames.forEach(name => { if (!name.whitelist || name.whitelist.html) { - this.createReplacementsForOffsets(node, name, findAll(templateContent, name.replace)) - .forEach(replacement => { - replacements.push({ - message: `Found deprecated CSS class "${red(name.replace)}" which has been` + - ` renamed to "${green(name.replaceWith)}"`, - replacement - }); - }); + const foundOffsets = findAllSubstringIndices(templateContent, name.replace); + + this.createReplacementsForOffsets(node, name, foundOffsets).forEach(replacement => { + replacements.push({ + message: `Found deprecated CSS class "${red(name.replace)}" which has been` + + ` renamed to "${green(name.replaceWith)}"`, + replacement + }); + }); } }); diff --git a/src/lib/schematics/update/rules/switchTemplateElementSelectorsRule.ts b/src/lib/schematics/update/rules/switchTemplateElementSelectorsRule.ts index f69e8c98ff4..f1dfa09b784 100644 --- a/src/lib/schematics/update/rules/switchTemplateElementSelectorsRule.ts +++ b/src/lib/schematics/update/rules/switchTemplateElementSelectorsRule.ts @@ -12,7 +12,7 @@ import * as ts from 'typescript'; import {elementSelectors} from '../material/data/element-selectors'; import {ExternalResource} from '../tslint/component-file'; import {ComponentWalker} from '../tslint/component-walker'; -import {findAll} from '../typescript/literal'; +import {findAllSubstringIndices} from '../typescript/literal'; /** * Rule that walks through every component decorator and updates their inline or external @@ -55,14 +55,15 @@ export class SwitchTemplateElementSelectorsWalker extends ComponentWalker { // Being more aggressive with that replacement here allows us to also handle inline // style elements. Normally we would check if the selector is surrounded by the HTML tag // characters. - this.createReplacementsForOffsets(node, selector, findAll(templateContent, selector.replace)) - .forEach(replacement => { - replacements.push({ - message: `Found deprecated element selector "${red(selector.replace)}" which has` + - ` been renamed to "${green(selector.replaceWith)}"`, - replacement - }); - }); + const foundOffsets = findAllSubstringIndices(templateContent, selector.replace); + + this.createReplacementsForOffsets(node, selector, foundOffsets).forEach(replacement => { + replacements.push({ + message: `Found deprecated element selector "${red(selector.replace)}" which has` + + ` been renamed to "${green(selector.replaceWith)}"`, + replacement + }); + }); }); return replacements; diff --git a/src/lib/schematics/update/rules/switchTemplateExportAsNamesRule.ts b/src/lib/schematics/update/rules/switchTemplateExportAsNamesRule.ts index 04537e787d2..04e0bc75b00 100644 --- a/src/lib/schematics/update/rules/switchTemplateExportAsNamesRule.ts +++ b/src/lib/schematics/update/rules/switchTemplateExportAsNamesRule.ts @@ -12,7 +12,7 @@ import * as ts from 'typescript'; import {exportAsNames} from '../material/data/export-as-names'; import {ExternalResource} from '../tslint/component-file'; import {ComponentWalker} from '../tslint/component-walker'; -import {findAll} from '../typescript/literal'; +import {findAllSubstringIndices} from '../typescript/literal'; /** * Rule that walks through every component decorator and updates their inline or external @@ -52,14 +52,15 @@ export class SwitchTemplateExportAsNamesWalker extends ComponentWalker { const replacements: {message: string, replacement: Replacement}[] = []; exportAsNames.forEach(name => { - this.createReplacementsForOffsets(node, name, findAll(templateContent, name.replace)) - .forEach(replacement => { - replacements.push({ - message: `Found deprecated exportAs reference "${red(name.replace)}" which has been` + - ` renamed to "${green(name.replaceWith)}"`, - replacement - }); - }); + const foundOffsets = findAllSubstringIndices(templateContent, name.replace); + + this.createReplacementsForOffsets(node, name, foundOffsets).forEach(replacement => { + replacements.push({ + message: `Found deprecated exportAs reference "${red(name.replace)}" which has been` + + ` renamed to "${green(name.replaceWith)}"`, + replacement + }); + }); }); return replacements; diff --git a/src/lib/schematics/update/rules/switchTemplateInputNamesRule.ts b/src/lib/schematics/update/rules/switchTemplateInputNamesRule.ts index a530327c27b..c3e97c5b2bb 100644 --- a/src/lib/schematics/update/rules/switchTemplateInputNamesRule.ts +++ b/src/lib/schematics/update/rules/switchTemplateInputNamesRule.ts @@ -13,7 +13,7 @@ import {findInputsOnElementWithAttr, findInputsOnElementWithTag} from '../html/a import {inputNames} from '../material/data/input-names'; import {ExternalResource} from '../tslint/component-file'; import {ComponentWalker} from '../tslint/component-walker'; -import {findAll} from '../typescript/literal'; +import {findAllSubstringIndices} from '../typescript/literal'; /** * Rule that walks through every component decorator and updates their inline or external @@ -62,7 +62,7 @@ export class SwitchTemplateInputNamesWalker extends ComponentWalker { templateContent, name.replace, name.whitelist.elements)); } if (!name.whitelist) { - offsets = offsets.concat(findAll(templateContent, name.replace)); + offsets = offsets.concat(findAllSubstringIndices(templateContent, name.replace)); } this.createReplacementsForOffsets(node, name, offsets).forEach(replacement => { replacements.push({ diff --git a/src/lib/schematics/update/rules/switchTemplateOutputNamesRule.ts b/src/lib/schematics/update/rules/switchTemplateOutputNamesRule.ts index bc453bb1836..7e77b9d4b7f 100644 --- a/src/lib/schematics/update/rules/switchTemplateOutputNamesRule.ts +++ b/src/lib/schematics/update/rules/switchTemplateOutputNamesRule.ts @@ -13,7 +13,7 @@ import {findOutputsOnElementWithAttr, findOutputsOnElementWithTag} from '../html import {outputNames} from '../material/data/output-names'; import {ExternalResource} from '../tslint/component-file'; import {ComponentWalker} from '../tslint/component-walker'; -import {findAll} from '../typescript/literal'; +import {findAllSubstringIndices} from '../typescript/literal'; /** * Rule that walks through every component decorator and updates their inline or external @@ -62,7 +62,7 @@ export class SwitchTemplateOutputNamesWalker extends ComponentWalker { templateContent, name.replace, name.whitelist.elements)); } if (!name.whitelist) { - offsets = offsets.concat(findAll(templateContent, name.replace)); + offsets = offsets.concat(findAllSubstringIndices(templateContent, name.replace)); } this.createReplacementsForOffsets(node, name, offsets).forEach(replacement => { replacements.push({ diff --git a/src/lib/schematics/update/tslint/rule-failures.ts b/src/lib/schematics/update/tslint/rule-failures.ts new file mode 100644 index 00000000000..a65d69fe1e6 --- /dev/null +++ b/src/lib/schematics/update/tslint/rule-failures.ts @@ -0,0 +1,27 @@ +/** + * @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 {Replacement, RuleFailure, RuleWalker} from 'tslint'; +import {ExternalResource} from './component-file'; + +/** + * Creates a standalone rule failure that can be used to apply a replacement for an + * external resource from the custom ComponentWalker. + */ +export function createExternalReplacementFailure(node: ExternalResource, failureMessage: string, + ruleName: string, replacement: Replacement) { + + return new RuleFailure(node, replacement.start, replacement.end, failureMessage, + ruleName, replacement); +} + +/** Adds a failure to the given walker at the location of the specified replacement. */ +export function addFailureAtReplacement(walker: RuleWalker, failureMessage: string, + replacement: Replacement) { + walker.addFailureAt(replacement.start, replacement.end, failureMessage, replacement); +} diff --git a/src/lib/schematics/update/typescript/literal.ts b/src/lib/schematics/update/typescript/literal.ts index fe8c3d49deb..c9ce90a0523 100644 --- a/src/lib/schematics/update/typescript/literal.ts +++ b/src/lib/schematics/update/typescript/literal.ts @@ -13,11 +13,11 @@ export function getLiteralTextWithoutQuotes(literal: ts.StringLiteral) { return literal.getText().substring(1, literal.getText().length - 1); } -/** Method that can be used to replace all search occurrences in a string. */ -export function findAll(str: string, search: string): number[] { +/** Finds all start indices of the given search string in the input string. */ +export function findAllSubstringIndices(input: string, search: string): number[] { const result: number[] = []; let i = -1; - while ((i = str.indexOf(search, i + 1)) !== -1) { + while ((i = input.indexOf(search, i + 1)) !== -1) { result.push(i); } return result; diff --git a/src/lib/schematics/update/update.ts b/src/lib/schematics/update/update.ts index d33296f232d..e15f91cfa1b 100644 --- a/src/lib/schematics/update/update.ts +++ b/src/lib/schematics/update/update.ts @@ -26,26 +26,31 @@ export default function(): Rule { for (const tsconfig of allTsConfigPaths) { // Run the update tslint rules. tslintFixTasks.push(context.addTask(new TslintFixTask({ - rulesDirectory: path.join(__dirname, 'rules/'), + rulesDirectory: [ + path.join(__dirname, 'rules/'), + path.join(__dirname, 'rules/attribute-selectors'), + ], rules: { // Automatic fixes. 'switch-identifiers': true, 'switch-property-names': true, - 'switch-string-literal-attribute-selectors': true, 'switch-string-literal-css-names': true, 'switch-string-literal-element-selectors': true, - 'switch-stylesheet-attribute-selectors': true, 'switch-stylesheet-css-names': true, 'switch-stylesheet-element-selectors': true, 'switch-stylesheet-input-names': true, 'switch-stylesheet-output-names': true, - 'switch-template-attribute-selectors': true, 'switch-template-css-names': true, 'switch-template-element-selectors': true, 'switch-template-export-as-names': true, 'switch-template-input-names': true, 'switch-template-output-names': true, + // Attribute selector update rules. + 'attribute-selectors-string-literal': true, + 'attribute-selectors-stylesheet': true, + 'attribute-selectors-template': true, + // Additional issues we can detect but not automatically fix. 'check-class-declaration-misc': true, 'check-identifier-misc': true,