diff --git a/packages/core/schematics/utils/tsurge/helpers/angular_devkit/BUILD.bazel b/packages/core/schematics/utils/tsurge/helpers/angular_devkit/BUILD.bazel index 82f39c0c56af..633be9ee4947 100644 --- a/packages/core/schematics/utils/tsurge/helpers/angular_devkit/BUILD.bazel +++ b/packages/core/schematics/utils/tsurge/helpers/angular_devkit/BUILD.bazel @@ -1,10 +1,13 @@ -load("//tools:defaults.bzl", "ts_project") +load("//tools:defaults.bzl", "jasmine_test", "ts_project") package(default_visibility = ["//packages/core/schematics:__subpackages__"]) ts_project( name = "angular_devkit", - srcs = glob(["**/*.ts"]), + srcs = glob( + ["**/*.ts"], + exclude = ["*.spec.ts"], + ), deps = [ "//:node_modules/@angular-devkit/core", "//:node_modules/@angular-devkit/schematics", @@ -14,3 +17,19 @@ ts_project( "//packages/core/schematics/utils/tsurge", ], ) + +ts_project( + name = "test_lib", + testonly = True, + srcs = glob(["*.spec.ts"]), + deps = [ + ":angular_devkit", + "//:node_modules/@angular-devkit/schematics", + "//packages/core/schematics/utils/tsurge", + ], +) + +jasmine_test( + name = "test", + data = [":test_lib"], +) diff --git a/packages/core/schematics/utils/tsurge/helpers/angular_devkit/run_in_devkit.spec.ts b/packages/core/schematics/utils/tsurge/helpers/angular_devkit/run_in_devkit.spec.ts new file mode 100644 index 000000000000..cdad43a28e0d --- /dev/null +++ b/packages/core/schematics/utils/tsurge/helpers/angular_devkit/run_in_devkit.spec.ts @@ -0,0 +1,156 @@ +/** + * @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.dev/license + */ + +import {HostTree} from '@angular-devkit/schematics'; +import {UnitTestTree} from '@angular-devkit/schematics/testing/index.js'; +import { + confirmAsSerializable, + ProgramInfo, + projectFile, + Replacement, + Serializable, + TextUpdate, + TsurgeFunnelMigration, +} from '../../index'; +import {runMigrationInDevkit} from './run_in_devkit'; + +interface TestMigrationData { + replacements: Replacement[]; +} + +class TestMigration extends TsurgeFunnelMigration { + override async analyze(info: ProgramInfo): Promise> { + const replacements: Replacement[] = []; + + for (const sf of info.sourceFiles) { + const start = sf.text.indexOf('before'); + if (start === -1) { + continue; + } + + replacements.push( + new Replacement( + projectFile(sf, info), + new TextUpdate({position: start, end: start + 'before'.length, toInsert: 'after'}), + ), + ); + } + + return confirmAsSerializable({replacements}); + } + + override async combine( + unitA: TestMigrationData, + unitB: TestMigrationData, + ): Promise> { + return confirmAsSerializable({ + replacements: [...unitA.replacements, ...unitB.replacements], + }); + } + + override async globalMeta(data: TestMigrationData): Promise> { + return confirmAsSerializable(data); + } + + override async stats(): Promise> { + return confirmAsSerializable({}); + } + + override async migrate(data: TestMigrationData): Promise<{replacements: Replacement[]}> { + return {replacements: data.replacements}; + } +} + +describe('runMigrationInDevkit', () => { + let tree: UnitTestTree; + + beforeEach(() => { + tree = new UnitTestTree(new HostTree()); + }); + + it('applies replacements when no rootDir is specified', async () => { + tree.create( + '/angular.json', + JSON.stringify({ + version: 1, + projects: { + app: { + root: '', + architect: { + build: { + options: { + tsConfig: './tsconfig.app.json', + }, + }, + }, + }, + }, + }), + ); + tree.create( + '/tsconfig.app.json', + JSON.stringify({ + compilerOptions: { + module: 'preserve', + target: 'ES2022', + noLib: true, + }, + include: ['src/**/*.ts'], + }), + ); + tree.create('/src/app/app.ts', `export const value = 'before';\n`); + + await runMigrationInDevkit({ + tree, + getMigration: () => new TestMigration(), + }); + + expect(tree.readContent('/src/app/app.ts')).toContain(`'after'`); + }); + + it('applies replacements to workspace-relative paths when tsconfig rootDir is narrower', async () => { + tree.create( + '/angular.json', + JSON.stringify({ + version: 1, + projects: { + app: { + root: '', + architect: { + build: { + options: { + tsConfig: './tsconfig.app.json', + }, + }, + }, + }, + }, + }), + ); + tree.create( + '/tsconfig.app.json', + JSON.stringify({ + compilerOptions: { + rootDir: './src', + module: 'preserve', + target: 'ES2022', + noLib: true, + }, + include: ['src/**/*.ts'], + }), + ); + tree.create('/src/app/app.ts', `export const value = 'before';\n`); + + await runMigrationInDevkit({ + tree, + getMigration: () => new TestMigration(), + }); + + expect(tree.readContent('/src/app/app.ts')).toContain(`'after'`); + }); +}); diff --git a/packages/core/schematics/utils/tsurge/helpers/angular_devkit/run_in_devkit.ts b/packages/core/schematics/utils/tsurge/helpers/angular_devkit/run_in_devkit.ts index 646c0b29e1ef..c934aedf1636 100644 --- a/packages/core/schematics/utils/tsurge/helpers/angular_devkit/run_in_devkit.ts +++ b/packages/core/schematics/utils/tsurge/helpers/angular_devkit/run_in_devkit.ts @@ -79,6 +79,9 @@ export async function runMigrationInDevkit( for (const tsconfigPath of tsconfigPaths) { config.beforeProgramCreation?.(tsconfigPath, MigrationStage.Analysis); const info = migration.createProgram(tsconfigPath, fs); + // Devkit tree updates need workspace-relative paths. Keep `sortedRootDirs` + // intact for logical file IDs, but make `rootRelativePath` resolve from `/`. + info.projectRoot = fs.pwd(); modifyProgramInfoToEnsureNonOverlappingFiles(tsconfigPath, info, compilationUnitAssignments); @@ -107,6 +110,7 @@ export async function runMigrationInDevkit( for (const tsconfigPath of tsconfigPaths) { config.beforeProgramCreation?.(tsconfigPath, MigrationStage.Migrate); const info = migration.createProgram(tsconfigPath, fs); + info.projectRoot = fs.pwd(); modifyProgramInfoToEnsureNonOverlappingFiles(tsconfigPath, info, compilationUnitAssignments); config.afterProgramCreation?.(info, fs, MigrationStage.Migrate);