Skip to content

Commit f065977

Browse files
devversionjelbourn
authored andcommitted
feat(material/ng-update): add migration for hammerjs in version 9 (#17369)
1 parent 14c4dba commit f065977

28 files changed

+2589
-81
lines changed

src/cdk/private/testing/public-api.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,4 @@
99
export * from './expect-async-error';
1010
export * from './wrapped-error-message';
1111
export * from './mock-ng-zone';
12+
export * from './text-dedent';
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
/**
10+
* Template string function that can be used to dedent a given string
11+
* literal. The smallest common indentation will be omitted.
12+
*/
13+
export function dedent(strings: TemplateStringsArray, ...values: any[]) {
14+
let joinedString = '';
15+
for (let i = 0; i < values.length; i++) {
16+
joinedString += `${strings[i]}${values[i]}`;
17+
}
18+
joinedString += strings[strings.length - 1];
19+
20+
const matches = joinedString.match(/^[ \t]*(?=\S)/gm);
21+
if (matches === null) {
22+
return joinedString;
23+
}
24+
25+
const minLineIndent = Math.min(...matches.map(el => el.length));
26+
const omitMinIndentRegex = new RegExp(`^[ \\t]{${minLineIndent}}`, 'gm');
27+
return minLineIndent > 0 ? joinedString.replace(omitMinIndentRegex, '') : joinedString;
28+
}

src/cdk/schematics/index.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,10 @@
99
export * from './utils';
1010
export * from './ng-update/public-api';
1111
export * from './update-tool/public-api';
12+
13+
// Re-export parse5 from the CDK. Material schematics code cannot simply import
14+
// "parse5" because it could result in a different version. As long as we import
15+
// it from within the CDK, it will always be the correct version that is specified
16+
// in the CDK "package.json" as optional dependency.
17+
import * as parse5 from 'parse5';
18+
export {parse5};

src/cdk/schematics/ng-update/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ function onMigrationComplete(targetVersion: TargetVersion, hasFailures: boolean)
4040

4141
if (hasFailures) {
4242
console.log(chalk.yellow(
43-
' ⚠ Some issues were detected but could not be fixed automatically. Please check the ' +
44-
'output above and fix these issues manually.'));
43+
' ⚠ Some issues were detected but could not be fixed automatically. Please check the ' +
44+
'output above and fix these issues manually.'));
4545
}
4646
}

src/cdk/schematics/ng-update/upgrade-rules/index.ts

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,7 @@
88

99
import {Rule, SchematicContext, Tree} from '@angular-devkit/schematics';
1010

11-
import {Constructor, runMigrationRules} from '../../update-tool';
12-
import {MigrationRule} from '../../update-tool/migration-rule';
11+
import {MigrationRuleType, runMigrationRules} from '../../update-tool';
1312
import {TargetVersion} from '../../update-tool/target-version';
1413
import {getProjectTsConfigPaths} from '../../utils/project-tsconfig-paths';
1514
import {RuleUpgradeData} from '../upgrade-data';
@@ -28,7 +27,7 @@ import {PropertyNamesRule} from './property-names-rule';
2827

2928

