Skip to content
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
64 changes: 44 additions & 20 deletions src/lib/schematics/update/rules/checkInheritanceRule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,35 +9,59 @@
import {bold, green, red} from 'chalk';
import {ProgramAwareRuleWalker, RuleFailure, Rules} from 'tslint';
import * as ts from 'typescript';
import {propertyNames} from '../material/data/property-names';
import {MaterialPropertyNameData, propertyNames} from '../material/data/property-names';

/**
* Rule that walks through every property access expression and updates properties that have
* been changed in favor of the new name.
* Map of classes that have been updated. Each class name maps to the according property change
* data.
*/
const changedClassesMap = new Map<string, MaterialPropertyNameData>();

propertyNames.filter(data => data.whitelist && data.whitelist.classes).forEach(data => {
data.whitelist.classes.forEach(name => changedClassesMap.set(name, data));
});

/**
* Rule that identifies class declarations that extend CDK or Material classes and had
* a public property change.
*/
export class Rule extends Rules.TypedRule {
applyWithProgram(sourceFile: ts.SourceFile, program: ts.Program): RuleFailure[] {
return this.applyWithWalker(
new CheckInheritanceWalker(sourceFile, this.getOptions(), program));
return this.applyWithWalker(new Walker(sourceFile, this.getOptions(), program));
}
}

export class CheckInheritanceWalker extends ProgramAwareRuleWalker {
visitClassDeclaration(declaration: ts.ClassDeclaration) {
// Check if user is extending an Angular Material class whose properties have changed.
const type = this.getTypeChecker().getTypeAtLocation(declaration.name);
const baseTypes = this.getTypeChecker().getBaseTypes(type as ts.InterfaceType);
baseTypes.forEach(t => {
const propertyData = propertyNames.find(
data => data.whitelist && new Set(data.whitelist.classes).has(t.symbol.name));
if (propertyData) {
this.addFailureAtNode(
declaration,
`Found class "${bold(declaration.name.text)}" which extends class` +
` "${bold(t.symbol.name)}". Please note that the base class property` +
` "${red(propertyData.replace)}" has changed to "${green(propertyData.replaceWith)}".` +
` You may need to update your class as well`);
export class Walker extends ProgramAwareRuleWalker {

visitClassDeclaration(node: ts.ClassDeclaration) {
const baseTypes = this._determineBaseTypes(node);

if (!baseTypes) {
return;
}

baseTypes.forEach(typeName => {
const data = changedClassesMap.get(typeName);

if (data) {
this.addFailureAtNode(node,
`Found class "${bold(node.name.text)}" which extends class ` +
`"${bold(typeName)}". Please note that the base class property ` +
`"${red(data.replace)}" has changed to "${green(data.replaceWith)}". ` +
`You may need to update your class as well`);
}
});
}

private _determineBaseTypes(node: ts.ClassDeclaration): string[] | null {
if (!node.heritageClauses) {
return null;
}

return node.heritageClauses
.reduce((types, clause) => types.concat(clause.types), [])
.map(typeExpression => typeExpression.expression)
.filter(expression => expression && ts.isIdentifier(expression))
.map(identifier => identifier.text);
}
}
2 changes: 1 addition & 1 deletion src/lib/schematics/update/update.spec.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
describe('material-nav-schematic', () => {});
describe('Material Update Tool', () => {});