Skip to content

feat(material/ng-update): add migration for hammerjs in version 9 #17369

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Oct 14, 2019
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
1 change: 1 addition & 0 deletions src/cdk/private/testing/public-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@
export * from './expect-async-error';
export * from './wrapped-error-message';
export * from './mock-ng-zone';
export * from './text-dedent';
28 changes: 28 additions & 0 deletions src/cdk/private/testing/text-dedent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/**
* @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
*/

/**
* Template string function that can be used to dedent a given string
* literal. The smallest common indentation will be omitted.
*/
export function dedent(strings: TemplateStringsArray, ...values: any[]) {
let joinedString = '';
for (let i = 0; i < values.length; i++) {
joinedString += `${strings[i]}${values[i]}`;
}
joinedString += strings[strings.length - 1];

const matches = joinedString.match(/^[ \t]*(?=\S)/gm);
if (matches === null) {
return joinedString;
}

const minLineIndent = Math.min(...matches.map(el => el.length));
const omitMinIndentRegex = new RegExp(`^[ \\t]{${minLineIndent}}`, 'gm');
return minLineIndent > 0 ? joinedString.replace(omitMinIndentRegex, '') : joinedString;
}
7 changes: 7 additions & 0 deletions src/cdk/schematics/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,10 @@
export * from './utils';
export * from './ng-update/public-api';
export * from './update-tool/public-api';

// Re-export parse5 from the CDK. Material schematics code cannot simply import
// "parse5" because it could result in a different version. As long as we import
// it from within the CDK, it will always be the correct version that is specified
// in the CDK "package.json" as optional dependency.
import * as parse5 from 'parse5';
export {parse5};
4 changes: 2 additions & 2 deletions src/cdk/schematics/ng-update/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ function onMigrationComplete(targetVersion: TargetVersion, hasFailures: boolean)

if (hasFailures) {
console.log(chalk.yellow(
' ⚠ Some issues were detected but could not be fixed automatically. Please check the ' +
'output above and fix these issues manually.'));
' ⚠ Some issues were detected but could not be fixed automatically. Please check the ' +
'output above and fix these issues manually.'));
}
}
32 changes: 22 additions & 10 deletions src/cdk/schematics/ng-update/upgrade-rules/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@

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

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


/** List of migration rules which run for the CDK update. */
export const cdkMigrationRules: Constructor<MigrationRule<RuleUpgradeData>>[] = [
export const cdkMigrationRules: MigrationRuleType<RuleUpgradeData>[] = [
AttributeSelectorsRule,
ClassInheritanceRule,
ClassNamesRule,
Expand All @@ -42,7 +41,7 @@ export const cdkMigrationRules: Constructor<MigrationRule<RuleUpgradeData>>[] =
PropertyNamesRule,
];

type NullableMigrationRule = Constructor<MigrationRule<RuleUpgradeData|null>>;
type NullableMigrationRule = MigrationRuleType<RuleUpgradeData|null>;

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

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

for (const tsconfigPath of [...buildPaths, ...testPaths]) {
hasRuleFailures = hasRuleFailures || runMigrationRules(
tree, context.logger, tsconfigPath, targetVersion, [...cdkMigrationRules, ...extraRules],
upgradeData, analyzedFiles);
}
const runMigration = (tsconfigPath: string, isTestTarget: boolean) => {
const result = runMigrationRules(
tree, context.logger, tsconfigPath, isTestTarget, targetVersion,
rules, upgradeData, analyzedFiles);

hasRuleFailures = hasRuleFailures || result.hasFailures;
};

buildPaths.forEach(p => runMigration(p, false));
testPaths.forEach(p => runMigration(p, true));

// Run the global post migration static members for all migration rules.
rules.forEach(r => r.globalPostMigration(tree, context));

// Execute all asynchronous tasks and await them here. We want to run
// the "onMigrationCompleteFn" after all work is done.
await context.engine.executePostTasks().toPromise();

if (onMigrationCompleteFn) {
onMigrationCompleteFn(targetVersion, hasRuleFailures);
Expand Down
17 changes: 16 additions & 1 deletion src/cdk/schematics/testing/test-case-setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {SchematicTestRunner, UnitTestTree} from '@angular-devkit/schematics/test
import {readFileSync, removeSync} from 'fs-extra';
import {sync as globSync} from 'glob';
import {basename, extname, join, relative, sep} from 'path';
import {EMPTY} from 'rxjs';
import {createTestApp} from '../testing';

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

function writeFile(filePath: string, content: string) {
// Update the temp file system host to reflect the changes in the real file system.
// This is still necessary since we depend on the real file system for parsing the
// TypeScript project.
tempFileSystemHost.sync.write(normalize(filePath), virtualFs.stringToFileBuffer(content));
if (hostTree.exists(filePath)) {
hostTree.overwrite(filePath, content);
} else {
hostTree.create(filePath, content);
}
}
}

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

// Patch "executePostTasks" to do nothing. This is necessary since
// we cannot run the node install task in unit tests. Rather we just
// assert that certain async post tasks are scheduled.
// TODO(devversion): RxJS version conflicts between angular-devkit and our dev deps.
runner.engine.executePostTasks = () => EMPTY as any;

await runner.runSchematicAsync(migrationName, {}, appTree).toPromise();

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

return {appTree, writeFile, tempPath, removeTempDir, runFixers};
return {runner, appTree, writeFile, tempPath, removeTempDir, runFixers};
}

