refactor(migrations): allow tsurge migrations to run with just NgCompiler (#57562)

This is important so that migrations can easily be wired up in the
language service where only `NgCompiler` is available.

PR Close #57562
This commit is contained in:
Paul Gschwendtner 2024-08-28 11:48:56 +00:00 committed by Alex Rickabaugh
parent 674aed5984
commit 389933f079
11 changed files with 52 additions and 100 deletions

View file

@ -25,7 +25,7 @@ import {TemplateTypeChecker} from '../../../../../compiler-cli/src/ngtsc/typeche
* Interface containing the analysis information
* for an Angular program to be migrated.
*/
export interface AnalysisProgramInfo extends ProgramInfo<NgtscProgram> {
export interface AnalysisProgramInfo extends ProgramInfo {
// List of source files in the program.
sourceFiles: ts.SourceFile[];
// List of all files in the program, including external `d.ts`.

View file

@ -6,7 +6,6 @@
* found in the LICENSE file at https://angular.io/license
*/
import {NgtscProgram} from '../../../../../compiler-cli/src/ngtsc/program';
import {FileSystem} from '../../../../../compiler-cli/src/ngtsc/file_system';
import {confirmAsSerializable, Serializable} from '../../../utils/tsurge/helpers/serializable';
import {BaseProgramInfo, ProgramInfo} from '../../../utils/tsurge/program_info';
@ -26,6 +25,7 @@ import {InheritanceGraph} from './utils/inheritance_graph';
import {executeMigrationPhase} from './phase_migrate';
import {filterIncompatibilitiesForBestEffortMode} from './best_effort_mode';
import {createNgtscProgram} from '../../../utils/tsurge/helpers/ngtsc_program';
import assert from 'assert';
/**
* Tsurge migration for migrating Angular `@Input()` declarations to
@ -33,9 +33,7 @@ import {createNgtscProgram} from '../../../utils/tsurge/helpers/ngtsc_program';
*/
export class SignalInputMigration extends TsurgeComplexMigration<
CompilationUnitData,
CompilationUnitData,
NgtscProgram,
AnalysisProgramInfo
CompilationUnitData
> {
upgradeAnalysisPhaseToAvoidBatch = false;
upgradedAnalysisPhaseResults: Replacement[] | null = null;
@ -43,7 +41,7 @@ export class SignalInputMigration extends TsurgeComplexMigration<
bestEffortMode = false;
// Override the default ngtsc program creation, to add extra flags.
override createProgram(tsconfigAbsPath: string, fs?: FileSystem): BaseProgramInfo<NgtscProgram> {
override createProgram(tsconfigAbsPath: string, fs?: FileSystem): BaseProgramInfo {
return createNgtscProgram(tsconfigAbsPath, fs, {
_enableTemplateTypeChecker: true,
_compilePoisonedComponents: true,
@ -57,23 +55,20 @@ export class SignalInputMigration extends TsurgeComplexMigration<
}
// Extend the program info with the analysis information we need in every phase.
override prepareProgram(baseInfo: BaseProgramInfo<NgtscProgram>): AnalysisProgramInfo {
const info = super.prepareProgram(baseInfo);
prepareAnalysisDeps(info: ProgramInfo): AnalysisProgramInfo {
assert(info.ngCompiler !== null, 'Expected `NgCompiler` to be configured.');
return {
...info,
...prepareAnalysisInfo(
info.program.getTsProgram(),
info.program.compiler,
info.programAbsoluteRootPaths,
),
...prepareAnalysisInfo(info.program, info.ngCompiler, info.programAbsoluteRootPaths),
};
}
override async analyze(analysisDeps: AnalysisProgramInfo) {
override async analyze(info: ProgramInfo) {
const analysisDeps = this.prepareAnalysisDeps(info);
const {metaRegistry} = analysisDeps;
const knownInputs = new KnownInputs();
const result = new MigrationResult();
const host = createMigrationHost(analysisDeps);
const host = createMigrationHost(info);
const {inheritanceGraph} = executeAnalysisPhase(host, knownInputs, result, analysisDeps);
pass4__checkInheritanceOfInputs(host, inheritanceGraph, metaRegistry, knownInputs);
@ -86,11 +81,12 @@ export class SignalInputMigration extends TsurgeComplexMigration<
// Non-batch mode!
if (this.upgradeAnalysisPhaseToAvoidBatch) {
const merged = await this.merge([unitData]);
const replacements = await this.migrate(merged, analysisDeps, {
const replacements = await this.migrate(merged, info, {
knownInputs,
result,
host,
inheritanceGraph,
analysisDeps,
});
// Expose the upgraded analysis stage results.
@ -106,18 +102,19 @@ export class SignalInputMigration extends TsurgeComplexMigration<
override async migrate(
globalMetadata: CompilationUnitData,
analysisDeps: AnalysisProgramInfo,
info: ProgramInfo,
nonBatchData?: {
knownInputs: KnownInputs;
result: MigrationResult;
host: MigrationHost;
inheritanceGraph: InheritanceGraph;
analysisDeps: AnalysisProgramInfo;
},
): Promise<Replacement[]> {
const knownInputs = nonBatchData?.knownInputs ?? new KnownInputs();
const result = nonBatchData?.result ?? new MigrationResult();
const host = nonBatchData?.host ?? createMigrationHost(analysisDeps);
const {metaRegistry} = analysisDeps;
const host = nonBatchData?.host ?? createMigrationHost(info);
const analysisDeps = nonBatchData?.analysisDeps ?? this.prepareAnalysisDeps(info);
let inheritanceGraph: InheritanceGraph;
// Can't re-use analysis structures, so re-build them.
@ -129,7 +126,7 @@ export class SignalInputMigration extends TsurgeComplexMigration<
inheritanceGraph = nonBatchData.inheritanceGraph;
}
pass4__checkInheritanceOfInputs(host, inheritanceGraph, metaRegistry, knownInputs);
pass4__checkInheritanceOfInputs(host, inheritanceGraph, analysisDeps.metaRegistry, knownInputs);
if (this.bestEffortMode) {
filterIncompatibilitiesForBestEffortMode(knownInputs);
}
@ -140,7 +137,7 @@ export class SignalInputMigration extends TsurgeComplexMigration<
}
}
function createMigrationHost(info: ProgramInfo<NgtscProgram>): MigrationHost {
function createMigrationHost(info: ProgramInfo): MigrationHost {
return new MigrationHost(
/* projectDir */ info.projectDirAbsPath,
/* isMigratingCore */ false,

View file

@ -6,11 +6,8 @@
* found in the LICENSE file at https://angular.io/license
*/
import assert from 'assert';
import path from 'path';
import ts from 'typescript';
import {FileSystem} from '../../../../compiler-cli/src/ngtsc/file_system';
import {NgtscProgram} from '../../../../compiler-cli/src/ngtsc/program';
import {isShim} from '../../../../compiler-cli/src/ngtsc/shims';
import {createNgtscProgram} from './helpers/ngtsc_program';
import {BaseProgramInfo, ProgramInfo} from './program_info';
@ -21,22 +18,16 @@ import {BaseProgramInfo, ProgramInfo} from './program_info';
* For example, this class exposes methods to conveniently create
* TypeScript programs, while also allowing migration authors to override.
*/
export abstract class TsurgeBaseMigration<
TsProgramType extends ts.Program | NgtscProgram = NgtscProgram,
PreparationInfo = ProgramInfo<TsProgramType>,
> {
export abstract class TsurgeBaseMigration {
// By default, ngtsc programs are being created.
createProgram(tsconfigAbsPath: string, fs?: FileSystem): BaseProgramInfo<TsProgramType> {
return createNgtscProgram(tsconfigAbsPath, fs) as BaseProgramInfo<TsProgramType>;
createProgram(tsconfigAbsPath: string, fs?: FileSystem): BaseProgramInfo {
return createNgtscProgram(tsconfigAbsPath, fs);
}
// Optional function to prepare the base `ProgramInfo` even further,
// for the analyze and migrate phases. E.g. determining source files.
prepareProgram(info: BaseProgramInfo<TsProgramType>): PreparationInfo {
assert(info.program instanceof NgtscProgram);
const userProgram = info.program.getTsProgram();
const fullProgramSourceFiles = userProgram.getSourceFiles();
prepareProgram(info: BaseProgramInfo): ProgramInfo {
const fullProgramSourceFiles = [...info.program.getSourceFiles()];
const sourceFiles = fullProgramSourceFiles.filter(
(f) =>
!f.isDeclarationFile &&
@ -53,6 +44,6 @@ export abstract class TsurgeBaseMigration<
sourceFiles,
fullProgramSourceFiles,
projectDirAbsPath,
} as PreparationInfo;
};
}
}

View file

@ -8,8 +8,6 @@
import {TsurgeMigration} from '../migration';
import {Serializable} from '../helpers/serializable';
import ts from 'typescript';
import {NgtscProgram} from '../../../../../compiler-cli/src/ngtsc/program';
/**
* Executes the analyze phase of the given migration against
@ -17,12 +15,8 @@ import {NgtscProgram} from '../../../../../compiler-cli/src/ngtsc/program';
*
* @returns the serializable migration unit data.
*/
export async function executeAnalyzePhase<
UnitData,
GlobalData,
TsProgramType extends ts.Program | NgtscProgram,
>(
migration: TsurgeMigration<UnitData, GlobalData, TsProgramType, unknown>,
export async function executeAnalyzePhase<UnitData, GlobalData>(
migration: TsurgeMigration<UnitData, GlobalData>,
tsconfigAbsolutePath: string,
): Promise<Serializable<UnitData>> {
const baseInfo = migration.createProgram(tsconfigAbsolutePath);

View file

@ -6,10 +6,8 @@
* found in the LICENSE file at https://angular.io/license
*/
import ts from 'typescript';
import {Serializable} from '../helpers/serializable';
import {TsurgeMigration} from '../migration';
import {NgtscProgram} from '../../../../../compiler-cli/src/ngtsc/program';
/**
* Executes the merge phase for the given migration against
@ -17,12 +15,8 @@ import {NgtscProgram} from '../../../../../compiler-cli/src/ngtsc/program';
*
* @returns the serializable migration global data.
*/
export async function executeMergePhase<
UnitData,
GlobalData,
TsProgramType extends ts.Program | NgtscProgram,
>(
migration: TsurgeMigration<UnitData, GlobalData, TsProgramType, unknown>,
export async function executeMergePhase<UnitData, GlobalData>(
migration: TsurgeMigration<UnitData, GlobalData>,
units: UnitData[],
): Promise<Serializable<GlobalData>> {
return await migration.merge(units);

View file

@ -8,8 +8,6 @@
import {TsurgeMigration} from '../migration';
import {Replacement} from '../replacement';
import {NgtscProgram} from '../../../../../compiler-cli/src/ngtsc/program';
import ts from 'typescript';
/**
* Executes the migrate phase of the given migration against
@ -20,12 +18,8 @@ import ts from 'typescript';
*
* @returns a list of text replacements to apply to disk.
*/
export async function executeMigratePhase<
UnitData,
GlobalData,
TsProgramType extends ts.Program | NgtscProgram,
>(
migration: TsurgeMigration<UnitData, GlobalData, TsProgramType, unknown>,
export async function executeMigratePhase<UnitData, GlobalData>(
migration: TsurgeMigration<UnitData, GlobalData>,
globalMetadata: GlobalData,
tsconfigAbsolutePath: string,
): Promise<Replacement[]> {

View file

@ -25,7 +25,7 @@ export function createNgtscProgram(
absoluteTsconfigPath: string,
fs?: FileSystem,
optionOverrides: NgCompilerOptions = {},
): BaseProgramInfo<NgtscProgram> {
): BaseProgramInfo {
if (fs === undefined) {
fs = new NodeJSFileSystem();
setFileSystem(fs);
@ -57,7 +57,8 @@ export function createNgtscProgram(
);
return {
program: ngtscProgram,
ngCompiler: ngtscProgram.compiler,
program: ngtscProgram.getTsProgram(),
userOptions: tsconfig.options,
programAbsoluteRootPaths: tsconfig.rootNames,
tsconfigAbsolutePath: absoluteTsconfigPath,

View file

@ -6,8 +6,6 @@
* found in the LICENSE file at https://angular.io/license
*/
import ts from 'typescript';
import {NgtscProgram} from '../../../../compiler-cli/src/ngtsc/program';
import {TsurgeBaseMigration} from './base_migration';
import {Serializable} from './helpers/serializable';
import {ProgramInfo} from './program_info';
@ -37,24 +35,9 @@ import {Replacement} from './replacement';
*
* TODO: Link design doc
*/
export type TsurgeMigration<
UnitAnalysisMetadata,
CombinedGlobalMetadata,
TsProgramType extends ts.Program | NgtscProgram = NgtscProgram,
PreparationInfo = ProgramInfo<TsProgramType>,
> =
| TsurgeComplexMigration<
UnitAnalysisMetadata,
CombinedGlobalMetadata,
TsProgramType,
PreparationInfo
>
| TsurgeFunnelMigration<
UnitAnalysisMetadata,
CombinedGlobalMetadata,
TsProgramType,
PreparationInfo
>;
export type TsurgeMigration<UnitAnalysisMetadata, CombinedGlobalMetadata> =
| TsurgeComplexMigration<UnitAnalysisMetadata, CombinedGlobalMetadata>
| TsurgeFunnelMigration<UnitAnalysisMetadata, CombinedGlobalMetadata>;
/**
* A simpler variant of a {@link TsurgeComplexMigration} that does not
@ -71,9 +54,8 @@ export type TsurgeMigration<
export abstract class TsurgeFunnelMigration<
UnitAnalysisMetadata,
CombinedGlobalMetadata,
TsProgramType extends ts.Program | NgtscProgram = NgtscProgram,
PreparationInfo = ProgramInfo<TsProgramType>,
> extends TsurgeBaseMigration<TsProgramType, PreparationInfo> {
PreparationInfo = ProgramInfo,
> extends TsurgeBaseMigration {
/** Analyzes the given TypeScript project and returns serializable compilation unit data. */
abstract analyze(info: PreparationInfo): Promise<Serializable<UnitAnalysisMetadata>>;
@ -104,11 +86,9 @@ export abstract class TsurgeFunnelMigration<
export abstract class TsurgeComplexMigration<
UnitAnalysisMetadata,
CombinedGlobalMetadata,
TsProgramType extends ts.Program | NgtscProgram = NgtscProgram,
PreparationInfo = ProgramInfo<TsProgramType>,
> extends TsurgeBaseMigration<TsProgramType, PreparationInfo> {
> extends TsurgeBaseMigration {
/** Analyzes the given TypeScript project and returns serializable compilation unit data. */
abstract analyze(info: PreparationInfo): Promise<Serializable<UnitAnalysisMetadata>>;
abstract analyze(info: ProgramInfo): Promise<Serializable<UnitAnalysisMetadata>>;
/** Merges all compilation unit data from previous analysis phases into a global result. */
abstract merge(units: UnitAnalysisMetadata[]): Promise<Serializable<CombinedGlobalMetadata>>;
@ -122,6 +102,6 @@ export abstract class TsurgeComplexMigration<
*/
abstract migrate(
globalMetadata: CombinedGlobalMetadata,
info: PreparationInfo,
info: ProgramInfo,
): Promise<Replacement[]>;
}

View file

@ -10,14 +10,16 @@ import {NgtscProgram} from '../../../../compiler-cli/src/ngtsc/program';
import {NgCompilerOptions} from '../../../../compiler-cli/src/ngtsc/core/api';
import ts from 'typescript';
import {NgCompiler} from '../../../../compiler-cli/src/ngtsc/core';
/**
* Base information for a TypeScript project, including an instantiated
* TypeScript program. Base information may be extended by user-overridden
* migration preparation methods to extend the stages with more data.
*/
export interface BaseProgramInfo<T extends NgtscProgram | ts.Program> {
program: T;
export interface BaseProgramInfo {
ngCompiler: NgCompiler | null;
program: ts.Program;
userOptions: NgCompilerOptions;
programAbsoluteRootPaths: string[];
tsconfigAbsolutePath: string;
@ -30,8 +32,8 @@ export interface BaseProgramInfo<T extends NgtscProgram | ts.Program> {
* A different interface may be used as full program information, configured via a
* {@link TsurgeMigration.prepareProgram} override.
*/
export interface ProgramInfo<T extends NgtscProgram | ts.Program> extends BaseProgramInfo<T> {
export interface ProgramInfo extends BaseProgramInfo {
sourceFiles: ts.SourceFile[];
fullProgramSourceFiles: ts.SourceFile[];
fullProgramSourceFiles: readonly ts.SourceFile[];
projectDirAbsPath: string;
}

View file

@ -10,7 +10,6 @@ import path from 'path';
import {UniqueID} from '../helpers/unique_id';
import ts from 'typescript';
import {ProgramInfo} from '../program_info';
import {NgtscProgram} from '../../../../../compiler-cli/src/ngtsc/program';
import {DtsMetadataReader} from '../../../../../compiler-cli/src/ngtsc/metadata';
import {ClassDeclaration, ReflectionHost} from '../../../../../compiler-cli/src/ngtsc/reflection';
import {Reference} from '../../../../../compiler-cli/src/ngtsc/imports';
@ -24,7 +23,7 @@ export function getIdOfOutput(projectDirAbsPath: string, prop: ts.PropertyDeclar
}
export function findOutputDeclarationsAndReferences(
{sourceFiles, projectDirAbsPath}: ProgramInfo<NgtscProgram>,
{sourceFiles, projectDirAbsPath}: ProgramInfo,
checker: ts.TypeChecker,
reflector: ReflectionHost,
dtsReader: DtsMetadataReader,

View file

@ -27,8 +27,8 @@ type GlobalMetadata = {[id: OutputID]: {canBeMigrated: boolean}};
* framework works as expected. This is **not a full migration**, but rather an example.
*/
export class OutputMigration extends TsurgeComplexMigration<AnalysisUnit, GlobalMetadata> {
override async analyze(info: ProgramInfo<NgtscProgram>) {
const program = info.program.getTsProgram();
override async analyze(info: ProgramInfo) {
const program = info.program;
const typeChecker = program.getTypeChecker();
const reflector = new TypeScriptReflectionHost(typeChecker, false);
const dtsReader = new DtsMetadataReader(typeChecker, reflector);
@ -74,8 +74,8 @@ export class OutputMigration extends TsurgeComplexMigration<AnalysisUnit, Global
return confirmAsSerializable(merged);
}
override async migrate(globalAnalysisData: GlobalMetadata, info: ProgramInfo<NgtscProgram>) {
const program = info.program.getTsProgram();
override async migrate(globalAnalysisData: GlobalMetadata, info: ProgramInfo) {
const program = info.program;
const typeChecker = program.getTypeChecker();
const reflector = new TypeScriptReflectionHost(typeChecker, false);
const dtsReader = new DtsMetadataReader(typeChecker, reflector);