3029
/** List of migration rules which run for the CDK update. */
31-
export const cdkMigrationRules: Constructor<MigrationRule<RuleUpgradeData>>[] = [
30+
export const cdkMigrationRules: MigrationRuleType<RuleUpgradeData>[] = [
3231
AttributeSelectorsRule,
3332
ClassInheritanceRule,
3433
ClassNamesRule,
@@ -42,7 +41,7 @@ export const cdkMigrationRules: Constructor<MigrationRule<RuleUpgradeData>>[] =
4241
PropertyNamesRule,
4342
];
4443

45-
type NullableMigrationRule = Constructor<MigrationRule<RuleUpgradeData|null>>;
44+
type NullableMigrationRule = MigrationRuleType<RuleUpgradeData|null>;
4645

4746
/**
4847
* Creates a Angular schematic rule that runs the upgrade for the
@@ -51,7 +50,7 @@ type NullableMigrationRule = Constructor<MigrationRule<RuleUpgradeData|null>>;
5150
export function createUpgradeRule(
5251
targetVersion: TargetVersion, extraRules: NullableMigrationRule[], upgradeData: RuleUpgradeData,
5352
onMigrationCompleteFn?: (targetVersion: TargetVersion, hasFailures: boolean) => void): Rule {
54-
return (tree: Tree, context: SchematicContext) => {
53+
return async (tree: Tree, context: SchematicContext) => {
5554
const logger = context.logger;
5655
const {buildPaths, testPaths} = getProjectTsConfigPaths(tree);
5756

@@ -66,13 +65,26 @@ export function createUpgradeRule(
6665
// necessary because multiple TypeScript projects can contain the same source file and
6766
// we don't want to check these again, as this would result in duplicated failure messages.
6867
const analyzedFiles = new Set<string>();
68+
const rules = [...cdkMigrationRules, ...extraRules];
6969
let hasRuleFailures = false;
7070

71-
for (const tsconfigPath of [...buildPaths, ...testPaths]) {
72-
hasRuleFailures = hasRuleFailures || runMigrationRules(
73-
tree, context.logger, tsconfigPath, targetVersion, [...cdkMigrationRules, ...extraRules],
74-
upgradeData, analyzedFiles);
75-
}
71+
const runMigration = (tsconfigPath: string, isTestTarget: boolean) => {
72+
const result = runMigrationRules(
73+
tree, context.logger, tsconfigPath, isTestTarget, targetVersion,
74+
rules, upgradeData, analyzedFiles);
75+
76+
hasRuleFailures = hasRuleFailures || result.hasFailures;
77+
};
78+
79+
buildPaths.forEach(p => runMigration(p, false));
80+
testPaths.forEach(p => runMigration(p, true));
81+
82+
// Run the global post migration static members for all migration rules.
83+
rules.forEach(r => r.globalPostMigration(tree, context));
84+
85+
// Execute all asynchronous tasks and await them here. We want to run
86+
// the "onMigrationCompleteFn" after all work is done.
87+
await context.engine.executePostTasks().toPromise();
7688

7789
if (onMigrationCompleteFn) {
7890
onMigrationCompleteFn(targetVersion, hasRuleFailures);

src/cdk/schematics/testing/test-case-setup.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {SchematicTestRunner, UnitTestTree} from '@angular-devkit/schematics/test
1414
import {readFileSync, removeSync} from 'fs-extra';
1515
import {sync as globSync} from 'glob';
1616
import {basename, extname, join, relative, sep} from 'path';
17+
import {EMPTY} from 'rxjs';
1718
import {createTestApp} from '../testing';
1819

1920
/** Suffix that indicates whether a given file is a test case input. */
@@ -53,7 +54,15 @@ export async function createFileSystemTestApp(runner: SchematicTestRunner) {
5354
};
5455

5556
function writeFile(filePath: string, content: string) {
57+
// Update the temp file system host to reflect the changes in the real file system.
58+
// This is still necessary since we depend on the real file system for parsing the
59+
// TypeScript project.
5660
tempFileSystemHost.sync.write(normalize(filePath), virtualFs.stringToFileBuffer(content));
61+
if (hostTree.exists(filePath)) {
62+
hostTree.overwrite(filePath, content);
63+
} else {
64+
hostTree.create(filePath, content);
65+
}
5766
}
5867
}
5968

@@ -95,6 +104,12 @@ export async function createTestCaseSetup(migrationName: string, collectionPath:
95104
// from within the project.
96105
process.chdir(tempPath);
97106

107+
// Patch "executePostTasks" to do nothing. This is necessary since
108+
// we cannot run the node install task in unit tests. Rather we just
109+
// assert that certain async post tasks are scheduled.
110+
// TODO(devversion): RxJS version conflicts between angular-devkit and our dev deps.
111+
runner.engine.executePostTasks = () => EMPTY as any;
112+
98113
await runner.runSchematicAsync(migrationName, {}, appTree).toPromise();
99114

100115
// Switch back to the initial working directory.
@@ -103,7 +118,7 @@ export async function createTestCaseSetup(migrationName: string, collectionPath:
103118
return {logOutput};
104119
};
105120

106-
return {appTree, writeFile, tempPath, removeTempDir, runFixers};
121+
return {runner, appTree, writeFile, tempPath, removeTempDir, runFixers};
107122
}
108123

