diff --git a/src/material/schematics/ng-update/test-cases/v9/misc/hammer-migration-v9.spec.ts b/src/material/schematics/ng-update/test-cases/v9/misc/hammer-migration-v9.spec.ts
index a4b045b854a4..fc1c6a6f689b 100644
--- a/src/material/schematics/ng-update/test-cases/v9/misc/hammer-migration-v9.spec.ts
+++ b/src/material/schematics/ng-update/test-cases/v9/misc/hammer-migration-v9.spec.ts
@@ -297,7 +297,7 @@ describe('v9 HammerJS removal', () => {
});
});
- describe('hammerjs used', () => {
+ describe('hammerjs used programmatically', () => {
beforeEach(() => {
appendContent('/projects/cdk-testing/src/main.ts', `
import 'hammerjs';
@@ -335,7 +335,7 @@ describe('v9 HammerJS removal', () => {
await runMigration();
expect(tree.readContent('/projects/cdk-testing/src/main.ts'))
- .not.toContain(`import 'hammerjs';`);
+ .not.toContain(`import 'hammerjs';`);
});
it('should not create gesture config if hammer is only used programmatically', async () => {
@@ -388,10 +388,93 @@ describe('v9 HammerJS removal', () => {
})
export class TestModule {}`);
});
+ });
+
+ describe('used in template with standard HammerJS events', () => {
+ beforeEach(() => {
+ appendContent('/projects/cdk-testing/src/main.ts', `
+ import 'hammerjs';
+ `);
+ });
- it('should create gesture config file if used in template', async () => {
+ it('should not create gesture config file', async () => {
writeFile('/projects/cdk-testing/src/app/app.component.html', `
-
+
+ `);
+
+ await runMigration();
+
+ expect(tree.readContent('/projects/cdk-testing/src/main.ts')).toContain(`import 'hammerjs';`);
+ expect(tree.exists('/projects/cdk-testing/src/gesture-config.ts')).toBe(false);
+ });
+
+ it('should not setup custom gesture config provider in root module', async () => {
+ writeFile('/projects/cdk-testing/src/app/app.component.html', `
+
+ `);
+
+ await runMigration();
+
+ expect(tree.readContent('/projects/cdk-testing/src/app/app.module.ts')).toContain(dedent`\
+ import { BrowserModule, HammerModule } from '@angular/platform-browser';
+ import { NgModule } from '@angular/core';
+
+ import { AppComponent } from './app.component';
+
+ @NgModule({
+ declarations: [
+ AppComponent
+ ],
+ imports: [
+ BrowserModule,
+ HammerModule
+ ],
+ providers: [],
+ bootstrap: [AppComponent]
+ })
+ export class AppModule { }`);
+ });
+
+ it('should remove references to the deprecated gesture config', async () => {
+ writeFile('/projects/cdk-testing/src/app/app.component.html', `
+
+ `);
+
+ writeFile('/projects/cdk-testing/src/test.module.ts', dedent`
+ import {NgModule} from '@angular/core';
+ import {HAMMER_GESTURE_CONFIG} from '@angular/platform-browser';
+ import {GestureConfig} from '@angular/material/core';
+
+ @NgModule({
+ providers: [{provide: HAMMER_GESTURE_CONFIG, useClass: GestureConfig}]
+ })
+ export class TestModule {}
+ `);
+
+ await runMigration();
+
+ expect(tree.readContent('/projects/cdk-testing/src/test.module.ts')).toContain(dedent`
+ import {NgModule} from '@angular/core';
+
+ @NgModule({
+ providers: []
+ })
+ export class TestModule {}`);
+ });
+ });
+
+ describe('used in template with custom Material gesture events', () => {
+ beforeEach(() => {
+ appendContent('/projects/cdk-testing/src/main.ts', `
+ import 'hammerjs';
+ `);
+ });
+
+ it('should create gesture config file', async () => {
+ writeFile('/projects/cdk-testing/src/app/app.component.html', `
+
+
+
`);
await runMigration();
@@ -441,7 +524,7 @@ describe('v9 HammerJS removal', () => {
it('should create gesture config file if used in template and programmatically', async () => {
writeFile('/projects/cdk-testing/src/app/app.component.html', `
-
+
`);
writeFile('/projects/cdk-testing/src/app/hammer.ts', `
@@ -478,7 +561,7 @@ describe('v9 HammerJS removal', () => {
it('should rewrite references to gesture config', async () => {
writeFile('/projects/cdk-testing/src/app/app.component.html', `
-
+
`);
writeFile('/projects/cdk-testing/src/nested/test.module.ts', dedent`
@@ -515,7 +598,7 @@ describe('v9 HammerJS removal', () => {
it('should rewrite references to gesture config without causing conflicts', async () => {
writeFile('/projects/cdk-testing/src/app/app.component.html', `
-
+
`);
writeFile('/projects/cdk-testing/src/test.module.ts', dedent`
@@ -553,7 +636,7 @@ describe('v9 HammerJS removal', () => {
it('should set up Hammer gestures in app module', async () => {
writeFile('/projects/cdk-testing/src/app/app.component.html', `
-
+
`);
await runMigration();
@@ -561,7 +644,7 @@ describe('v9 HammerJS removal', () => {
expect(tree.readContent('/projects/cdk-testing/src/main.ts')).toContain(`import 'hammerjs';`);
expect(tree.exists('/projects/cdk-testing/src/gesture-config.ts')).toBe(true);
expect(tree.readContent('/projects/cdk-testing/src/app/app.module.ts')).toContain(dedent`\
- import { BrowserModule, HammerModule, HAMMER_GESTURE_CONFIG } from '@angular/platform-browser';
+ import { BrowserModule, HAMMER_GESTURE_CONFIG, HammerModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';
@@ -584,7 +667,7 @@ describe('v9 HammerJS removal', () => {
it('should add gesture config provider to app module if module is referenced through ' +
're-exports in bootstrap', async () => {
writeFile('/projects/cdk-testing/src/app/app.component.html', `
-
+
`);
writeFile('/projects/cdk-testing/src/main.ts', `
@@ -610,7 +693,7 @@ describe('v9 HammerJS removal', () => {
expect(tree.readContent('/projects/cdk-testing/src/main.ts')).toContain(`import 'hammerjs';`);
expect(tree.exists('/projects/cdk-testing/src/gesture-config.ts')).toBe(true);
expect(tree.readContent('/projects/cdk-testing/src/app/app.module.ts')).toContain(dedent`\
- import { BrowserModule, HammerModule, HAMMER_GESTURE_CONFIG } from '@angular/platform-browser';
+ import { BrowserModule, HAMMER_GESTURE_CONFIG, HammerModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';
@@ -632,7 +715,7 @@ describe('v9 HammerJS removal', () => {
it('should not add gesture config provider multiple times if already provided', async () => {
writeFile('/projects/cdk-testing/src/app/app.component.html', `
-
+
`);
writeFile('/projects/cdk-testing/src/app/app.module.ts', dedent`
@@ -674,7 +757,7 @@ describe('v9 HammerJS removal', () => {
it('should not add HammerModule multiple times if already provided', async () => {
writeFile('/projects/cdk-testing/src/app/app.component.html', `
-
+
`);
writeFile('/projects/cdk-testing/src/app/app.module.ts', dedent`
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 dedea3f9f708..e4ae0bb33528 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
@@ -13,22 +13,22 @@ import {
} from '@angular-devkit/core';
import {SchematicContext, SchematicsException, Tree} from '@angular-devkit/schematics';
import {
+ getImportOfIdentifier,
getProjectIndexFiles,
getProjectMainFile,
+ Import,
MigrationFailure,
MigrationRule,
PostMigrationAction,
ResolvedResource,
TargetVersion,
- Import,
- getImportOfIdentifier,
} from '@angular/cdk/schematics';
import {
addSymbolToNgModuleMetadata,
getDecoratorMetadata,
getMetadataField
} from '@schematics/angular/utility/ast-utils';
-import {Change, InsertChange} from '@schematics/angular/utility/change';
+import {InsertChange} from '@schematics/angular/utility/change';
import {getWorkspace} from '@schematics/angular/utility/config';
import {WorkspaceProject} from '@schematics/angular/utility/workspace-models';
import chalk from 'chalk';
@@ -59,9 +59,6 @@ const HAMMER_MODULE_SPECIFIER = 'hammerjs';
const CANNOT_REMOVE_REFERENCE_ERROR =
`Cannot remove reference to "GestureConfig". Please remove manually.`;
-const CANNOT_SETUP_APP_MODULE_ERROR = `Could not setup Hammer gestures in module. Please ` +
- `manually ensure that the Hammer gesture config is set up.`;
-
interface IdentifierReference {
node: ts.Identifier;
importData: Import;
@@ -80,8 +77,14 @@ export class HammerGesturesRule extends MigrationRule {
private _importManager = new ImportManager(this.getUpdateRecorder, this._printer);
private _nodeFailures: {node: ts.Node, message: string}[] = [];
- /** Whether HammerJS is explicitly used in any component template. */
- private _usedInTemplate = false;
+ /**
+ * Whether custom HammerJS events provided by the Material gesture
+ * config are used in a template.
+ */
+ private _customEventsUsedInTemplate = false;
+
+ /** Whether standard HammerJS events are used in a template. */
+ private _standardEventsUsedInTemplate = false;
/** Whether HammerJS is accessed at runtime. */
private _usedInRuntime = false;
@@ -116,8 +119,10 @@ export class HammerGesturesRule extends MigrationRule {
private _deletedIdentifiers: ts.Identifier[] = [];
visitTemplate(template: ResolvedResource): void {
- if (!this._usedInTemplate && isHammerJsUsedInTemplate(template.content)) {
- this._usedInTemplate = true;
+ if (!this._customEventsUsedInTemplate || !this._standardEventsUsedInTemplate) {
+ const {standardEvents, customEvents} = isHammerJsUsedInTemplate(template.content);
+ this._customEventsUsedInTemplate = this._customEventsUsedInTemplate || customEvents;
+ this._standardEventsUsedInTemplate = this._standardEventsUsedInTemplate || standardEvents;
}
}
@@ -134,21 +139,26 @@ export class HammerGesturesRule extends MigrationRule {
// is a potential custom gesture config setup.
const hasCustomGestureConfigSetup =
this._hammerConfigTokenReferences.some(r => this._checkForCustomGestureConfigSetup(r));
+ const usedInTemplate = this._standardEventsUsedInTemplate || this._customEventsUsedInTemplate;
/*
Possible scenarios and how the migration should change the project:
1. We detect that a custom HammerJS gesture config is set up:
- - Remove references to the Material gesture config if no event from the
- Angular Material gesture config is used.
+ - Remove references to the Material gesture config if no HammerJS event is used.
- Print a warning about ambiguous configuration that cannot be handled completely
if there are references to the Material gesture config.
2. We detect that HammerJS is only used programmatically:
- Remove references to GestureConfig of Material.
- Remove references to the "HammerModule" if present.
- 3. We detect that HammerJS is used in a template:
+ 3. We detect that standard HammerJS events are used in a template:
+ - Set up the "HammerModule" from platform-browser.
+ - Remove all gesture config references.
+ 4. We detect that custom HammerJS events provided by the Material gesture
+ config are used.
- Copy the Material gesture config into the app.
- Rewrite all gesture config references to the newly copied one.
- Set up the new gesture config in the root app module.
+ - Set up the "HammerModule" from platform-browser.
4. We detect no HammerJS usage at all:
- Remove Hammer imports
- Remove Material gesture config references
@@ -159,28 +169,28 @@ export class HammerGesturesRule extends MigrationRule {
if (hasCustomGestureConfigSetup) {
// If a custom gesture config is provided, we always assume that HammerJS is used.
HammerGesturesRule.globalUsesHammer = true;
- if (!this._usedInTemplate && this._gestureConfigReferences.length) {
+ if (!usedInTemplate && this._gestureConfigReferences.length) {
// If the Angular Material gesture events are not used and we found a custom
// gesture config, we can safely remove references to the Material gesture config
// since events provided by the Material gesture config are guaranteed to be unused.
this._removeMaterialGestureConfigSetup();
this.printInfo(
- 'The HammerJS v9 migration for Angular Components detected that HammerJS is ' +
- 'manually set up in combination with references to the Angular Material gesture ' +
- 'config. This target cannot be migrated completely, but all references to the ' +
- 'deprecated Angular Material gesture have been removed.');
- } else if (this._usedInTemplate && this._gestureConfigReferences.length) {
+ 'The HammerJS v9 migration for Angular Components detected that HammerJS is ' +
+ 'manually set up in combination with references to the Angular Material gesture ' +
+ 'config. This target cannot be migrated completely, but all references to the ' +
+ 'deprecated Angular Material gesture have been removed.');
+ } else if (usedInTemplate && this._gestureConfigReferences.length) {
// Since there is a reference to the Angular Material gesture config, and we detected
// usage of a gesture event that could be provided by Angular Material, we *cannot*
// automatically remove references. This is because we do *not* know whether the
// event is actually provided by the custom config or by the Material config.
this.printInfo(
- 'The HammerJS v9 migration for Angular Components detected that HammerJS is ' +
- 'manually set up in combination with references to the Angular Material gesture ' +
- 'config. This target cannot be migrated completely. Please manually remove references ' +
- 'to the deprecated Angular Material gesture config.');
+ 'The HammerJS v9 migration for Angular Components detected that HammerJS is ' +
+ 'manually set up in combination with references to the Angular Material gesture ' +
+ 'config. This target cannot be migrated completely. Please manually remove ' +
+ 'references to the deprecated Angular Material gesture config.');
}
- } else if (this._usedInRuntime || this._usedInTemplate) {
+ } else if (this._usedInRuntime || usedInTemplate) {
// We keep track of whether Hammer is used globally. This is necessary because we
// want to only remove Hammer from the "package.json" if it is not used in any project
// target. Just because it isn't used in one target doesn't mean that we can safely
@@ -189,11 +199,13 @@ export class HammerGesturesRule extends MigrationRule {
// If hammer is only used at runtime, we don't need the gesture config or "HammerModule"
// and can remove it (along with the hammer config token import if no longer needed).
- if (!this._usedInTemplate) {
+ if (!usedInTemplate) {
this._removeMaterialGestureConfigSetup();
this._removeHammerModuleReferences();
+ } else if (this._standardEventsUsedInTemplate && !this._customEventsUsedInTemplate) {
+ this._setupHammerWithStandardEvents();
} else {
- this._setupHammerGestureConfig();
+ this._setupHammerWithCustomEvents();
}
} else {
this._removeHammerSetup();
@@ -212,7 +224,7 @@ export class HammerGesturesRule extends MigrationRule {
// output could also be from a component having an output named similarly to a known
// hammerjs event (e.g. "@Output() slide"). The usage is therefore somewhat ambiguous
// and we want to print a message that developers might be able to remove Hammer manually.
- if (!hasCustomGestureConfigSetup && !this._usedInRuntime && this._usedInTemplate) {
+ if (!hasCustomGestureConfigSetup && !this._usedInRuntime && usedInTemplate) {
this.printInfo(
'The HammerJS v9 migration for Angular Components migrated the ' +
'project to keep HammerJS installed, but detected ambiguous usage of HammerJS. Please ' +
@@ -225,27 +237,45 @@ export class HammerGesturesRule extends MigrationRule {
* following steps are performed:
* 1) Create copy of Angular Material gesture config.
* 2) Rewrite all references to the Angular Material gesture config to the
- * newly copied gesture config.
- * 3) Setup the HAMMER_GESTURE_CONFIG provider in the root app module
- * (if not done already).
+ * new gesture config.
+ * 3) Setup the HAMMER_GESTURE_CONFIG in the root app module (if not done already).
+ * 4) Setup the "HammerModule" in the root app module (if not done already).
*/
- private _setupHammerGestureConfig() {
+ private _setupHammerWithCustomEvents() {
const project = this._getProjectOrThrow();
const sourceRoot = devkitNormalize(project.sourceRoot || project.root);
- const gestureConfigPath =
+ const newConfigPath =
devkitJoin(sourceRoot, this._getAvailableGestureConfigFileName(sourceRoot));
// Copy gesture config template into the CLI project.
this.tree.create(
- gestureConfigPath, readFileSync(require.resolve(GESTURE_CONFIG_TEMPLATE_PATH), 'utf8'));
+ newConfigPath, readFileSync(require.resolve(GESTURE_CONFIG_TEMPLATE_PATH), 'utf8'));
- // Replace all references to the gesture config of Material.
+ // Replace all Material gesture config references to resolve to the
+ // newly copied gesture config.
this._gestureConfigReferences.forEach(
- i => this._replaceGestureConfigReference(i, gestureConfigPath));
+ i => this._replaceGestureConfigReference(
+ i, GESTURE_CONFIG_CLASS_NAME,
+ getModuleSpecifier(newConfigPath, i.node.getSourceFile().fileName)));
+
+ // Setup the gesture config provider and the "HammerModule" in the root module
+ // if not done already. The "HammerModule" is needed in v9 since it enables the
+ // Hammer event plugin that was previously enabled by default in v8.
+ this._setupNewGestureConfigInRootModule(project, newConfigPath);
+ this._setupHammerModuleInRootModule(project);
+ }
+
+ /**
+ * Sets up the standard hammer module in the project and removes all
+ * references to the deprecated Angular Material gesture config.
+ */
+ private _setupHammerWithStandardEvents() {
+ const project = this._getProjectOrThrow();
- // Setup the gesture config provider and the "HammerModule" in the project app
- // module if not done already.
- this._setupHammerGesturesInAppModule(project, gestureConfigPath);
+ // Setup the HammerModule. The HammerModule enables support for
+ // the standard HammerJS events.
+ this._setupHammerModuleInRootModule(project);
+ this._removeMaterialGestureConfigSetup();
}
/**
@@ -467,15 +497,12 @@ export class HammerGesturesRule extends MigrationRule {
return `${possibleName + index}.ts`;
}
- /**
- * Replaces a given gesture config reference by ensuring that it is imported
- * from the new specified path.
- */
+ /** Replaces a given gesture config reference with a new import. */
private _replaceGestureConfigReference(
- {node, importData, isImport}: IdentifierReference, newPath: string) {
+ {node, importData, isImport}: IdentifierReference, symbolName: string,
+ moduleSpecifier: string) {
const sourceFile = node.getSourceFile();
const recorder = this.getUpdateRecorder(sourceFile.fileName);
- const newModuleSpecifier = getModuleSpecifier(newPath, sourceFile.fileName);
// List of all identifiers referring to the gesture config in the current file. This
// allows us to add an import for the copied gesture configuration without generating a
@@ -490,8 +517,7 @@ export class HammerGesturesRule extends MigrationRule {
// the config has been imported through a namespaced import.
if (isNamespacedIdentifierAccess(node)) {
const newExpression = this._importManager.addImportToSourceFile(
- sourceFile, GESTURE_CONFIG_CLASS_NAME, newModuleSpecifier, false,
- gestureIdentifiersInFile);
+ sourceFile, symbolName, moduleSpecifier, false, gestureIdentifiersInFile);
recorder.remove(node.parent.getStart(), node.parent.getWidth());
recorder.insertRight(node.parent.getStart(), this._printNode(newExpression, sourceFile));
@@ -508,8 +534,7 @@ export class HammerGesturesRule extends MigrationRule {
// to remove unused imports to the Material gesture config.
if (!isImport) {
const newExpression = this._importManager.addImportToSourceFile(
- sourceFile, GESTURE_CONFIG_CLASS_NAME, newModuleSpecifier, false,
- gestureIdentifiersInFile);
+ sourceFile, symbolName, moduleSpecifier, false, gestureIdentifiersInFile);
recorder.remove(node.getStart(), node.getWidth());
recorder.insertRight(node.getStart(), this._printNode(newExpression, sourceFile));
@@ -619,94 +644,124 @@ export class HammerGesturesRule extends MigrationRule {
});
}
- /** Sets up the Hammer gesture config provider in the app module if needed. */
- private _setupHammerGesturesInAppModule(project: WorkspaceProject, gestureConfigPath: string) {
+ /** Sets up the Hammer gesture config in the root module if needed. */
+ private _setupNewGestureConfigInRootModule(project: WorkspaceProject, gestureConfigPath: string) {
const mainFilePath = join(this.basePath, getProjectMainFile(project));
- const mainFile = this.program.getSourceFile(mainFilePath);
- if (!mainFile) {
- this.failures.push({
- filePath: mainFilePath,
- message: CANNOT_SETUP_APP_MODULE_ERROR,
- });
- return;
- }
+ const rootModuleSymbol = this._getRootModuleSymbol(mainFilePath);
- const appModuleExpr = findMainModuleExpression(mainFile);
- if (!appModuleExpr) {
+ if (rootModuleSymbol === null) {
this.failures.push({
filePath: mainFilePath,
- message: CANNOT_SETUP_APP_MODULE_ERROR,
+ message: `Could not setup Hammer gestures in module. Please ` +
+ `manually ensure that the Hammer gesture config is set up.`,
});
return;
}
- const appModuleSymbol = this._getDeclarationSymbolOfNode(unwrapExpression(appModuleExpr));
- if (!appModuleSymbol || !appModuleSymbol.valueDeclaration) {
- this.failures.push({
- filePath: mainFilePath,
- message: CANNOT_SETUP_APP_MODULE_ERROR,
- });
+ const sourceFile = rootModuleSymbol.valueDeclaration.getSourceFile();
+ const relativePath = relative(this.basePath, sourceFile.fileName);
+ const metadata = getDecoratorMetadata(sourceFile, 'NgModule', '@angular/core') as
+ ts.ObjectLiteralExpression[];
+
+ // If no "NgModule" definition is found inside the source file, we just do nothing.
+ if (!metadata.length) {
return;
}
- const sourceFile = appModuleSymbol.valueDeclaration.getSourceFile();
- const relativePath = relative(this.basePath, sourceFile.fileName);
- const hammerModuleExpr = this._importManager.addImportToSourceFile(
- sourceFile, HAMMER_MODULE_NAME, HAMMER_MODULE_IMPORT);
- const hammerConfigTokenExpr = this._importManager.addImportToSourceFile(
- sourceFile, HAMMER_CONFIG_TOKEN_NAME, HAMMER_CONFIG_TOKEN_MODULE);
+ const recorder = this.getUpdateRecorder(sourceFile.fileName);
+ const providersField = getMetadataField(metadata[0], 'providers')[0];
+ const providerIdentifiers =
+ providersField ? findMatchingChildNodes(providersField, ts.isIdentifier) : null;
const gestureConfigExpr = this._importManager.addImportToSourceFile(
sourceFile, GESTURE_CONFIG_CLASS_NAME,
getModuleSpecifier(gestureConfigPath, sourceFile.fileName), false,
this._getGestureConfigIdentifiersOfFile(sourceFile));
-
- const recorder = this.getUpdateRecorder(sourceFile.fileName);
+ const hammerConfigTokenExpr = this._importManager.addImportToSourceFile(
+ sourceFile, HAMMER_CONFIG_TOKEN_NAME, HAMMER_CONFIG_TOKEN_MODULE);
const newProviderNode = ts.createObjectLiteral([
ts.createPropertyAssignment('provide', hammerConfigTokenExpr),
ts.createPropertyAssignment('useClass', gestureConfigExpr)
]);
- // If no "NgModule" definition is found inside the source file, we just do nothing.
+ // If the providers field exists and already contains references to the hammer gesture
+ // config token and the gesture config, we naively assume that the gesture config is
+ // already set up. We only want to add the gesture config provider if it is not set up.
+ if (!providerIdentifiers ||
+ !(this._hammerConfigTokenReferences.some(r => providerIdentifiers.includes(r.node)) &&
+ this._gestureConfigReferences.some(r => providerIdentifiers.includes(r.node)))) {
+ addSymbolToNgModuleMetadata(
+ sourceFile, relativePath, 'providers', this._printNode(newProviderNode, sourceFile), null)
+ .forEach(change => {
+ if (change instanceof InsertChange) {
+ recorder.insertRight(change.pos, change.toAdd);
+ }
+ });
+ }
+ }
+
+ /**
+ * Gets the TypeScript symbol of the root module by looking for the module
+ * bootstrap expression in the specified source file.
+ */
+ private _getRootModuleSymbol(mainFilePath: string): ts.Symbol|null {
+ const mainFile = this.program.getSourceFile(mainFilePath);
+ if (!mainFile) {
+ return null;
+ }
+
+ const appModuleExpr = findMainModuleExpression(mainFile);
+ if (!appModuleExpr) {
+ return null;
+ }
+
+ const appModuleSymbol = this._getDeclarationSymbolOfNode(unwrapExpression(appModuleExpr));
+ if (!appModuleSymbol || !appModuleSymbol.valueDeclaration) {
+ return null;
+ }
+ return appModuleSymbol;
+ }
+
+ /** Sets up the "HammerModule" in the root module of the project. */
+ private _setupHammerModuleInRootModule(project: WorkspaceProject) {
+ const mainFilePath = join(this.basePath, getProjectMainFile(project));
+ const rootModuleSymbol = this._getRootModuleSymbol(mainFilePath);
+
+ if (rootModuleSymbol === null) {
+ this.failures.push({
+ filePath: mainFilePath,
+ message: `Could not setup HammerModule. Please manually set up the "HammerModule" ` +
+ `from "@angular/platform-browser".`,
+ });
+ return;
+ }
+
+ const sourceFile = rootModuleSymbol.valueDeclaration.getSourceFile();
+ const relativePath = relative(this.basePath, sourceFile.fileName);
const metadata = getDecoratorMetadata(sourceFile, 'NgModule', '@angular/core') as
ts.ObjectLiteralExpression[];
if (!metadata.length) {
return;
}
- const providersField = getMetadataField(metadata[0], 'providers')[0];
const importsField = getMetadataField(metadata[0], 'imports')[0];
-
- const providerIdentifiers =
- providersField ? findMatchingChildNodes(providersField, ts.isIdentifier) : null;
const importIdentifiers =
importsField ? findMatchingChildNodes(importsField, ts.isIdentifier) : null;
- const changeActions: Change[] = [];
-
- // If the providers field exists and already contains references to the hammer gesture
- // config token and the gesture config, we naively assume that the gesture config is
- // already set up. We only want to add the gesture config provider if it is not set up.
- if (!providerIdentifiers ||
- !(this._hammerConfigTokenReferences.some(r => providerIdentifiers.includes(r.node)) &&
- this._gestureConfigReferences.some(r => providerIdentifiers.includes(r.node)))) {
- changeActions.push(...addSymbolToNgModuleMetadata(
- sourceFile, relativePath, 'providers', this._printNode(newProviderNode, sourceFile),
- null));
- }
+ const recorder = this.getUpdateRecorder(sourceFile.fileName);
+ const hammerModuleExpr = this._importManager.addImportToSourceFile(
+ sourceFile, HAMMER_MODULE_NAME, HAMMER_MODULE_IMPORT);
// If the "HammerModule" is not already imported in the app module, we set it up
- // by adding it to the "imports" field.
+ // by adding it to the "imports" field of the app module.
if (!importIdentifiers ||
!this._hammerModuleReferences.some(r => importIdentifiers.includes(r.node))) {
- changeActions.push(...addSymbolToNgModuleMetadata(
- sourceFile, relativePath, 'imports', this._printNode(hammerModuleExpr, sourceFile),
- null));
+ addSymbolToNgModuleMetadata(
+ sourceFile, relativePath, 'imports', this._printNode(hammerModuleExpr, sourceFile), null)
+ .forEach(change => {
+ if (change instanceof InsertChange) {
+ recorder.insertRight(change.pos, change.toAdd);
+ }
+ });
}
-
- changeActions.forEach(change => {
- if (change instanceof InsertChange) {
- recorder.insertRight(change.pos, change.toAdd);
- }
- });
}
/** Prints a given node within the specified source file. */
@@ -794,9 +849,9 @@ export class HammerGesturesRule extends MigrationRule {
static globalPostMigration(tree: Tree, context: SchematicContext): PostMigrationAction {
// Always notify the developer that the Hammer v9 migration does not migrate tests.
context.logger.info(chalk.yellow(
- '\n⚠ General notice: The HammerJS v9 migration for Angular Components is not able to ' +
- 'migrate tests. Please manually clean up tests in your project if they rely on ' +
- (this.globalUsesHammer ? 'the deprecated Angular Material gesture config.' : 'HammerJS.')));
+ '\n⚠ General notice: The HammerJS v9 migration for Angular Components is not able to ' +
+ 'migrate tests. Please manually clean up tests in your project if they rely on ' +
+ (this.globalUsesHammer ? 'the deprecated Angular Material gesture config.' : 'HammerJS.')));
if (!this.globalUsesHammer && this._removeHammerFromPackageJson(tree)) {
// Since Hammer has been removed from the workspace "package.json" file,
diff --git a/src/material/schematics/ng-update/upgrade-rules/hammer-gestures-v9/hammer-template-check.ts b/src/material/schematics/ng-update/upgrade-rules/hammer-gestures-v9/hammer-template-check.ts
index 7868e3cf2a0c..2b85d6033f30 100644
--- a/src/material/schematics/ng-update/upgrade-rules/hammer-gestures-v9/hammer-template-check.ts
+++ b/src/material/schematics/ng-update/upgrade-rules/hammer-gestures-v9/hammer-template-check.ts
@@ -8,41 +8,52 @@
import {parse5} from '@angular/cdk/schematics';
-/**
- * List of known events which are supported by the "HammerGesturesPlugin" and by
- * the gesture config which was provided by Angular Material.
- */
-const KNOWN_HAMMERJS_EVENTS = [
+/** List of known events which are supported by the "HammerGesturesPlugin". */
+const STANDARD_HAMMERJS_EVENTS = [
// Events supported by the "HammerGesturesPlugin". See:
// angular/angular/blob/0119f46d/packages/platform-browser/src/dom/events/hammer_gestures.ts#L19
- 'pan', 'panstart', 'panmove', 'panend', 'pancancel', 'panleft', 'panright', 'panup', 'pandown',
- 'pinch', 'pinchstart', 'pinchmove', 'pinchend', 'pinchcancel', 'pinchin', 'pinchout', 'press',
- 'pressup', 'rotate', 'rotatestart', 'rotatemove', 'rotateend', 'rotatecancel', 'swipe',
- 'swipeleft', 'swiperight', 'swipeup', 'swipedown', 'tap',
-
- // Events from the Angular Material gesture config.
- 'longpress', 'slide', 'slidestart', 'slideend', 'slideright', 'slideleft'
+ 'pan', 'panstart', 'panmove', 'panend', 'pancancel', 'panleft',
+ 'panright', 'panup', 'pandown', 'pinch', 'pinchstart', 'pinchmove',
+ 'pinchend', 'pinchcancel', 'pinchin', 'pinchout', 'press', 'pressup',
+ 'rotate', 'rotatestart', 'rotatemove', 'rotateend', 'rotatecancel', 'swipe',
+ 'swipeleft', 'swiperight', 'swipeup', 'swipedown', 'tap',
];
+/** List of events which are provided by the deprecated Angular Material "GestureConfig". */
+const CUSTOM_MATERIAL_HAMMERJS_EVENS =
+ ['longpress', 'slide', 'slidestart', 'slideend', 'slideright', 'slideleft'];
+
/**
* Parses the specified HTML and searches for elements with Angular outputs listening to
* one of the known HammerJS events. This check naively assumes that the bindings never
* match on a component output, but only on the Hammer plugin.
*/
-export function isHammerJsUsedInTemplate(html: string): boolean {
+export function isHammerJsUsedInTemplate(html: string):
+ {standardEvents: boolean, customEvents: boolean} {
const document =
parse5.parseFragment(html, {sourceCodeLocationInfo: true}) as parse5.DefaultTreeDocument;
- let result = false;
+ let customEvents = false;
+ let standardEvents = false;
const visitNodes = nodes => {
- nodes.forEach(node => {
- if (node.attrs &&
- node.attrs.some(attr => KNOWN_HAMMERJS_EVENTS.some(e => `(${e})` === attr.name))) {
- result = true;
- } else if (node.childNodes) {
+ nodes.forEach((node: parse5.DefaultTreeElement) => {
+ if (node.attrs) {
+ for (let attr of node.attrs) {
+ if (!customEvents && CUSTOM_MATERIAL_HAMMERJS_EVENS.some(e => `(${e})` === attr.name)) {
+ customEvents = true;
+ }
+ if (!standardEvents && STANDARD_HAMMERJS_EVENTS.some(e => `(${e})` === attr.name)) {
+ standardEvents = true;
+ }
+ }
+ }
+
+ // Do not continue traversing the AST if both type of HammerJS
+ // usages have been detected already.
+ if (node.childNodes && (!customEvents || !standardEvents)) {
visitNodes(node.childNodes);
}
});
};
visitNodes(document.childNodes);
- return result;
+ return {customEvents, standardEvents};
}