Skip to content

Commit

Permalink
feat(core): add migration to remove entryComponents (#44322)
Browse files Browse the repository at this point in the history
Adds an automated migration that will drop any usages of `entryComponents` from `@NgModule` and `@Component`.

PR Close #44322
  • Loading branch information
crisbeto authored and dylhunn committed Dec 1, 2021
1 parent f7ea524 commit e65a245
Show file tree
Hide file tree
Showing 11 changed files with 595 additions and 0 deletions.
1 change: 1 addition & 0 deletions packages/core/schematics/BUILD.bazel
Expand Up @@ -13,6 +13,7 @@ pkg_npm(
],
visibility = ["//packages/core:__pkg__"],
deps = [
"//packages/core/schematics/migrations/entry-components",
"//packages/core/schematics/migrations/router-link-empty-expression",
"//packages/core/schematics/migrations/testbed-teardown",
],
Expand Down
10 changes: 10 additions & 0 deletions packages/core/schematics/migrations.json
Expand Up @@ -9,6 +9,16 @@
"version": "13.0.0-beta",
"description": "In Angular version 13, the `teardown` flag in `TestBed` will be enabled by default. This migration automatically opts out existing apps from the new teardown behavior.",
"factory": "./migrations/testbed-teardown/index"
},
"migration-v13.1-entry-components": {
"version": "13.1.0-beta",
"description": "As of Angular version 13, `entryComponents` are no longer necessary.",
"factory": "./migrations/entry-components/index"
},
"migration-v14-entry-components": {
"version": "14.0.0-beta",
"description": "As of Angular version 13, `entryComponents` are no longer necessary.",
"factory": "./migrations/entry-components/index"
}
}
}
18 changes: 18 additions & 0 deletions packages/core/schematics/migrations/entry-components/BUILD.bazel
@@ -0,0 +1,18 @@
load("//tools:defaults.bzl", "ts_library")

ts_library(
name = "entry-components",
srcs = glob(["**/*.ts"]),
tsconfig = "//packages/core/schematics:tsconfig.json",
visibility = [
"//packages/core/schematics:__pkg__",
"//packages/core/schematics/migrations/google3:__pkg__",
"//packages/core/schematics/test:__pkg__",
],
deps = [
"//packages/core/schematics/utils",
"@npm//@angular-devkit/schematics",
"@npm//@types/node",
"@npm//typescript",
],
)
33 changes: 33 additions & 0 deletions packages/core/schematics/migrations/entry-components/README.md
@@ -0,0 +1,33 @@
## entryComponents migration
As of Angular version 13, the `entryComponents` option in `@NgModule` and `@Component` isn't
necessary anymore. This migration will automatically remove any usages.

#### Before
```ts
import { NgModule, Component } from '@angular/core';

@Component({selector: 'my-comp', template: ''})
export class MyComp {}

@NgModule({
declarations: [MyComp],
entryComponents: [MyComp],
exports: [MyComp]
})
export class MyModule {}
```

#### After
```ts
import { NgModule, Component } from '@angular/core';

@Component({selector: 'my-comp', template: ''})
export class MyComp {}

@NgModule({
declarations: [MyComp],
exports: [MyComp]
})
export class MyModule {}
```

56 changes: 56 additions & 0 deletions packages/core/schematics/migrations/entry-components/index.ts
@@ -0,0 +1,56 @@
/**
* @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 {Rule, SchematicsException, Tree} from '@angular-devkit/schematics';
import {relative} from 'path';
import ts from 'typescript';

import {getProjectTsConfigPaths} from '../../utils/project_tsconfig_paths';
import {canMigrateFile, createMigrationProgram} from '../../utils/typescript/compiler_host';

import {migrateEntryComponentsUsages} from './util';


/** Migration that removes `entryComponents` usages. */
export default function(): Rule {
return async (tree: Tree) => {
const {buildPaths, testPaths} = await getProjectTsConfigPaths(tree);
const basePath = process.cwd();
const allPaths = [...buildPaths, ...testPaths];

if (!allPaths.length) {
throw new SchematicsException(
'Could not find any tsconfig file. Cannot remove `entryComponents`.');
}

for (const tsconfigPath of allPaths) {
runEntryComponentsMigration(tree, tsconfigPath, basePath);
}
};
}

