mirror of
https://github.com/angular/angular
synced 2026-05-24 09:28:37 +00:00
refactor(migrations): remove unused version 16 migrations (#51926)
When updating to version 17, version 16 migrations cannot be executed thus making them redundant to have in the package. PR Close #51926
This commit is contained in:
parent
c7127b98b5
commit
e66c18ffa0
14 changed files with 1 additions and 851 deletions
|
|
@ -18,8 +18,6 @@ pkg_npm(
|
|||
visibility = ["//packages/core:__pkg__"],
|
||||
deps = [
|
||||
"//packages/core/schematics/migrations/block-template-entities:bundle",
|
||||
"//packages/core/schematics/migrations/guard-and-resolve-interfaces:bundle",
|
||||
"//packages/core/schematics/migrations/remove-module-id:bundle",
|
||||
"//packages/core/schematics/ng-generate/standalone-migration:bundle",
|
||||
],
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,17 +1,7 @@
|
|||
{
|
||||
"schematics": {
|
||||
"migration-v16-remove-module-id": {
|
||||
"version": "16.0.0",
|
||||
"description": "As of Angular v16, the `moduleId` property of `@Component` is deprecated as it no longer has any effect.",
|
||||
"factory": "./migrations/remove-module-id/bundle"
|
||||
},
|
||||
"migration-v16-guard-and-resolve-interfaces": {
|
||||
"version": "16.0.0",
|
||||
"description": "In Angular version 15.2, the guard and resolver interfaces (CanActivate, Resolve, etc) were deprecated. This migration removes imports and 'implements' clauses that contain them.",
|
||||
"factory": "./migrations/guard-and-resolve-interfaces/bundle"
|
||||
},
|
||||
"migration-v17-block-template-entities": {
|
||||
"version": "17.0.0-0",
|
||||
"version": "17.0.0",
|
||||
"description": "Angular v17 introduces a new control flow syntax that uses the @ and } characters. This migration replaces the existing usages with their corresponding HTML entities.",
|
||||
"factory": "./migrations/block-template-entities/bundle"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ ts_library(
|
|||
srcs = glob(["**/*.ts"]),
|
||||
tsconfig = "//packages/core/schematics:tsconfig.json",
|
||||
deps = [
|
||||
"//packages/core/schematics/migrations/guard-and-resolve-interfaces",
|
||||
"//packages/core/schematics/utils",
|
||||
"//packages/core/schematics/utils/tslint",
|
||||
"@npm//tslint",
|
||||
|
|
@ -13,15 +12,6 @@ ts_library(
|
|||
],
|
||||
)
|
||||
|
||||
esbuild(
|
||||
name = "guard_and_resolve_interfaces_cjs",
|
||||
entry_point = ":guardAndResolveInterfacesRule.ts",
|
||||
format = "cjs",
|
||||
output = "guardAndResolveInterfacesCjsRule.js",
|
||||
platform = "node",
|
||||
deps = [":google3"],
|
||||
)
|
||||
|
||||
esbuild(
|
||||
name = "wait_for_async_rule_cjs",
|
||||
entry_point = ":waitForAsyncRule.ts",
|
||||
|
|
@ -34,7 +24,6 @@ esbuild(
|
|||
filegroup(
|
||||
name = "google3_cjs",
|
||||
srcs = [
|
||||
":guard_and_resolve_interfaces_cjs",
|
||||
":wait_for_async_rule_cjs",
|
||||
],
|
||||
visibility = ["//packages/core/schematics/test/google3:__pkg__"],
|
||||
|
|
|
|||
|
|
@ -1,31 +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 {Replacement, RuleFailure, Rules} from 'tslint';
|
||||
import ts from 'typescript';
|
||||
|
||||
import {migrateFile} from '../guard-and-resolve-interfaces/util';
|
||||
|
||||
/** TSLint rule for the guard and resolve interfaces migration. */
|
||||
export class Rule extends Rules.TypedRule {
|
||||
override applyWithProgram(sourceFile: ts.SourceFile, program: ts.Program): RuleFailure[] {
|
||||
const failures: RuleFailure[] = [];
|
||||
|
||||
const rewriter = (startPos: number, origLength: number, text: string) => {
|
||||
const failure = new RuleFailure(
|
||||
sourceFile, startPos, startPos + origLength,
|
||||
'The guard and resolve interfaces in the Angular router are being removed.',
|
||||
this.ruleName, new Replacement(startPos, origLength, text));
|
||||
failures.push(failure);
|
||||
};
|
||||
|
||||
migrateFile(sourceFile, program.getTypeChecker(), rewriter);
|
||||
|
||||
return failures;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
load("//tools:defaults.bzl", "esbuild", "ts_library")
|
||||
|
||||
package(
|
||||
default_visibility = [
|
||||
"//packages/core/schematics:__pkg__",
|
||||
"//packages/core/schematics/migrations/google3:__pkg__",
|
||||
"//packages/core/schematics/test:__pkg__",
|
||||
],
|
||||
)
|
||||
|
||||
ts_library(
|
||||
name = "guard-and-resolve-interfaces",
|
||||
srcs = glob(["**/*.ts"]),
|
||||
tsconfig = "//packages/core/schematics:tsconfig.json",
|
||||
deps = [
|
||||
"//packages/core/schematics/utils",
|
||||
"@npm//@angular-devkit/schematics",
|
||||
"@npm//@types/node",
|
||||
"@npm//typescript",
|
||||
],
|
||||
)
|
||||
|
||||
esbuild(
|
||||
name = "bundle",
|
||||
entry_point = ":index.ts",
|
||||
external = [
|
||||
"@angular-devkit/*",
|
||||
"typescript",
|
||||
],
|
||||
format = "cjs",
|
||||
platform = "node",
|
||||
deps = [":guard-and-resolve-interfaces"],
|
||||
)
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
## Guard and resolver interfaces migration
|
||||
|
||||
Since Angular v15.2, the `Router` guard and resolver interfaces have been deprecated.
|
||||
Injectable classes can still be injected at the `Route` definition, but the `Router`
|
||||
will not export interfaces that define a specific shape for those classes. Instead,
|
||||
the guards and resolvers on the Route can inject the class and call whatever method
|
||||
they want, regardless of the guard name.
|
||||
|
||||
#### Before
|
||||
```ts
|
||||
import { Injectable } from '@angular/router';
|
||||
import { CanActivate } from '@angular/router';
|
||||
|
||||
@Injectable({providedIn: 'root'})
|
||||
export class MyGuard implements CanActivate {
|
||||
canActivate() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### After
|
||||
```ts
|
||||
import { Injectable } from '@angular/router';
|
||||
|
||||
@Injectable({providedIn: 'root'})
|
||||
export class MyGuard {
|
||||
canActivate() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
@ -1,59 +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 {Rule, SchematicsException, Tree, UpdateRecorder} from '@angular-devkit/schematics';
|
||||
import {relative} from 'path';
|
||||
|
||||
import {getProjectTsConfigPaths} from '../../utils/project_tsconfig_paths';
|
||||
import {canMigrateFile, createMigrationProgram} from '../../utils/typescript/compiler_host';
|
||||
|
||||
import {migrateFile} from './util';
|
||||
|
||||
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 run the guard and resolve interfaces migration.');
|
||||
}
|
||||
|
||||
for (const tsconfigPath of allPaths) {
|
||||
runGuardAndResolveInterfacesMigration(tree, tsconfigPath, basePath);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function runGuardAndResolveInterfacesMigration(tree: Tree, tsconfigPath: string, basePath: string) {
|
||||
const program = createMigrationProgram(tree, tsconfigPath, basePath);
|
||||
const typeChecker = program.getTypeChecker();
|
||||
const sourceFiles =
|
||||
program.getSourceFiles().filter(sourceFile => canMigrateFile(basePath, sourceFile, program));
|
||||
|
||||
for (const sourceFile of sourceFiles) {
|
||||
let update: UpdateRecorder|null = null;
|
||||
|
||||
const rewriter = (startPos: number, width: number, text: string|null) => {
|
||||
if (update === null) {
|
||||
// Lazily initialize update, because most files will not require migration.
|
||||
update = tree.beginUpdate(relative(basePath, sourceFile.fileName));
|
||||
}
|
||||
update.remove(startPos, width);
|
||||
if (text !== null) {
|
||||
update.insertLeft(startPos, text);
|
||||
}
|
||||
};
|
||||
migrateFile(sourceFile, typeChecker, rewriter);
|
||||
|
||||
if (update !== null) {
|
||||
tree.commitUpdate(update);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,170 +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 ts from 'typescript';
|
||||
|
||||
import {ChangeTracker} from '../../utils/change_tracker';
|
||||
import {getImportOfIdentifier, getImportSpecifier, getImportSpecifiers, removeSymbolFromNamedImports, replaceImport} from '../../utils/typescript/imports';
|
||||
import {closestNode} from '../../utils/typescript/nodes';
|
||||
|
||||
export const deprecatedInterfaces =
|
||||
new Set(['CanLoad', 'CanMatch', 'CanActivate', 'CanDeactivate', 'CanActivateChild', 'Resolve']);
|
||||
export const routerModule = '@angular/router';
|
||||
|
||||
export type RewriteFn = (startPos: number, width: number, text: string) => void;
|
||||
|
||||
export function migrateFile(
|
||||
sourceFile: ts.SourceFile, typeChecker: ts.TypeChecker, rewriteFn: RewriteFn) {
|
||||
const deprecatedImports =
|
||||
getImportSpecifiers(sourceFile, routerModule, Array.from(deprecatedInterfaces));
|
||||
if (deprecatedImports.length === 0) {
|
||||
return;
|
||||
}
|
||||
const changeTracker = new ChangeTracker(ts.createPrinter());
|
||||
// Map of original named imports to the most recent migrated node. We might update it multiple
|
||||
// times so we need to accumulate updates.
|
||||
const updatedImports = new Map<ts.NamedImports, ts.NamedImports>();
|
||||
const updatedImplements = new Map<ts.HeritageClause, ts.HeritageClause>();
|
||||
|
||||
findUsages(
|
||||
sourceFile, typeChecker, updatedImplements, updatedImports, changeTracker, deprecatedImports);
|
||||
findImports(sourceFile, updatedImports);
|
||||
|
||||
for (const [originalNode, rewrittenNode] of updatedImports.entries()) {
|
||||
if (rewrittenNode.elements.length > 0) {
|
||||
changeTracker.replaceNode(originalNode, rewrittenNode);
|
||||
} else {
|
||||
const importDeclaration = originalNode.parent.parent;
|
||||
changeTracker.removeNode(importDeclaration);
|
||||
}
|
||||
}
|
||||
|
||||
for (const [originalNode, rewrittenNode] of updatedImplements.entries()) {
|
||||
if (rewrittenNode.types.length > 0) {
|
||||
changeTracker.replaceNode(originalNode, rewrittenNode);
|
||||
} else {
|
||||
changeTracker.removeNode(originalNode);
|
||||
}
|
||||
}
|
||||
|
||||
for (const changesInFile of changeTracker.recordChanges().values()) {
|
||||
for (const change of changesInFile) {
|
||||
rewriteFn(change.start, change.removeLength ?? 0, change.text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function findImports(
|
||||
sourceFile: ts.SourceFile, updatedImports: Map<ts.NamedImports, ts.NamedImports>) {
|
||||
for (const deprecatedInterface of deprecatedInterfaces) {
|
||||
const importSpecifier = getImportSpecifier(sourceFile, routerModule, deprecatedInterface);
|
||||
|
||||
// No `specifier` found, nothing to migrate, exit early.
|
||||
if (importSpecifier === null) continue;
|
||||
|
||||
const namedImports = closestNode(importSpecifier, ts.isNamedImports);
|
||||
if (namedImports !== null) {
|
||||
const importToUpdate = updatedImports.get(namedImports) ?? namedImports;
|
||||
const rewrittenNamedImports = removeSymbolFromNamedImports(importToUpdate, importSpecifier);
|
||||
updatedImports.set(namedImports, rewrittenNamedImports);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function findUsages(
|
||||
sourceFile: ts.SourceFile, typeChecker: ts.TypeChecker,
|
||||
updatedImplements: Map<ts.HeritageClause, ts.HeritageClause>,
|
||||
updatedImports: Map<ts.NamedImports, ts.NamedImports>, changeTracker: ChangeTracker,
|
||||
deprecatedImports: ts.ImportSpecifier[]): void {
|
||||
const visitNode = (node: ts.Node) => {
|
||||
if (ts.isImportSpecifier(node)) {
|
||||
// Skip this node and all of its children; imports are a special case.
|
||||
return;
|
||||
}
|
||||
if ((ts.isInterfaceDeclaration(node) || ts.isClassLike(node)) && node.heritageClauses) {
|
||||
for (const heritageClause of node.heritageClauses) {
|
||||
visitHeritageClause(heritageClause, typeChecker, updatedImplements, deprecatedImports);
|
||||
}
|
||||
ts.forEachChild(node, visitNode);
|
||||
} else if (ts.isTypeReferenceNode(node)) {
|
||||
visitTypeReference(
|
||||
node, typeChecker, changeTracker, sourceFile, updatedImports, deprecatedImports);
|
||||
} else {
|
||||
ts.forEachChild(node, visitNode);
|
||||
}
|
||||
};
|
||||
ts.forEachChild(sourceFile, visitNode);
|
||||
}
|
||||
|
||||
function visitHeritageClause(
|
||||
heritageClause: ts.HeritageClause, typeChecker: ts.TypeChecker,
|
||||
updatedImplements: Map<ts.HeritageClause, ts.HeritageClause>,
|
||||
deprecatedImports: ts.ImportSpecifier[]) {
|
||||
const visitChildren = (node: ts.Node): void => {
|
||||
if (ts.isIdentifier(node)) {
|
||||
if (deprecatedImports.some(importSpecifier => importSpecifier.name.text === node.text)) {
|
||||
const importIdentifier = getImportOfIdentifier(typeChecker, node);
|
||||
if (importIdentifier?.importModule === routerModule &&
|
||||
deprecatedInterfaces.has(importIdentifier.name)) {
|
||||
const heritageClauseToUpdate = updatedImplements.get(heritageClause) ?? heritageClause;
|
||||
const mostRecentUpdate = ts.factory.updateHeritageClause(
|
||||
heritageClauseToUpdate, heritageClauseToUpdate.types.filter(current => {
|
||||
return !ts.isExpressionWithTypeArguments(current) || current.expression !== node;
|
||||
}));
|
||||
updatedImplements.set(heritageClause, mostRecentUpdate);
|
||||
}
|
||||
}
|
||||
}
|
||||
ts.forEachChild(node, visitChildren);
|
||||
};
|
||||
ts.forEachChild(heritageClause, visitChildren);
|
||||
}
|
||||
|
||||
function visitTypeReference(
|
||||
typeReference: ts.TypeReferenceNode, typeChecker: ts.TypeChecker, changeTracker: ChangeTracker,
|
||||
sourceFile: ts.SourceFile, updatedImports: Map<ts.NamedImports, ts.NamedImports>,
|
||||
deprecatedImports: ts.ImportSpecifier[]) {
|
||||
const visitTypeReferenceChildren = (node: ts.Node): void => {
|
||||
if (ts.isIdentifier(node) &&
|
||||
deprecatedImports.some(importSpecifier => importSpecifier.name.text === node.text)) {
|
||||
const importIdentifier = getImportOfIdentifier(typeChecker, node);
|
||||
if (importIdentifier?.importModule === routerModule &&
|
||||
deprecatedInterfaces.has(importIdentifier.name)) {
|
||||
const {name: interfaceName} = importIdentifier;
|
||||
const functionTypeName = `${interfaceName}Fn`;
|
||||
const classFunctionName =
|
||||
`${interfaceName.charAt(0).toLocaleLowerCase()}${interfaceName.slice(1)}`;
|
||||
// i.e. Resolve<T> => {resolve: ResolveFn<T>}
|
||||
const replacement = ts.factory.createTypeLiteralNode([ts.factory.createPropertySignature(
|
||||
undefined,
|
||||
ts.factory.createIdentifier(classFunctionName),
|
||||
undefined,
|
||||
ts.factory.createTypeReferenceNode(
|
||||
ts.factory.createIdentifier(functionTypeName),
|
||||
ts.isTypeReferenceNode(node.parent) ? node.parent.typeArguments : undefined,
|
||||
),
|
||||
)]);
|
||||
changeTracker.replaceNode(node.parent, replacement);
|
||||
const importSpecifier = getImportSpecifier(sourceFile, routerModule, interfaceName);
|
||||
|
||||
// No `specifier` found, nothing to migrate, exit early.
|
||||
if (importSpecifier === null) return;
|
||||
|
||||
const namedImports = closestNode(importSpecifier, ts.isNamedImports);
|
||||
if (namedImports !== null) {
|
||||
const importToUpdate = updatedImports.get(namedImports) ?? namedImports;
|
||||
const rewrittenNamedImports =
|
||||
replaceImport(importToUpdate, interfaceName, functionTypeName);
|
||||
updatedImports.set(namedImports, rewrittenNamedImports);
|
||||
}
|
||||
}
|
||||
}
|
||||
ts.forEachChild(node, visitTypeReferenceChildren);
|
||||
};
|
||||
ts.forEachChild(typeReference, visitTypeReferenceChildren);
|
||||
}
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
load("//tools:defaults.bzl", "esbuild", "ts_library")
|
||||
|
||||
package(
|
||||
default_visibility = [
|
||||
"//packages/core/schematics:__pkg__",
|
||||
"//packages/core/schematics/migrations/google3:__pkg__",
|
||||
"//packages/core/schematics/test:__pkg__",
|
||||
],
|
||||
)
|
||||
|
||||
ts_library(
|
||||
name = "remove-module-id",
|
||||
srcs = glob(["**/*.ts"]),
|
||||
tsconfig = "//packages/core/schematics:tsconfig.json",
|
||||
deps = [
|
||||
"//packages/core/schematics/utils",
|
||||
"@npm//@angular-devkit/schematics",
|
||||
"@npm//@types/node",
|
||||
"@npm//typescript",
|
||||
],
|
||||
)
|
||||
|
||||
esbuild(
|
||||
name = "bundle",
|
||||
entry_point = ":index.ts",
|
||||
external = [
|
||||
"@angular-devkit/*",
|
||||
"typescript",
|
||||
],
|
||||
format = "cjs",
|
||||
platform = "node",
|
||||
deps = [":remove-module-id"],
|
||||
)
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
## RemoveModuleId migration
|
||||
|
||||
As of Angular version 9, the `moduleId` property has no effect. This migration
|
||||
removes the field from all `@Directive` or `@Component` decorators.
|
||||
|
||||
#### Before
|
||||
```ts
|
||||
@Component({
|
||||
moduleId: <..>,
|
||||
template: 'Works',
|
||||
})
|
||||
export class MyComponent {}
|
||||
```
|
||||
|
||||
#### After
|
||||
```ts
|
||||
@Component({
|
||||
template: 'Works',
|
||||
})
|
||||
export class MyComponent {}
|
||||
```
|
||||
|
|
@ -1,96 +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 {Rule, SchematicsException, Tree} from '@angular-devkit/schematics';
|
||||
import {relative} from 'path';
|
||||
import ts from 'typescript';
|
||||
|
||||
import {extractAngularClassMetadata} from '../../utils/extract_metadata';
|
||||
import {getProjectTsConfigPaths} from '../../utils/project_tsconfig_paths';
|
||||
import {canMigrateFile, createMigrationProgram} from '../../utils/typescript/compiler_host';
|
||||
import {getPropertyNameText} from '../../utils/typescript/property_name';
|
||||
|
||||
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 run the `RemoveModuleId` migration.');
|
||||
}
|
||||
|
||||
for (const tsconfigPath of allPaths) {
|
||||
runRemoveModuleIdMigration(tree, tsconfigPath, basePath);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function runRemoveModuleIdMigration(tree: Tree, tsconfigPath: string, basePath: string) {
|
||||
const program = createMigrationProgram(tree, tsconfigPath, basePath);
|
||||
const typeChecker = program.getTypeChecker();
|
||||
const sourceFiles =
|
||||
program.getSourceFiles().filter(sourceFile => canMigrateFile(basePath, sourceFile, program));
|
||||
|
||||
for (const sourceFile of sourceFiles) {
|
||||
const nodesToRemove = collectUpdatesForFile(typeChecker, sourceFile);
|
||||
if (nodesToRemove.length !== 0) {
|
||||
const update = tree.beginUpdate(relative(basePath, sourceFile.fileName));
|
||||
for (const node of nodesToRemove) {
|
||||
update.remove(node.getFullStart(), node.getFullWidth());
|
||||
}
|
||||
tree.commitUpdate(update);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function collectUpdatesForFile(typeChecker: ts.TypeChecker, file: ts.SourceFile): ts.Node[] {
|
||||
const removeNodes: ts.Node[] = [];
|
||||
const attemptMigrateClass = (node: ts.ClassDeclaration) => {
|
||||
const metadata = extractAngularClassMetadata(typeChecker, node);
|
||||
if (metadata === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const syntaxList = metadata.node.getChildren().find(
|
||||
((n): n is ts.SyntaxList => n.kind === ts.SyntaxKind.SyntaxList));
|
||||
const tokens = syntaxList?.getChildren();
|
||||
|
||||
if (!tokens) {
|
||||
return;
|
||||
}
|
||||
|
||||
let removeNextComma = false;
|
||||
for (const token of tokens) {
|
||||
// Track the comma token if it's requested to be removed.
|
||||
if (token.kind === ts.SyntaxKind.CommaToken) {
|
||||
if (removeNextComma) {
|
||||
removeNodes.push(token);
|
||||
}
|
||||
removeNextComma = false;
|
||||
}
|
||||
|
||||
// Track the `moduleId` property assignment. Note that the AST node does not include a
|
||||
// potential followed comma token.
|
||||
if (ts.isPropertyAssignment(token) && getPropertyNameText(token.name) === 'moduleId') {
|
||||
removeNodes.push(token);
|
||||
removeNextComma = true;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
ts.forEachChild(file, function visitNode(node: ts.Node) {
|
||||
if (ts.isClassDeclaration(node)) {
|
||||
attemptMigrateClass(node);
|
||||
}
|
||||
ts.forEachChild(node, visitNode);
|
||||
});
|
||||
|
||||
return removeNodes;
|
||||
}
|
||||
|
|
@ -21,10 +21,6 @@ jasmine_node_test(
|
|||
"//packages/core/schematics:migrations.json",
|
||||
"//packages/core/schematics/migrations/block-template-entities",
|
||||
"//packages/core/schematics/migrations/block-template-entities:bundle",
|
||||
"//packages/core/schematics/migrations/guard-and-resolve-interfaces",
|
||||
"//packages/core/schematics/migrations/guard-and-resolve-interfaces:bundle",
|
||||
"//packages/core/schematics/migrations/remove-module-id",
|
||||
"//packages/core/schematics/migrations/remove-module-id:bundle",
|
||||
"//packages/core/schematics/ng-generate/standalone-migration",
|
||||
"//packages/core/schematics/ng-generate/standalone-migration:bundle",
|
||||
"//packages/core/schematics/ng-generate/standalone-migration:static_files",
|
||||
|
|
|
|||
|
|
@ -1,200 +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 {getSystemPath, normalize, virtualFs} from '@angular-devkit/core';
|
||||
import {TempScopedNodeJsSyncHost} from '@angular-devkit/core/node/testing';
|
||||
import {HostTree} from '@angular-devkit/schematics';
|
||||
import {SchematicTestRunner, UnitTestTree} from '@angular-devkit/schematics/testing';
|
||||
import {runfiles} from '@bazel/runfiles';
|
||||
import shx from 'shelljs';
|
||||
|
||||
describe('Guard and Resolve interfaces migration', () => {
|
||||
let runner: SchematicTestRunner;
|
||||
let host: TempScopedNodeJsSyncHost;
|
||||
let tree: UnitTestTree;
|
||||
let tmpDirPath: string;
|
||||
let previousWorkingDir: string;
|
||||
|
||||
function writeFile(filePath: string, contents: string) {
|
||||
host.sync.write(normalize(filePath), virtualFs.stringToFileBuffer(contents));
|
||||
}
|
||||
|
||||
function runMigration() {
|
||||
return runner.runSchematic('migration-v16-guard-and-resolve-interfaces', {}, tree);
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
runner = new SchematicTestRunner('test', runfiles.resolvePackageRelative('../migrations.json'));
|
||||
host = new TempScopedNodeJsSyncHost();
|
||||
tree = new UnitTestTree(new HostTree(host));
|
||||
|
||||
writeFile('/tsconfig.json', JSON.stringify({
|
||||
compilerOptions: {
|
||||
lib: ['es2015'],
|
||||
strictNullChecks: true,
|
||||
},
|
||||
}));
|
||||
|
||||
writeFile('/angular.json', JSON.stringify({
|
||||
version: 1,
|
||||
projects: {t: {root: '', architect: {build: {options: {tsConfig: './tsconfig.json'}}}}}
|
||||
}));
|
||||
|
||||
// We need to declare the Angular symbols we're testing for, otherwise type checking won't work.
|
||||
writeFile('/node_modules/@angular/router/index.d.ts', `
|
||||
export declare interface Resolve<T> { }
|
||||
export declare interface CanActivate { }
|
||||
export declare interface CanActivateChild { }
|
||||
export declare interface CanDeactivate<T> { }
|
||||
export declare interface CanLoad { }
|
||||
export declare interface CanMatch { }`);
|
||||
|
||||
previousWorkingDir = shx.pwd();
|
||||
tmpDirPath = getSystemPath(host.root);
|
||||
|
||||
// Switch into the temporary directory path. This allows us to run
|
||||
// the schematic against our custom unit test tree.
|
||||
shx.cd(tmpDirPath);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
shx.cd(previousWorkingDir);
|
||||
shx.rm('-r', tmpDirPath);
|
||||
});
|
||||
|
||||
it('should be able to remove multiple imports and retain other symbols', async () => {
|
||||
writeFile('/index.ts', `
|
||||
import {Router, Resolve, CanActivate} from '@angular/router';
|
||||
`);
|
||||
|
||||
await runMigration();
|
||||
const content = tree.readContent('/index.ts');
|
||||
expect(content).toContain(`import { Router } from '@angular/router'`);
|
||||
});
|
||||
|
||||
it('should be able to multiple imports from different import statements', async () => {
|
||||
writeFile('/index.ts', `
|
||||
import {Router, Resolve} from '@angular/router';
|
||||
import {CanActivate} from '@angular/router';
|
||||
`);
|
||||
|
||||
await runMigration();
|
||||
const content = tree.readContent('/index.ts');
|
||||
expect(content).toContain(`import { Router } from '@angular/router'`);
|
||||
expect(content).not.toContain(`CanActivate`);
|
||||
expect(content).not.toContain(`Resolve`);
|
||||
});
|
||||
|
||||
it('should be able to remove implements', async () => {
|
||||
writeFile('/index.ts', `
|
||||
import {Resolve} from '@angular/router';
|
||||
|
||||
class A implements X, Resolve {}
|
||||
`);
|
||||
|
||||
await runMigration();
|
||||
const content = tree.readContent('/index.ts');
|
||||
expect(content).toContain(`implements X {}`);
|
||||
});
|
||||
|
||||
it('should be able to remove last implements', async () => {
|
||||
writeFile('/index.ts', `
|
||||
import {Resolve} from '@angular/router';
|
||||
|
||||
class A implements Resolve {}
|
||||
`);
|
||||
|
||||
await runMigration();
|
||||
const content = tree.readContent('/index.ts');
|
||||
expect(content).not.toContain(`import`);
|
||||
expect(content).not.toContain(`Resolve`);
|
||||
expect(content).toMatch(/class A\s+\{\}/);
|
||||
});
|
||||
|
||||
it('should be able to remove all deprecated imports and implements', async () => {
|
||||
writeFile('/index.ts', `
|
||||
import {Resolve, CanActivate, CanActivateChild, CanDeactivate, CanMatch, CanLoad} from '@angular/router';
|
||||
|
||||
class A implements Resolve {}
|
||||
class B implements CanActivate, CanActivateChild {}
|
||||
class C implements CanMatch, CanLoad {}
|
||||
class D implements CanDeactivate {}
|
||||
`);
|
||||
|
||||
await runMigration();
|
||||
const content = tree.readContent('/index.ts');
|
||||
expect(content).not.toContain(`implements`);
|
||||
expect(content).not.toContain(`import`);
|
||||
expect(content).toMatch(/class A\s+\{\}/);
|
||||
expect(content).toMatch(/class B\s+\{\}/);
|
||||
expect(content).toMatch(/class C\s+\{\}/);
|
||||
expect(content).toMatch(/class D\s+\{\}/);
|
||||
});
|
||||
|
||||
it('should migrates type references to function types', async () => {
|
||||
writeFile('/index.ts', `
|
||||
import {Resolve, CanActivate, CanActivateChild, CanDeactivate, CanMatch, CanLoad} from '@angular/router';
|
||||
|
||||
function runResolver<T>(resolver: Resolve<T>): T {}
|
||||
function runCanMatch(guard: CanMatch): any {}
|
||||
function runCanActivate(guard: CanActivate): any {}
|
||||
`);
|
||||
|
||||
await runMigration();
|
||||
const content = tree.readContent('/index.ts');
|
||||
expect(content).toMatch(/import.*ResolveFn.*angular\/router/);
|
||||
expect(content).toMatch(/import.*CanMatchFn.*angular\/router/);
|
||||
expect(content).toMatch(/import.*CanActivateFn.*angular\/router/);
|
||||
expectContainsIgnoreWhitespace(content, `runResolver<T>(resolver: {resolve: ResolveFn<T>;})`);
|
||||
expectContainsIgnoreWhitespace(content, `runCanMatch(guard: {canMatch: CanMatchFn;})`);
|
||||
expectContainsIgnoreWhitespace(content, `runCanActivate(guard: {canActivate: CanActivateFn;})`);
|
||||
});
|
||||
|
||||
it('should migrate type references within type references to function types', async () => {
|
||||
writeFile('/index.ts', `
|
||||
import {Resolve} from '@angular/router';
|
||||
|
||||
export interface Something {
|
||||
resolve?: {someData?: Type<Resolve<any>>}
|
||||
}
|
||||
`);
|
||||
|
||||
await runMigration();
|
||||
const content = tree.readContent('/index.ts');
|
||||
expectContainsIgnoreWhitespace(
|
||||
content, `resolve?: {someData?: Type<{resolve: ResolveFn<any>;}>}}`);
|
||||
});
|
||||
|
||||
it('should migrates type aliases', async () => {
|
||||
writeFile('/index.ts', `
|
||||
import {Resolve} from '@angular/router';
|
||||
|
||||
export interface IssueDetailViewParams {
|
||||
issueId: string;
|
||||
taskId?: string;
|
||||
}
|
||||
export type IssueDetailViewResolver = Resolve<IssueDetailViewParams>;
|
||||
`);
|
||||
|
||||
await runMigration();
|
||||
const content = tree.readContent('/index.ts');
|
||||
expectContainsIgnoreWhitespace(
|
||||
content, `type IssueDetailViewResolver = {resolve: ResolveFn<IssueDetailViewParams>;}`);
|
||||
});
|
||||
});
|
||||
|
||||
function expectContainsIgnoreWhitespace(actual: string, expected: string) {
|
||||
const originalExpected = expected;
|
||||
const originalActual = actual;
|
||||
expected = expected.replace(/ |\n|\r/g, '');
|
||||
actual = actual.replace(/ |\n|\r/g, '');
|
||||
|
||||
expect(actual)
|
||||
.withContext(`Expected text\n${originalActual}\nto contain\n${originalExpected}\n\n`)
|
||||
.toContain(expected);
|
||||
}
|
||||
|
|
@ -1,148 +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 {getSystemPath, normalize, virtualFs} from '@angular-devkit/core';
|
||||
import {TempScopedNodeJsSyncHost} from '@angular-devkit/core/node/testing';
|
||||
import {HostTree} from '@angular-devkit/schematics';
|
||||
import {SchematicTestRunner, UnitTestTree} from '@angular-devkit/schematics/testing';
|
||||
import {runfiles} from '@bazel/runfiles';
|
||||
import shx from 'shelljs';
|
||||
|
||||
import {dedent} from './helpers';
|
||||
|
||||
describe('Remove `moduleId` migration', () => {
|
||||
let runner: SchematicTestRunner;
|
||||
let host: TempScopedNodeJsSyncHost;
|
||||
let tree: UnitTestTree;
|
||||
let tmpDirPath: string;
|
||||
let previousWorkingDir: string;
|
||||
|
||||
function writeFile(filePath: string, contents: string) {
|
||||
host.sync.write(normalize(filePath), virtualFs.stringToFileBuffer(contents));
|
||||
}
|
||||
|
||||
function runMigration() {
|
||||
return runner.runSchematic('migration-v16-remove-module-id', {}, tree);
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
runner = new SchematicTestRunner('test', runfiles.resolvePackageRelative('../migrations.json'));
|
||||
host = new TempScopedNodeJsSyncHost();
|
||||
tree = new UnitTestTree(new HostTree(host));
|
||||
|
||||
writeFile('/tsconfig.json', JSON.stringify({
|
||||
compilerOptions: {
|
||||
lib: ['es2022'],
|
||||
strict: true,
|
||||
},
|
||||
}));
|
||||
|
||||
writeFile('/angular.json', JSON.stringify({
|
||||
version: 1,
|
||||
projects: {t: {root: '', architect: {build: {options: {tsConfig: './tsconfig.json'}}}}}
|
||||
}));
|
||||
|
||||
previousWorkingDir = shx.pwd();
|
||||
tmpDirPath = getSystemPath(host.root);
|
||||
|
||||
// Switch into the temporary directory path. This allows us to run
|
||||
// the schematic against our custom unit test tree.
|
||||
shx.cd(tmpDirPath);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
shx.cd(previousWorkingDir);
|
||||
shx.rm('-r', tmpDirPath);
|
||||
});
|
||||
|
||||
it('should remove `moduleId` from `@Directive`', async () => {
|
||||
writeFile('/index.ts', dedent`
|
||||
import {Directive} from '@angular/core';
|
||||
|
||||
@Directive({
|
||||
selector: 'my-dir',
|
||||
moduleId: module.id,
|
||||
standalone: true,
|
||||
})
|
||||
export class MyDir {}
|
||||
`);
|
||||
|
||||
await runMigration();
|
||||
|
||||
expect(tree.readContent('/index.ts')).toEqual(dedent`
|
||||
import {Directive} from '@angular/core';
|
||||
|
||||
@Directive({
|
||||
selector: 'my-dir',
|
||||
standalone: true,
|
||||
})
|
||||
export class MyDir {}
|
||||
`);
|
||||
});
|
||||
|
||||
it('should be able to remove `moduleId` from multiple classes in the same file', async () => {
|
||||
writeFile('/index.ts', dedent`
|
||||
import {Directive} from '@angular/core';
|
||||
|
||||
@Directive({
|
||||
selector: 'my-dir-a',
|
||||
moduleId: module.id,
|
||||
standalone: true,
|
||||
})
|
||||
export class MyDirA {}
|
||||
|
||||
@Directive({
|
||||
selector: 'my-dir-b',
|
||||
moduleId: module.id,
|
||||
standalone: true,
|
||||
})
|
||||
export class MyDirB {}
|
||||
`);
|
||||
|
||||
await runMigration();
|
||||
|
||||
expect(tree.readContent('/index.ts')).toEqual(dedent`
|
||||
import {Directive} from '@angular/core';
|
||||
|
||||
@Directive({
|
||||
selector: 'my-dir-a',
|
||||
standalone: true,
|
||||
})
|
||||
export class MyDirA {}
|
||||
|
||||
@Directive({
|
||||
selector: 'my-dir-b',
|
||||
standalone: true,
|
||||
})
|
||||
export class MyDirB {}
|
||||
`);
|
||||
});
|
||||
|
||||
it('should not fail if `moduleId` is last property of decorator', async () => {
|
||||
writeFile('/index.ts', dedent`
|
||||
import {Directive} from '@angular/core';
|
||||
|
||||
@Directive({
|
||||
selector: 'my-dir',
|
||||
moduleId: module.id,
|
||||
})
|
||||
export class MyDir {}
|
||||
`);
|
||||
|
||||
await runMigration();
|
||||
|
||||
expect(tree.readContent('/index.ts')).toEqual(dedent`
|
||||
import {Directive} from '@angular/core';
|
||||
|
||||
@Directive({
|
||||
selector: 'my-dir',
|
||||
})
|
||||
export class MyDir {}
|
||||
`);
|
||||
});
|
||||
});
|
||||
Loading…
Reference in a new issue