109124
/**

src/cdk/schematics/update-tool/index.ts

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,15 @@ import {MigrationFailure, MigrationRule} from './migration-rule';
1717
import {TargetVersion} from './target-version';
1818
import {parseTsconfigFile} from './utils/parse-tsconfig';
1919

20-
export type Constructor<T> = new (...args: any[]) => T;
20+
export type Constructor<T> = (new (...args: any[]) => T);
21+
export type MigrationRuleType<T> = Constructor<MigrationRule<T>>
22+
& {[m in keyof typeof MigrationRule]: (typeof MigrationRule)[m]};
23+
2124

2225
export function runMigrationRules<T>(
23-
tree: Tree, logger: logging.LoggerApi, tsconfigPath: string, targetVersion: TargetVersion,
24-
ruleTypes: Constructor<MigrationRule<T>>[], upgradeData: T,
25-
analyzedFiles: Set<string>): boolean {
26+
tree: Tree, logger: logging.LoggerApi, tsconfigPath: string, isTestTarget: boolean,
27+
targetVersion: TargetVersion, ruleTypes: MigrationRuleType<T>[], upgradeData: T,
28+
analyzedFiles: Set<string>): {hasFailures: boolean} {
2629
// The CLI uses the working directory as the base directory for the
2730
// virtual file system tree.
2831
const basePath = process.cwd();
@@ -44,8 +47,9 @@ export function runMigrationRules<T>(
4447

4548
// Create instances of all specified migration rules.
4649
for (const ruleCtor of ruleTypes) {
47-
const rule = new ruleCtor(program, typeChecker, targetVersion, upgradeData);
48-
rule.getUpdateRecorder = getUpdateRecorder;
50+
const rule = new ruleCtor(
51+
program, typeChecker, targetVersion, upgradeData, tree, getUpdateRecorder, basePath, logger,
52+
isTestTarget, tsconfigPath);
4953
rule.init();
5054
if (rule.ruleEnabled) {
5155
rules.push(rule);
@@ -102,6 +106,9 @@ export function runMigrationRules<T>(
102106
}
103107
});
104108

109+
// Call the "postAnalysis" method for each migration rule.
110+
rules.forEach(r => r.postAnalysis());
111+
105112
// Commit all recorded updates in the update recorder. We need to perform the
106113
// replacements per source file in order to ensure that offsets in the TypeScript
107114
// program are not incorrectly shifted.
@@ -120,7 +127,9 @@ export function runMigrationRules<T>(
120127
});
121128
}
122129

123-
return !!ruleFailures.length;
130+
return {
131+
hasFailures: !!ruleFailures.length,
132+
};
124133

125134
function getUpdateRecorder(filePath: string): UpdateRecorder {
126135
const treeFilePath = getProjectRelativePath(filePath);

src/cdk/schematics/update-tool/migration-rule.ts

Lines changed: 41 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9-
import {UpdateRecorder} from '@angular-devkit/schematics';
9+
import {logging} from '@angular-devkit/core';
10+
import {SchematicContext, Tree, UpdateRecorder} from '@angular-devkit/schematics';
1011
import * as ts from 'typescript';
1112
import {ResolvedResource} from './component-resource-collector';
1213
import {TargetVersion} from './target-version';
@@ -26,12 +27,36 @@ export class MigrationRule<T> {
2627
ruleEnabled = true;
2728

2829
constructor(
29-
public program: ts.Program, public typeChecker: ts.TypeChecker,
30-
public targetVersion: TargetVersion, public upgradeData: T) {}
30+
/** TypeScript program for the migration. */
31+
public program: ts.Program,
32+
/** TypeChecker instance for the analysis program. */
33+
public typeChecker: ts.TypeChecker,
34+
/** Version for which the migration rule should run. */
35+
public targetVersion: TargetVersion,
36+
/** Upgrade data passed to the migration. */
37+
public upgradeData: T,
38+
/** Devkit tree for the current migration. Can be used to insert/remove files. */
39+
public tree: Tree,
40+
/** Gets the update recorder for a given source file or resolved template. */
41+
public getUpdateRecorder: (filePath: string) => UpdateRecorder,
42+
/** Base directory of the virtual file system tree. */
43+
public basePath: string,
44+
/** Logger that can be used to print messages as part of the migration. */
45+
public logger: logging.LoggerApi,
46+
/** Whether the migration runs for a test target. */
47+
public isTestTarget: boolean,
48+
/** Path to the tsconfig that is migrated. */
49+
public tsconfigPath: string) {}
3150

3251
/** Method can be used to perform global analysis of the program. */
3352
init(): void {}
3453

54+
/**
55+
* Method that will be called once all nodes, templates and stylesheets
56+
* have been visited.
57+
*/
58+
postAnalysis(): void {}
59+
3560
/**
3661
* Method that will be called for each node in a given source file. Unlike tslint, this
3762
* function will only retrieve TypeScript nodes that need to be casted manually. This
@@ -46,11 +71,6 @@ export class MigrationRule<T> {
4671
/** Method that will be called for each stylesheet in the program. */
4772
visitStylesheet(stylesheet: ResolvedResource): void {}
4873