function runEntryComponentsMigration(tree: Tree, tsconfigPath: string, basePath: string) {
const {program} = createMigrationProgram(tree, tsconfigPath, basePath);
const typeChecker = program.getTypeChecker();
const printer = ts.createPrinter();

program.getSourceFiles()
.filter(sourceFile => canMigrateFile(basePath, sourceFile, program))
.forEach(sourceFile => {
const usages = migrateEntryComponentsUsages(typeChecker, printer, sourceFile);

if (usages.length > 0) {
const update = tree.beginUpdate(relative(basePath, sourceFile.fileName));
usages.forEach(usage => {
update.remove(usage.start, usage.length);
update.insertRight(usage.start, usage.replacement);
});
tree.commitUpdate(update);
}
});
}
51 changes: 51 additions & 0 deletions packages/core/schematics/migrations/entry-components/util.ts
@@ -0,0 +1,51 @@
/**
* @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 ts from 'typescript';

import {getCallDecoratorImport} from '../../utils/typescript/decorators';

/** Finds and migrates all Angular decorators that pass in `entryComponents`. */
export function migrateEntryComponentsUsages(
typeChecker: ts.TypeChecker, printer: ts.Printer, sourceFile: ts.SourceFile) {
const results: {start: number, length: number, end: number, replacement: string}[] = [];

sourceFile.forEachChild(function walk(node: ts.Node) {
if (ts.isDecorator(node) && ts.isCallExpression(node.expression) &&
node.expression.arguments.length === 1 &&
ts.isObjectLiteralExpression(node.expression.arguments[0])) {
const analysis = getCallDecoratorImport(typeChecker, node);

if (analysis && analysis.importModule === '@angular/core' &&
(analysis.name === 'Component' || analysis.name === 'NgModule')) {
const literal = node.expression.arguments[0];
const entryComponentsProp = literal.properties.find(
property => ts.isPropertyAssignment(property) && ts.isIdentifier(property.name) &&
property.name.text === 'entryComponents');

if (entryComponentsProp) {
const replacementNode = ts.updateObjectLiteral(
literal, literal.properties.filter(prop => prop !== entryComponentsProp));

results.push({
start: literal.getStart(),
length: literal.getWidth(),
end: literal.getEnd(),
replacement: printer.printNode(ts.EmitHint.Unspecified, replacementNode, sourceFile)
});
}
}
}

node.forEachChild(walk);
});

// Sort the operations in reverse order in order to avoid
// issues when migrating multiple usages within the same file.
return results.sort((a, b) => b.start - a.start);
}
1 change: 1 addition & 0 deletions packages/core/schematics/migrations/google3/BUILD.bazel
Expand Up @@ -6,6 +6,7 @@ ts_library(
tsconfig = "//packages/core/schematics:tsconfig.json",
visibility = ["//packages/core/schematics/test/google3:__pkg__"],
deps = [
"//packages/core/schematics/migrations/entry-components",
"//packages/core/schematics/migrations/testbed-teardown",
"//packages/core/schematics/utils",
"//packages/core/schematics/utils/tslint",
Expand Down
28 changes: 28 additions & 0 deletions packages/core/schematics/migrations/google3/entryComponentsRule.ts
@@ -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
*/

import {Replacement, RuleFailure, Rules} from 'tslint';
import ts from 'typescript';

import {migrateEntryComponentsUsages} from '../entry-components/util';


/** TSLint rule that removes usages of `entryComponents`. */
export class Rule extends Rules.TypedRule {
override applyWithProgram(sourceFile: ts.SourceFile, program: ts.Program): RuleFailure[] {
const typeChecker = program.getTypeChecker();
const printer = ts.createPrinter();

return migrateEntryComponentsUsages(typeChecker, printer, sourceFile).map(usage => {
return new RuleFailure(
sourceFile, usage.start, usage.end,
'entryComponents are deprecated and don\'t need to be passed in.', this.ruleName,
new Replacement(usage.start, usage.length, usage.replacement));
});
}
}
1 change: 1 addition & 0 deletions packages/core/schematics/test/BUILD.bazel
Expand Up @@ -8,6 +8,7 @@ ts_library(
"//packages/core/schematics:migrations.json",
],
deps = [
"//packages/core/schematics/migrations/entry-components",
"//packages/core/schematics/migrations/router-link-empty-expression",
"//packages/core/schematics/migrations/testbed-teardown",
"//packages/core/schematics/utils",
Expand Down

0 comments on commit e65a245

Please sign in to comment.