fix(ng-update): do not throw if imports without named bindings are used (#12866) (#12984)

Currently if someone uses a namespace import or an import without named bindings in an Angular application, the `ng-update` schematics from Angular Material will fail due to missing safety checks before casting.

Fixes #11571
This commit is contained in:
Paul Gschwendtner
2018-09-05 19:29:55 +02:00
committed by Jeremy Elbourn
parent 4ddbde93ea
commit fe642115de
2 changed files with 35 additions and 14 deletions
@@ -27,6 +27,10 @@ export function isMaterialExportDeclaration(node: ts.Node) {
/** Whether the declaration is part of Angular Material. */
function isMaterialDeclaration(declaration: ts.ImportDeclaration | ts.ExportDeclaration) {
if (!declaration.moduleSpecifier) {
return false;
}
const moduleSpecifier = declaration.moduleSpecifier.getText();
return moduleSpecifier.indexOf(materialModuleSpecifier) !== -1 ||
moduleSpecifier.indexOf(cdkModuleSpecifier) !== -1;
@@ -12,26 +12,43 @@ import * as ts from 'typescript';
import {isMaterialImportDeclaration} from '../material/typescript-specifiers';
/**
* Rule that walks through every identifier that is part of Angular Material and replaces the
* outdated name with the new one.
* Rule that detects import declarations that refer to outdated identifiers from Angular Material
* or the CDK which cannot be updated automatically.
*/
export class Rule extends Rules.TypedRule {
applyWithProgram(sourceFile: ts.SourceFile, program: ts.Program): RuleFailure[] {
return this.applyWithWalker(new CheckImportMiscWalker(sourceFile, this.getOptions(), program));
return this.applyWithWalker(new Walker(sourceFile, this.getOptions(), program));
}
}
export class CheckImportMiscWalker extends ProgramAwareRuleWalker {
visitImportDeclaration(declaration: ts.ImportDeclaration) {
if (isMaterialImportDeclaration(declaration)) {
declaration.importClause.namedBindings.forEachChild(n => {
let importName = n.getFirstToken() && n.getFirstToken().getText();
if (importName === 'SHOW_ANIMATION' || importName === 'HIDE_ANIMATION') {
this.addFailureAtNode(
n,
`Found deprecated symbol "${red(importName)}" which has been removed`);
}
});
export class Walker extends ProgramAwareRuleWalker {
visitImportDeclaration(node: ts.ImportDeclaration) {
if (!isMaterialImportDeclaration(node) ||
!node.importClause ||
!node.importClause.namedBindings) {
return;
}
const namedBindings = node.importClause.namedBindings;
if (ts.isNamedImports(namedBindings)) {
this._checkAnimationConstants(namedBindings);
}
}
/**
* Checks for named imports that refer to the deleted animation constants.
* https://github.com/angular/material2/commit/9f3bf274c4f15f0b0fbd8ab7dbf1a453076e66d9
*/
private _checkAnimationConstants(namedImports: ts.NamedImports) {
namedImports.elements.filter(element => ts.isIdentifier(element.name)).forEach(element => {
const importName = element.name.text;
if (importName === 'SHOW_ANIMATION' || importName === 'HIDE_ANIMATION') {
this.addFailureAtNode(element,
`Found deprecated symbol "${red(importName)}" which has been removed`);
}
});
}
}