49-
/** Gets the update recorder for a given source file or resolved template. */
50-
getUpdateRecorder(filePath: string): UpdateRecorder {
51-
throw new Error('MigrationRule#getUpdateRecorder is not implemented.');
52-
}
53-
5474
/** Creates a failure with a specified message at the given node location. */
5575
createFailureAtNode(node: ts.Node, message: string) {
5676
const sourceFile = node.getSourceFile();
@@ -60,4 +80,17 @@ export class MigrationRule<T> {
6080
message: message,
6181
});
6282
}
83+
84+
/** Prints the specified message with "info" loglevel. */
85+
printInfo(text: string) {
86+
this.logger.info(`- ${this.tsconfigPath}: ${text}`);
87+
}
88+
89+
/**
90+
* Static method that will be called once the migration of all project targets
91+
* has been performed. This method can be used to make changes respecting the
92+
* migration result of all individual targets. e.g. removing HammerJS if it
93+
* is not needed in any project target.
94+
*/
95+
static globalPostMigration(tree: Tree, context: SchematicContext) {}
6396
}

src/cdk/schematics/update-tool/target-version.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export enum TargetVersion {
1212
V7 = 'version 7',
1313
V8 = 'version 8',
1414
V9 = 'version 9',
15+
V10 = 'version 10',
1516
}
1617

1718
/**

src/cdk/schematics/utils/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export * from './build-component';
1212
export * from './get-project';
1313
export * from './html-head-element';
1414
export * from './parse5-element';
15+
export * from './project-index-file';
1516
export * from './project-main-file';
1617
export * from './project-style-file';
1718
export * from './project-targets';
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
import {WorkspaceProject} from '@angular-devkit/core/src/experimental/workspace';
10+
import {BrowserBuilderTarget} from '@schematics/angular/utility/workspace-models';
11+
import {defaultTargetBuilders, getTargetsByBuilderName} from './project-targets';
12+
13+
/** Gets the path of the index file in the given project. */
14+
export function getProjectIndexFiles(project: WorkspaceProject): string[] {
15+
// Use a set to remove duplicate index files referenced in multiple build targets
16+
// of a project.
17+
return [...new Set(
18+
(getTargetsByBuilderName(project, defaultTargetBuilders.build) as BrowserBuilderTarget[])
19+
.filter(t => t.options.index)
20+
.map(t => t.options.index!))];
21+
}

src/cdk/schematics/utils/project-targets.ts

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,26 +8,37 @@
88

99
import {WorkspaceProject} from '@angular-devkit/core/src/experimental/workspace';
1010
import {SchematicsException} from '@angular-devkit/schematics';
11+
import {BuilderTarget} from '@schematics/angular/utility/workspace-models';
12+
13+
/** Object that maps a CLI target to its default builder name. */
14+
export const defaultTargetBuilders = {
15+
build: '@angular-devkit/build-angular:browser',
16+
test: '@angular-devkit/build-angular:karma',
17+
};
1118

1219
/** Resolves the architect options for the build target of the given project. */
1320
export function getProjectTargetOptions(project: WorkspaceProject, buildTarget: string) {
14-
if (project.targets &&
15-
project.targets[buildTarget] &&
16-
project.targets[buildTarget].options) {
17-
21+
if (project.targets && project.targets[buildTarget] && project.targets[buildTarget].options) {
1822
return project.targets[buildTarget].options;
1923
}
2024

2125
// TODO(devversion): consider removing this architect check if the CLI completely switched
2226
// over to `targets`, and the `architect` support has been removed.
2327
// See: https://github.com/angular/angular-cli/commit/307160806cb48c95ecb8982854f452303801ac9f
24-
if (project.architect &&
25-
project.architect[buildTarget] &&
28+
if (project.architect && project.architect[buildTarget] &&
2629
project.architect[buildTarget].options) {
27-
2830
return project.architect[buildTarget].options;
2931
}
3032

3133
throw new SchematicsException(
32-
`Cannot determine project target configuration for: ${buildTarget}.`);
34+
`Cannot determine project target configuration for: ${buildTarget}.`);
35+
}
36+
37+
/** Gets all targets from the given project that match the specified builder name. */
38+
export function getTargetsByBuilderName(
39+
project: WorkspaceProject, builderName: string): BuilderTarget<any, unknown>[] {
40+
const targets = project.targets || project.architect || {};
41+
return Object.keys(targets)
42+
.filter(name => targets[name].builder === builderName)
43+
.map(name => targets[name]);
3344
}

0 commit comments

Comments
 (0)