/**
Expand Down
23 changes: 16 additions & 7 deletions src/cdk/schematics/update-tool/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,15 @@ import {MigrationFailure, MigrationRule} from './migration-rule';
import {TargetVersion} from './target-version';
import {parseTsconfigFile} from './utils/parse-tsconfig';

export type Constructor<T> = new (...args: any[]) => T;
export type Constructor<T> = (new (...args: any[]) => T);
export type MigrationRuleType<T> = Constructor<MigrationRule<T>>
& {[m in keyof typeof MigrationRule]: (typeof MigrationRule)[m]};


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

// Create instances of all specified migration rules.
for (const ruleCtor of ruleTypes) {
const rule = new ruleCtor(program, typeChecker, targetVersion, upgradeData);
rule.getUpdateRecorder = getUpdateRecorder;
const rule = new ruleCtor(
program, typeChecker, targetVersion, upgradeData, tree, getUpdateRecorder, basePath, logger,
isTestTarget, tsconfigPath);
rule.init();
if (rule.ruleEnabled) {
rules.push(rule);
Expand Down Expand Up @@ -102,6 +106,9 @@ export function runMigrationRules<T>(
}
});

// Call the "postAnalysis" method for each migration rule.
rules.forEach(r => r.postAnalysis());

// Commit all recorded updates in the update recorder. We need to perform the
// replacements per source file in order to ensure that offsets in the TypeScript
// program are not incorrectly shifted.
Expand All @@ -120,7 +127,9 @@ export function runMigrationRules<T>(
});
}

return !!ruleFailures.length;
return {
hasFailures: !!ruleFailures.length,
};

function getUpdateRecorder(filePath: string): UpdateRecorder {
const treeFilePath = getProjectRelativePath(filePath);
Expand Down
49 changes: 41 additions & 8 deletions src/cdk/schematics/update-tool/migration-rule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
* found in the LICENSE file at https://angular.io/license
*/

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

constructor(
public program: ts.Program, public typeChecker: ts.TypeChecker,
public targetVersion: TargetVersion, public upgradeData: T) {}
/** TypeScript program for the migration. */
public program: ts.Program,
/** TypeChecker instance for the analysis program. */
public typeChecker: ts.TypeChecker,
/** Version for which the migration rule should run. */
public targetVersion: TargetVersion,
/** Upgrade data passed to the migration. */
public upgradeData: T,
/** Devkit tree for the current migration. Can be used to insert/remove files. */
public tree: Tree,
/** Gets the update recorder for a given source file or resolved template. */
public getUpdateRecorder: (filePath: string) => UpdateRecorder,
/** Base directory of the virtual file system tree. */
public basePath: string,
/** Logger that can be used to print messages as part of the migration. */
public logger: logging.LoggerApi,
/** Whether the migration runs for a test target. */
public isTestTarget: boolean,
/** Path to the tsconfig that is migrated. */
public tsconfigPath: string) {}

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

/**
* Method that will be called once all nodes, templates and stylesheets
* have been visited.
*/
postAnalysis(): void {}

