diff --git a/packages/core/schematics/BUILD.bazel b/packages/core/schematics/BUILD.bazel index e52ebf28cf8..f5176346cfb 100644 --- a/packages/core/schematics/BUILD.bazel +++ b/packages/core/schematics/BUILD.bazel @@ -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", ], ) diff --git a/packages/core/schematics/migrations.json b/packages/core/schematics/migrations.json index 924ed872d1c..67969ae5b37 100644 --- a/packages/core/schematics/migrations.json +++ b/packages/core/schematics/migrations.json @@ -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" } diff --git a/packages/core/schematics/migrations/google3/BUILD.bazel b/packages/core/schematics/migrations/google3/BUILD.bazel index 0c99c36b1f1..b4ee726449a 100644 --- a/packages/core/schematics/migrations/google3/BUILD.bazel +++ b/packages/core/schematics/migrations/google3/BUILD.bazel @@ -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__"], diff --git a/packages/core/schematics/migrations/google3/guardAndResolveInterfacesRule.ts b/packages/core/schematics/migrations/google3/guardAndResolveInterfacesRule.ts deleted file mode 100644 index a78efefe143..00000000000 --- a/packages/core/schematics/migrations/google3/guardAndResolveInterfacesRule.ts +++ /dev/null @@ -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; - } -} diff --git a/packages/core/schematics/migrations/guard-and-resolve-interfaces/BUILD.bazel b/packages/core/schematics/migrations/guard-and-resolve-interfaces/BUILD.bazel deleted file mode 100644 index 407c443b994..00000000000 --- a/packages/core/schematics/migrations/guard-and-resolve-interfaces/BUILD.bazel +++ /dev/null @@ -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"], -) diff --git a/packages/core/schematics/migrations/guard-and-resolve-interfaces/README.md b/packages/core/schematics/migrations/guard-and-resolve-interfaces/README.md deleted file mode 100644 index 13fbfa24f23..00000000000 --- a/packages/core/schematics/migrations/guard-and-resolve-interfaces/README.md +++ /dev/null @@ -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; - } -} -``` diff --git a/packages/core/schematics/migrations/guard-and-resolve-interfaces/index.ts b/packages/core/schematics/migrations/guard-and-resolve-interfaces/index.ts deleted file mode 100644 index f460ac02e8f..00000000000 --- a/packages/core/schematics/migrations/guard-and-resolve-interfaces/index.ts +++ /dev/null @@ -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); - } - } -} diff --git a/packages/core/schematics/migrations/guard-and-resolve-interfaces/util.ts b/packages/core/schematics/migrations/guard-and-resolve-interfaces/util.ts deleted file mode 100644 index a59c06b0684..00000000000 --- a/packages/core/schematics/migrations/guard-and-resolve-interfaces/util.ts +++ /dev/null @@ -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(); - const updatedImplements = new Map(); - - 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) { - 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, - updatedImports: Map, 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, - 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, - 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 => {resolve: ResolveFn} - 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); -} diff --git a/packages/core/schematics/migrations/remove-module-id/BUILD.bazel b/packages/core/schematics/migrations/remove-module-id/BUILD.bazel deleted file mode 100644 index aad18a831c5..00000000000 --- a/packages/core/schematics/migrations/remove-module-id/BUILD.bazel +++ /dev/null @@ -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"], -) diff --git a/packages/core/schematics/migrations/remove-module-id/README.md b/packages/core/schematics/migrations/remove-module-id/README.md deleted file mode 100644 index 8435bcd50d0..00000000000 --- a/packages/core/schematics/migrations/remove-module-id/README.md +++ /dev/null @@ -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 {} -``` diff --git a/packages/core/schematics/migrations/remove-module-id/index.ts b/packages/core/schematics/migrations/remove-module-id/index.ts deleted file mode 100644 index 8cb961e28cf..00000000000 --- a/packages/core/schematics/migrations/remove-module-id/index.ts +++ /dev/null @@ -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; -} diff --git a/packages/core/schematics/test/BUILD.bazel b/packages/core/schematics/test/BUILD.bazel index 2e9ebb0ec6f..716f1f30a55 100644 --- a/packages/core/schematics/test/BUILD.bazel +++ b/packages/core/schematics/test/BUILD.bazel @@ -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", diff --git a/packages/core/schematics/test/guard_and_resolve_interfaces_spec.ts b/packages/core/schematics/test/guard_and_resolve_interfaces_spec.ts deleted file mode 100644 index a007cb50095..00000000000 --- a/packages/core/schematics/test/guard_and_resolve_interfaces_spec.ts +++ /dev/null @@ -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 { } - export declare interface CanActivate { } - export declare interface CanActivateChild { } - export declare interface CanDeactivate { } - 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(resolver: Resolve): 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(resolver: {resolve: ResolveFn;})`); - 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>} - } - `); - - await runMigration(); - const content = tree.readContent('/index.ts'); - expectContainsIgnoreWhitespace( - content, `resolve?: {someData?: Type<{resolve: ResolveFn;}>}}`); - }); - - 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; - `); - - await runMigration(); - const content = tree.readContent('/index.ts'); - expectContainsIgnoreWhitespace( - content, `type IssueDetailViewResolver = {resolve: ResolveFn;}`); - }); -}); - -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); -} diff --git a/packages/core/schematics/test/remove_module_id_spec.ts b/packages/core/schematics/test/remove_module_id_spec.ts deleted file mode 100644 index 0c2568f03ee..00000000000 --- a/packages/core/schematics/test/remove_module_id_spec.ts +++ /dev/null @@ -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 {} - `); - }); -});