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:
Alan Agius 2023-09-27 12:19:31 +00:00 committed by Dylan Hunn
parent c7127b98b5
commit e66c18ffa0
14 changed files with 1 additions and 851 deletions

View file

@ -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",
],
)

View file

@ -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"
}

View file

@ -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__"],

View file

@ -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;
}
}

View file

@ -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"],
)

View file

@ -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;
}
}
```

View file

@ -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);
}
}
}

View file

@ -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);
}

View file

@ -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"],
)

View file

@ -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 {}
```

View file

@ -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;
}

View file

@ -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",

View file

@ -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);
}

View file

@ -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 {}
`);
});
});