/**
* Method that will be called for each node in a given source file. Unlike tslint, this
* function will only retrieve TypeScript nodes that need to be casted manually. This
Expand All @@ -46,11 +71,6 @@ export class MigrationRule<T> {
/** Method that will be called for each stylesheet in the program. */
visitStylesheet(stylesheet: ResolvedResource): void {}

/** Gets the update recorder for a given source file or resolved template. */
getUpdateRecorder(filePath: string): UpdateRecorder {
throw new Error('MigrationRule#getUpdateRecorder is not implemented.');
}

/** Creates a failure with a specified message at the given node location. */
createFailureAtNode(node: ts.Node, message: string) {
const sourceFile = node.getSourceFile();
Expand All @@ -60,4 +80,17 @@ export class MigrationRule<T> {
message: message,
});
}

/** Prints the specified message with "info" loglevel. */
printInfo(text: string) {
this.logger.info(`- ${this.tsconfigPath}: ${text}`);
}

/**
* Static method that will be called once the migration of all project targets
* has been performed. This method can be used to make changes respecting the
* migration result of all individual targets. e.g. removing HammerJS if it
* is not needed in any project target.
*/
static globalPostMigration(tree: Tree, context: SchematicContext) {}
}
1 change: 1 addition & 0 deletions src/cdk/schematics/update-tool/target-version.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export enum TargetVersion {
V7 = 'version 7',
V8 = 'version 8',
V9 = 'version 9',
V10 = 'version 10',
}

/**
Expand Down
1 change: 1 addition & 0 deletions src/cdk/schematics/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export * from './build-component';
export * from './get-project';
export * from './html-head-element';
export * from './parse5-element';
export * from './project-index-file';
export * from './project-main-file';
export * from './project-style-file';
export * from './project-targets';
Expand Down
21 changes: 21 additions & 0 deletions src/cdk/schematics/utils/project-index-file.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/**
* @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 {WorkspaceProject} from '@angular-devkit/core/src/experimental/workspace';
import {BrowserBuilderTarget} from '@schematics/angular/utility/workspace-models';
import {defaultTargetBuilders, getTargetsByBuilderName} from './project-targets';

/** Gets the path of the index file in the given project. */
export function getProjectIndexFiles(project: WorkspaceProject): string[] {
// Use a set to remove duplicate index files referenced in multiple build targets
// of a project.
return [...new Set(
(getTargetsByBuilderName(project, defaultTargetBuilders.build) as BrowserBuilderTarget[])
.filter(t => t.options.index)
.map(t => t.options.index!))];
}
27 changes: 19 additions & 8 deletions src/cdk/schematics/utils/project-targets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,26 +8,37 @@

import {WorkspaceProject} from '@angular-devkit/core/src/experimental/workspace';
import {SchematicsException} from '@angular-devkit/schematics';
import {BuilderTarget} from '@schematics/angular/utility/workspace-models';

/** Object that maps a CLI target to its default builder name. */
export const defaultTargetBuilders = {
build: '@angular-devkit/build-angular:browser',
test: '@angular-devkit/build-angular:karma',
};

/** Resolves the architect options for the build target of the given project. */
export function getProjectTargetOptions(project: WorkspaceProject, buildTarget: string) {
if (project.targets &&
project.targets[buildTarget] &&
project.targets[buildTarget].options) {

if (project.targets && project.targets[buildTarget] && project.targets[buildTarget].options) {
return project.targets[buildTarget].options;
}

// TODO(devversion): consider removing this architect check if the CLI completely switched
// over to `targets`, and the `architect` support has been removed.
// See: https://github.com/angular/angular-cli/commit/307160806cb48c95ecb8982854f452303801ac9f
if (project.architect &&
project.architect[buildTarget] &&
if (project.architect && project.architect[buildTarget] &&
project.architect[buildTarget].options) {

return project.architect[buildTarget].options;
}

throw new SchematicsException(
`Cannot determine project target configuration for: ${buildTarget}.`);
`Cannot determine project target configuration for: ${buildTarget}.`);
}

/** Gets all targets from the given project that match the specified builder name. */
export function getTargetsByBuilderName(
project: WorkspaceProject, builderName: string): BuilderTarget<any, unknown>[] {
const targets = project.targets || project.architect || {};
return Object.keys(targets)
.filter(name => targets[name].builder === builderName)
.map(name => targets[name]);
}
Loading