diff --git a/src/cdk/schematics/ng-update/test-cases/v6/attribute-selectors_expected_output.ts b/src/cdk/schematics/ng-update/test-cases/v6/attribute-selectors_expected_output.ts
index f162584e9570..9fe7afb87205 100644
--- a/src/cdk/schematics/ng-update/test-cases/v6/attribute-selectors_expected_output.ts
+++ b/src/cdk/schematics/ng-update/test-cases/v6/attribute-selectors_expected_output.ts
@@ -1,4 +1,5 @@
import {Component} from '@angular/core';
+import * as core from '@angular/core';
import {By} from '@angular/platform-browser';
const a = By.css('[cdkPortalOutlet]');
@@ -9,7 +10,7 @@ const b = By.css('[cdkPortalOutlet]');
const c = 'cdkPortalHost';
const d = 'portalHost';
-@Component({
+@core.Component({
template: `
@@ -31,4 +32,4 @@ class E {}
'div[cdkPortalOutlet] {color: blue}'
]
})
-class F {}
\ No newline at end of file
+class F {}
diff --git a/src/cdk/schematics/ng-update/test-cases/v6/attribute-selectors_input.ts b/src/cdk/schematics/ng-update/test-cases/v6/attribute-selectors_input.ts
index 2ba661c35539..0a12f229ec71 100644
--- a/src/cdk/schematics/ng-update/test-cases/v6/attribute-selectors_input.ts
+++ b/src/cdk/schematics/ng-update/test-cases/v6/attribute-selectors_input.ts
@@ -1,4 +1,5 @@
import {Component} from '@angular/core';
+import * as core from '@angular/core';
import {By} from '@angular/platform-browser';
const a = By.css('[cdkPortalHost]');
@@ -9,7 +10,7 @@ const b = By.css('[portalHost]');
const c = 'cdkPortalHost';
const d = 'portalHost';
-@Component({
+@core.Component({
template: `
@@ -31,4 +32,4 @@ class E {}
'div[portalHost] {color: blue}'
]
})
-class F {}
\ No newline at end of file
+class F {}
diff --git a/src/cdk/schematics/testing/test-case-setup.ts b/src/cdk/schematics/testing/test-case-setup.ts
index 6ff1d0c640e9..67d65f9554a1 100644
--- a/src/cdk/schematics/testing/test-case-setup.ts
+++ b/src/cdk/schematics/testing/test-case-setup.ts
@@ -23,6 +23,9 @@ const TEST_CASE_INPUT_SUFFIX = '_input.ts';
/** Suffix that indicates whether a given file is an expected output of a test case. */
const TEST_CASE_OUTPUT_SUFFIX = '_expected_output.ts';
+/** Name of the folder that can contain test case files which should not run automatically. */
+const MISC_FOLDER_NAME = 'misc';
+
/** Reads the UTF8 content of the specified file. Normalizes the path and ensures that */
export function readFileContent(filePath: string): string {
return readFileSync(filePath, 'utf8');
@@ -135,7 +138,8 @@ export function findBazelVersionTestCases(basePath: string) {
// test case files by using "glob" and store them in our result map.
if (!manifestPath) {
const runfilesBaseDir = join(runfilesDir!, basePath);
- const inputFiles = globSync(`**/*${TEST_CASE_INPUT_SUFFIX}`, {cwd: runfilesBaseDir});
+ const inputFiles = globSync(`**/!(${MISC_FOLDER_NAME})/*${TEST_CASE_INPUT_SUFFIX}`,
+ {cwd: runfilesBaseDir});
inputFiles.forEach(inputFile => {
// The target version of an input file will be determined from the first
@@ -158,9 +162,13 @@ export function findBazelVersionTestCases(basePath: string) {
// In case the mapped runfile starts with the specified base path and ends with "_input.ts",
// we store it in our result map because we assume that this is a test case.
if (runfilePath.startsWith(basePath) && runfilePath.endsWith(TEST_CASE_INPUT_SUFFIX)) {
+ const pathSegments = relative(basePath, runfilePath).split(sep);
+ if (pathSegments.includes(MISC_FOLDER_NAME)) {
+ return;
+ }
// The target version of an input file will be determined from the first
// path segment. (e.g. "v6/my_rule_input.ts" will be for "v6")
- const targetVersion = relative(basePath, runfilePath).split(sep)[0];
+ const targetVersion = pathSegments[0];
testCasesMap.set(targetVersion, (testCasesMap.get(targetVersion) || []).concat(realPath));
}
});
diff --git a/src/cdk/schematics/update-tool/public-api.ts b/src/cdk/schematics/update-tool/public-api.ts
index db4d27fcab0f..313b293bca0f 100644
--- a/src/cdk/schematics/update-tool/public-api.ts
+++ b/src/cdk/schematics/update-tool/public-api.ts
@@ -11,3 +11,5 @@ export * from './target-version';
export * from './version-changes';
export * from './migration-rule';
export * from './component-resource-collector';
+export * from './utils/imports';
+export * from './utils/decorators';
diff --git a/src/cdk/schematics/update-tool/target-version.ts b/src/cdk/schematics/update-tool/target-version.ts
index 3566bee4cbaf..d5c7e0a1081f 100644
--- a/src/cdk/schematics/update-tool/target-version.ts
+++ b/src/cdk/schematics/update-tool/target-version.ts
@@ -21,5 +21,5 @@ export enum TargetVersion {
*/
export function getAllVersionNames(): string[] {
return Object.keys(TargetVersion)
- .filter(enumValue => typeof TargetVersion[enumValue] === 'number');
+ .filter(enumValue => typeof TargetVersion[enumValue] === 'string');
}
diff --git a/src/cdk/schematics/update-tool/utils/decorators.ts b/src/cdk/schematics/update-tool/utils/decorators.ts
index 659ea6ecc13f..28e48d797046 100644
--- a/src/cdk/schematics/update-tool/utils/decorators.ts
+++ b/src/cdk/schematics/update-tool/utils/decorators.ts
@@ -17,7 +17,6 @@ export type CallExpressionDecorator = ts.Decorator&{
export interface NgDecorator {
name: string;
node: CallExpressionDecorator;
- importNode: ts.ImportDeclaration;
}
/**
@@ -27,23 +26,23 @@ export interface NgDecorator {
export function getAngularDecorators(
typeChecker: ts.TypeChecker, decorators: ReadonlyArray): NgDecorator[] {
return decorators.map(node => ({node, importData: getCallDecoratorImport(typeChecker, node)}))
- .filter(({importData}) => importData && importData.importModule.startsWith('@angular/'))
- .map(({node, importData}) => ({
- node: node as CallExpressionDecorator,
- name: importData!.name,
- importNode: importData!.node
- }));
+ .filter(({importData}) => importData && importData.moduleName.startsWith('@angular/'))
+ .map(
+ ({node, importData}) =>
+ ({node: node as CallExpressionDecorator, name: importData!.symbolName}));
}
export function getCallDecoratorImport(
typeChecker: ts.TypeChecker, decorator: ts.Decorator): Import|null {
- // Note that this does not cover the edge case where decorators are called from
- // a namespace import: e.g. "@core.Component()". This is not handled by Ngtsc either.
- if (!ts.isCallExpression(decorator.expression) ||
- !ts.isIdentifier(decorator.expression.expression)) {
+ if (!ts.isCallExpression(decorator.expression)) {
return null;
}
-
- const identifier = decorator.expression.expression;
- return getImportOfIdentifier(typeChecker, identifier);
+ const valueExpr = decorator.expression.expression;
+ let identifier: ts.Identifier|null = null;
+ if (ts.isIdentifier(valueExpr)) {
+ identifier = valueExpr;
+ } else if (ts.isPropertyAccessExpression(valueExpr) && ts.isIdentifier(valueExpr.name)) {
+ identifier = valueExpr.name;
+ }
+ return identifier ? getImportOfIdentifier(identifier, typeChecker) : null;
}
diff --git a/src/cdk/schematics/update-tool/utils/imports.ts b/src/cdk/schematics/update-tool/utils/imports.ts
index 66450acad5fd..fccdb289d25a 100644
--- a/src/cdk/schematics/update-tool/utils/imports.ts
+++ b/src/cdk/schematics/update-tool/utils/imports.ts
@@ -8,39 +8,117 @@
import * as ts from 'typescript';
-export type Import = {
- name: string,
- importModule: string,
- node: ts.ImportDeclaration
-};
-
-/** Gets import information about the specified identifier by using the type checker. */
-export function getImportOfIdentifier(typeChecker: ts.TypeChecker, node: ts.Identifier): Import|
+/** Interface describing a resolved import. */
+export interface Import {
+ /** Name of the imported symbol. */
+ symbolName: string;
+ /** Module name from which the symbol has been imported. */
+ moduleName: string;
+}
+
+
+/** Resolves the import of the specified identifier. */
+export function getImportOfIdentifier(node: ts.Identifier, typeChecker: ts.TypeChecker): Import|
null {
- const symbol = typeChecker.getSymbolAtLocation(node);
+ // Free standing identifiers which resolve to an import will be handled
+ // as direct imports. e.g. "@Component()" where "Component" is an identifier
+ // referring to an import specifier.
+ const directImport = getSpecificImportOfIdentifier(node, typeChecker);
+ if (directImport !== null) {
+ return directImport;
+ } else if (ts.isQualifiedName(node.parent) && node.parent.right === node) {
+ // Determines the import of a qualified name. e.g. "let t: core.Component". In that
+ // case, the import of the most left identifier will be determined ("core").
+ const qualifierRoot = getQualifiedNameRoot(node.parent);
+ if (qualifierRoot) {
+ const moduleName = getImportOfNamespacedIdentifier(qualifierRoot, typeChecker);
+ if (moduleName) {
+ return {moduleName, symbolName: node.text};
+ }
+ }
+ } else if (ts.isPropertyAccessExpression(node.parent) && node.parent.name === node) {
+ // Determines the import of a property expression. e.g. "@core.Component". In that
+ // case, the import of the most left identifier will be determined ("core").
+ const rootIdentifier = getPropertyAccessRoot(node.parent);
+ if (rootIdentifier) {
+ const moduleName = getImportOfNamespacedIdentifier(rootIdentifier, typeChecker);
+ if (moduleName) {
+ return {moduleName, symbolName: node.text};
+ }
+ }
+ }
+ return null;
+}
+/**
+ * Resolves the import of the specified identifier. Expects the identifier to resolve
+ * to a fine-grained import declaration with import specifiers.
+ */
+function getSpecificImportOfIdentifier(node: ts.Identifier, typeChecker: ts.TypeChecker): Import|
+ null {
+ const symbol = typeChecker.getSymbolAtLocation(node);
if (!symbol || !symbol.declarations || !symbol.declarations.length) {
return null;
}
-
- const decl = symbol.declarations[0];
-
- if (!ts.isImportSpecifier(decl)) {
+ const declaration = symbol.declarations[0];
+ if (!ts.isImportSpecifier(declaration)) {
return null;
}
-
- // Since "decl" is an import specifier, we can walk up three times to get a reference
+ // Since the declaration is an import specifier, we can walk up three times to get a reference
// to the import declaration node (NamedImports -> ImportClause -> ImportDeclaration).
- const importDecl = decl.parent.parent.parent;
-
+ const importDecl = declaration.parent.parent.parent;
if (!ts.isStringLiteral(importDecl.moduleSpecifier)) {
return null;
}
-
return {
- // Handles aliased imports: e.g. "import {Component as myComp} from ...";
- name: decl.propertyName ? decl.propertyName.text : decl.name.text,
- importModule: importDecl.moduleSpecifier.text,
- node: importDecl
+ moduleName: importDecl.moduleSpecifier.text,
+ symbolName: declaration.propertyName ? declaration.propertyName.text : declaration.name.text
};
}
+
+/**
+ * Resolves the import of the specified identifier. Expects the identifier to
+ * resolve to a namespaced import declaration. e.g. "import * as core from ...".
+ */
+function getImportOfNamespacedIdentifier(node: ts.Identifier, typeChecker: ts.TypeChecker): string|
+ null {
+ const symbol = typeChecker.getSymbolAtLocation(node);
+ if (!symbol || !symbol.declarations || !symbol.declarations.length) {
+ return null;
+ }
+ const declaration = symbol.declarations[0];
+ if (!ts.isNamespaceImport(declaration)) {
+ return null;
+ }
+ // Since the declaration is a namespace import, we can walk up three times to get a reference
+ // to the import declaration node (NamespaceImport -> ImportClause -> ImportDeclaration).
+ const importDecl = declaration.parent.parent;
+ if (!ts.isStringLiteral(importDecl.moduleSpecifier)) {
+ return null;
+ }
+
+ return importDecl.moduleSpecifier.text;
+}
+
+
+/**
+ * Gets the root identifier of a qualified type chain. For example: "core.GestureConfig"
+ * will return the "core" identifier. Allowing us to find the import of "core".
+ */
+function getQualifiedNameRoot(name: ts.QualifiedName): ts.Identifier|null {
+ while (ts.isQualifiedName(name.left)) {
+ name = name.left;
+ }
+ return ts.isIdentifier(name.left) ? name.left : null;
+}
+
+/**
+ * Gets the root identifier of a property access chain. For example: "core.GestureConfig"
+ * will return the "core" identifier. Allowing us to find the import of "core".
+ */
+function getPropertyAccessRoot(node: ts.PropertyAccessExpression): ts.Identifier|null {
+ while (ts.isPropertyAccessExpression(node.expression)) {
+ node = node.expression;
+ }
+ return ts.isIdentifier(node.expression) ? node.expression : null;
+}
diff --git a/src/material/schematics/ng-update/test-cases/v8/material-imports.spec.ts b/src/material/schematics/ng-update/test-cases/v8/misc/material-imports.spec.ts
similarity index 96%
rename from src/material/schematics/ng-update/test-cases/v8/material-imports.spec.ts
rename to src/material/schematics/ng-update/test-cases/v8/misc/material-imports.spec.ts
index 2452cba0c8f4..3c1d912d545e 100644
--- a/src/material/schematics/ng-update/test-cases/v8/material-imports.spec.ts
+++ b/src/material/schematics/ng-update/test-cases/v8/misc/material-imports.spec.ts
@@ -1,5 +1,5 @@
import {createTestCaseSetup, readFileContent} from '@angular/cdk/schematics/testing';
-import {migrationCollection} from '../index.spec';
+import {migrationCollection} from '../../index.spec';
describe('v8 material imports', () => {
it('should re-map top-level material imports to the proper entry points', async () => {
diff --git a/src/material/schematics/ng-update/test-cases/v8/material-imports_expected_output.ts b/src/material/schematics/ng-update/test-cases/v8/misc/material-imports_expected_output.ts
similarity index 100%
rename from src/material/schematics/ng-update/test-cases/v8/material-imports_expected_output.ts
rename to src/material/schematics/ng-update/test-cases/v8/misc/material-imports_expected_output.ts
diff --git a/src/material/schematics/ng-update/test-cases/v8/material-imports_input.ts b/src/material/schematics/ng-update/test-cases/v8/misc/material-imports_input.ts
similarity index 100%
rename from src/material/schematics/ng-update/test-cases/v8/material-imports_input.ts
rename to src/material/schematics/ng-update/test-cases/v8/misc/material-imports_input.ts
diff --git a/src/material/schematics/ng-update/test-cases/v9/hammer-migration-v9.spec.ts b/src/material/schematics/ng-update/test-cases/v9/misc/hammer-migration-v9.spec.ts
similarity index 99%
rename from src/material/schematics/ng-update/test-cases/v9/hammer-migration-v9.spec.ts
rename to src/material/schematics/ng-update/test-cases/v9/misc/hammer-migration-v9.spec.ts
index c14456a9f0b5..a4b045b854a4 100644
--- a/src/material/schematics/ng-update/test-cases/v9/hammer-migration-v9.spec.ts
+++ b/src/material/schematics/ng-update/test-cases/v9/misc/hammer-migration-v9.spec.ts
@@ -4,11 +4,11 @@ import {addPackageToPackageJson} from '@angular/cdk/schematics/ng-add/package-co
import {createTestCaseSetup} from '@angular/cdk/schematics/testing';
import {readFileSync} from 'fs';
-import {migrationCollection} from '../index.spec';
+import {migrationCollection} from '../../index.spec';
describe('v9 HammerJS removal', () => {
const GESTURE_CONFIG_TEMPLATE_PATH =
- require.resolve('../../upgrade-rules/hammer-gestures-v9/gesture-config.template');
+ require.resolve('../../../upgrade-rules/hammer-gestures-v9/gesture-config.template');
let runner: SchematicTestRunner;
let tree: UnitTestTree;
diff --git a/src/material/schematics/ng-update/test-cases/v9/material-imports.spec.ts b/src/material/schematics/ng-update/test-cases/v9/misc/material-imports.spec.ts
similarity index 94%
rename from src/material/schematics/ng-update/test-cases/v9/material-imports.spec.ts
rename to src/material/schematics/ng-update/test-cases/v9/misc/material-imports.spec.ts
index 4434f4db561c..a6c1833162dc 100644
--- a/src/material/schematics/ng-update/test-cases/v9/material-imports.spec.ts
+++ b/src/material/schematics/ng-update/test-cases/v9/misc/material-imports.spec.ts
@@ -1,5 +1,5 @@
import {createTestCaseSetup, readFileContent} from '@angular/cdk/schematics/testing';
-import {migrationCollection} from '../index.spec';
+import {migrationCollection} from '../../index.spec';
describe('v9 material imports', () => {
it('should re-map top-level material imports to the proper entry points when top-level ' +
diff --git a/src/material/schematics/ng-update/test-cases/v9/material-imports_expected_output.ts b/src/material/schematics/ng-update/test-cases/v9/misc/material-imports_expected_output.ts
similarity index 100%
rename from src/material/schematics/ng-update/test-cases/v9/material-imports_expected_output.ts
rename to src/material/schematics/ng-update/test-cases/v9/misc/material-imports_expected_output.ts
diff --git a/src/material/schematics/ng-update/test-cases/v9/material-imports_input.ts b/src/material/schematics/ng-update/test-cases/v9/misc/material-imports_input.ts
similarity index 100%
rename from src/material/schematics/ng-update/test-cases/v9/material-imports_input.ts
rename to src/material/schematics/ng-update/test-cases/v9/misc/material-imports_input.ts
diff --git a/src/material/schematics/ng-update/upgrade-rules/hammer-gestures-v9/hammer-gestures-rule.ts b/src/material/schematics/ng-update/upgrade-rules/hammer-gestures-v9/hammer-gestures-rule.ts
index 0163dc56ac38..dedea3f9f708 100644
--- a/src/material/schematics/ng-update/upgrade-rules/hammer-gestures-v9/hammer-gestures-rule.ts
+++ b/src/material/schematics/ng-update/upgrade-rules/hammer-gestures-v9/hammer-gestures-rule.ts
@@ -19,7 +19,9 @@ import {
MigrationRule,
PostMigrationAction,
ResolvedResource,
- TargetVersion
+ TargetVersion,
+ Import,
+ getImportOfIdentifier,
} from '@angular/cdk/schematics';
import {
addSymbolToNgModuleMetadata,
@@ -38,7 +40,6 @@ import {getProjectFromProgram} from './cli-workspace';
import {findHammerScriptImportElements} from './find-hammer-script-tags';
import {findMainModuleExpression} from './find-main-module';
import {isHammerJsUsedInTemplate} from './hammer-template-check';
-import {getImportOfIdentifier, Import} from './identifier-imports';
import {ImportManager} from './import-manager';
import {removeElementFromArrayExpression} from './remove-array-element';
import {removeElementFromHtml} from './remove-element-from-html';
diff --git a/src/material/schematics/ng-update/upgrade-rules/hammer-gestures-v9/identifier-imports.ts b/src/material/schematics/ng-update/upgrade-rules/hammer-gestures-v9/identifier-imports.ts
deleted file mode 100644
index e49b3d6a902b..000000000000
--- a/src/material/schematics/ng-update/upgrade-rules/hammer-gestures-v9/identifier-imports.ts
+++ /dev/null
@@ -1,113 +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 * as ts from 'typescript';
-
-/** Interface describing a resolved import. */
-export interface Import {
- /** Name of the imported symbol. */
- symbolName: string;
- /** Module name from which the symbol has been imported. */
- moduleName: string;
-}
-
-
-/** Resolves the import of the specified identifier. */
-export function getImportOfIdentifier(node: ts.Identifier, typeChecker: ts.TypeChecker): Import|
- null {
- const directImport = getSpecificImportOfIdentifier(node, typeChecker);
- if (directImport !== null) {
- return directImport;
- } else if (ts.isQualifiedName(node.parent) && node.parent.right === node) {
- const qualifierRoot = getQualifiedNameRoot(node.parent);
- if (qualifierRoot) {
- const moduleName = getImportOfNamespacedIdentifier(qualifierRoot, typeChecker);
- if (moduleName) {
- return {moduleName, symbolName: node.text};
- }
- }
- } else if (ts.isPropertyAccessExpression(node.parent) && node.parent.name === node) {
- const rootIdentifier = getPropertyAccessRoot(node.parent);
- if (rootIdentifier) {
- const moduleName = getImportOfNamespacedIdentifier(rootIdentifier, typeChecker);
- if (moduleName) {
- return {moduleName, symbolName: node.text};
- }
- }
- }
- return null;
-}
-
-/**
- * Resolves the import of the specified identifier. Expects the identifier to resolve
- * to a fine-grained import declaration with import specifiers.
- */
-function getSpecificImportOfIdentifier(node: ts.Identifier, typeChecker: ts.TypeChecker): Import|
- null {
- const symbol = typeChecker.getSymbolAtLocation(node);
- if (!symbol || !symbol.declarations || !symbol.declarations.length) {
- return null;
- }
- const declaration = symbol.declarations[0];
- if (!ts.isImportSpecifier(declaration)) {
- return null;
- }
- const importDecl = declaration.parent.parent.parent;
- if (!ts.isStringLiteral(importDecl.moduleSpecifier)) {
- return null;
- }
- return {
- moduleName: importDecl.moduleSpecifier.text,
- symbolName: declaration.propertyName ? declaration.propertyName.text : declaration.name.text
- };
-}
-
-/**
- * Resolves the import of the specified identifier. Expects the identifier to
- * resolve to a namespaced import declaration. e.g. "import * as core from ...".
- */
-function getImportOfNamespacedIdentifier(node: ts.Identifier, typeChecker: ts.TypeChecker): string|
- null {
- const symbol = typeChecker.getSymbolAtLocation(node);
- if (!symbol || !symbol.declarations || !symbol.declarations.length) {
- return null;
- }
- const declaration = symbol.declarations[0];
- if (!ts.isNamespaceImport(declaration)) {
- return null;
- }
- const importDecl = declaration.parent.parent;
- if (!ts.isStringLiteral(importDecl.moduleSpecifier)) {
- return null;
- }
-
- return importDecl.moduleSpecifier.text;
-}
-
-
-/**
- * Gets the root identifier of a qualified type chain. For example: "core.GestureConfig"
- * will return the "matCore" identifier. Allowing us to find the import of "core".
- */
-function getQualifiedNameRoot(name: ts.QualifiedName): ts.Identifier|null {
- while (ts.isQualifiedName(name.left)) {
- name = name.left;
- }
- return ts.isIdentifier(name.left) ? name.left : null;
-}
-
-/**
- * Gets the root identifier of a property access chain. For example: "core.GestureConfig"
- * will return the "matCore" identifier. Allowing us to find the import of "core".
- */
-function getPropertyAccessRoot(node: ts.PropertyAccessExpression): ts.Identifier|null {
- while (ts.isPropertyAccessExpression(node.expression)) {
- node = node.expression;
- }
- return ts.isIdentifier(node.expression) ? node.expression : null;
-}