diff --git a/packages/compiler-cli/src/ngtsc/imports/src/emitter.ts b/packages/compiler-cli/src/ngtsc/imports/src/emitter.ts index 3c5b31fe471..00bda87ef21 100644 --- a/packages/compiler-cli/src/ngtsc/imports/src/emitter.ts +++ b/packages/compiler-cli/src/ngtsc/imports/src/emitter.ts @@ -100,7 +100,7 @@ export enum ImportFlags { */ export type ImportedFile = ts.SourceFile | 'unknown' | null; -export const enum ReferenceEmitKind { +export enum ReferenceEmitKind { Success, Failed, } diff --git a/packages/core/schematics/migrations/output-migration/output-migration.ts b/packages/core/schematics/migrations/output-migration/output-migration.ts index c2734535daf..082900c4bf3 100644 --- a/packages/core/schematics/migrations/output-migration/output-migration.ts +++ b/packages/core/schematics/migrations/output-migration/output-migration.ts @@ -101,11 +101,14 @@ export class OutputMigration extends TsurgeFunnelMigration< const reflector = new TypeScriptReflectionHost(checker); const dtsReader = new DtsMetadataReader(checker, reflector); const evaluator = new PartialEvaluator(reflector, checker, null); - const ngCompiler = info.ngCompiler; - assert(ngCompiler !== null, 'Requires ngCompiler to run the migration'); - const resourceLoader = ngCompiler['resourceManager']; - // Pre-Analyze the program and get access to the template type checker. - const {templateTypeChecker} = ngCompiler['ensureAnalyzed'](); + const resourceLoader = info.ngCompiler?.['resourceManager'] ?? null; + + // Pre-analyze the program and get access to the template type checker. + // If we are processing a non-Angular target, there is no template info. + const {templateTypeChecker} = info.ngCompiler?.['ensureAnalyzed']() ?? { + templateTypeChecker: null, + }; + const knownFields: KnownFields = { // Note: We don't support cross-target migration of `Partial` usages. // This is an acceptable limitation for performance reasons. diff --git a/packages/core/schematics/migrations/signal-migration/src/analysis_deps.ts b/packages/core/schematics/migrations/signal-migration/src/analysis_deps.ts index ac36c74ceed..bc4523c9138 100644 --- a/packages/core/schematics/migrations/signal-migration/src/analysis_deps.ts +++ b/packages/core/schematics/migrations/signal-migration/src/analysis_deps.ts @@ -16,7 +16,6 @@ import {ResourceLoader} from '@angular/compiler-cli/src/ngtsc/annotations'; import {NgCompiler} from '@angular/compiler-cli/src/ngtsc/core'; import {ReferenceEmitter} from '@angular/compiler-cli/src/ngtsc/imports'; import {TemplateTypeChecker} from '@angular/compiler-cli/src/ngtsc/typecheck/api'; -import assert from 'assert'; import {ProgramInfo} from '../../../utils/tsurge/program_info'; /** @@ -26,12 +25,13 @@ import {ProgramInfo} from '../../../utils/tsurge/program_info'; export interface AnalysisProgramInfo extends ProgramInfo { reflector: TypeScriptReflectionHost; typeChecker: ts.TypeChecker; - templateTypeChecker: TemplateTypeChecker; - metaRegistry: MetadataReader; dtsMetadataReader: DtsMetadataReader; evaluator: PartialEvaluator; - refEmitter: ReferenceEmitter; - resourceLoader: ResourceLoader; + + templateTypeChecker: TemplateTypeChecker | null; + metaRegistry: MetadataReader | null; + refEmitter: ReferenceEmitter | null; + resourceLoader: ResourceLoader | null; } /** @@ -42,37 +42,37 @@ export interface AnalysisProgramInfo extends ProgramInfo { */ export function prepareAnalysisInfo( userProgram: ts.Program, - compiler: NgCompiler, + compiler: NgCompiler | null, programAbsoluteRootPaths?: string[], ) { - // Analyze sync and retrieve necessary dependencies. - // Note: `getTemplateTypeChecker` requires the `enableTemplateTypeChecker` flag, but - // this has negative effects as it causes optional TCB operations to execute, which may - // error with unsuccessful reference emits that previously were ignored outside of the migration. - // The migration is resilient to TCB information missing, so this is fine, and all the information - // we need is part of required TCB operations anyway. - const {refEmitter, metaReader, templateTypeChecker} = compiler['ensureAnalyzed'](); + let refEmitter: ReferenceEmitter | null = null; + let metaReader: MetadataReader | null = null; + let templateTypeChecker: TemplateTypeChecker | null = null; + let resourceLoader: ResourceLoader | null = null; - // Generate all type check blocks. - templateTypeChecker.generateAllTypeCheckBlocks(); + if (compiler !== null) { + // Analyze sync and retrieve necessary dependencies. + // Note: `getTemplateTypeChecker` requires the `enableTemplateTypeChecker` flag, but + // this has negative effects as it causes optional TCB operations to execute, which may + // error with unsuccessful reference emits that previously were ignored outside of the migration. + // The migration is resilient to TCB information missing, so this is fine, and all the information + // we need is part of required TCB operations anyway. + const state = compiler['ensureAnalyzed'](); + + resourceLoader = compiler['resourceManager']; + refEmitter = state.refEmitter; + metaReader = state.metaReader; + templateTypeChecker = state.templateTypeChecker; + + // Generate all type check blocks. + state.templateTypeChecker.generateAllTypeCheckBlocks(); + } const typeChecker = userProgram.getTypeChecker(); const reflector = new TypeScriptReflectionHost(typeChecker); const evaluator = new PartialEvaluator(reflector, typeChecker, null); const dtsMetadataReader = new DtsMetadataReader(typeChecker, reflector); - const resourceLoader = compiler['resourceManager']; - - // Optional filter for testing. Allows for simulation of parallel execution - // even if some tsconfig's have overlap due to sharing of TS sources. - // (this is commonly not the case in g3 where deps are `.d.ts` files). - const limitToRootNamesOnly = process.env['LIMIT_TO_ROOT_NAMES_ONLY'] === '1'; - if (limitToRootNamesOnly) { - assert( - programAbsoluteRootPaths !== undefined, - 'Expected absolute root paths when limiting to root names.', - ); - } return { metaRegistry: metaReader, diff --git a/packages/core/schematics/migrations/signal-migration/src/input_detection/input_decorator.ts b/packages/core/schematics/migrations/signal-migration/src/input_detection/input_decorator.ts index c33715fa4bf..e653dd38c04 100644 --- a/packages/core/schematics/migrations/signal-migration/src/input_detection/input_decorator.ts +++ b/packages/core/schematics/migrations/signal-migration/src/input_detection/input_decorator.ts @@ -11,7 +11,11 @@ import ts from 'typescript'; import {getAngularDecorators} from '@angular/compiler-cli/src/ngtsc/annotations'; import {parseDecoratorInputTransformFunction} from '@angular/compiler-cli/src/ngtsc/annotations/directive'; import {FatalDiagnosticError} from '@angular/compiler-cli/src/ngtsc/diagnostics'; -import {Reference, ReferenceEmitter} from '@angular/compiler-cli/src/ngtsc/imports'; +import { + Reference, + ReferenceEmitKind, + ReferenceEmitter, +} from '@angular/compiler-cli/src/ngtsc/imports'; import { DecoratorInputTransform, DirectiveMeta, @@ -31,6 +35,7 @@ import { import {CompilationMode} from '@angular/compiler-cli/src/ngtsc/transform'; import {MigrationHost} from '../migration_host'; import {InputNode, isInputContainerNode} from '../input_detection/input_node'; +import {NULL_EXPR} from '../../../../../../compiler/src/output/output_ast'; /** Metadata extracted of an input declaration (in `.ts` or `.d.ts` files). */ export interface ExtractedInput extends InputMapping { @@ -46,10 +51,9 @@ export function extractDecoratorInput( reflector: ReflectionHost, metadataReader: DtsMetadataReader, evaluator: PartialEvaluator, - refEmitter: ReferenceEmitter, ): ExtractedInput | null { return ( - extractSourceCodeInput(node, host, reflector, evaluator, refEmitter) ?? + extractSourceCodeInput(node, host, reflector, evaluator) ?? extractDtsInput(node, metadataReader) ); } @@ -116,7 +120,6 @@ function extractSourceCodeInput( host: MigrationHost, reflector: ReflectionHost, evaluator: PartialEvaluator, - refEmitter: ReferenceEmitter, ): ExtractedInput | null { if ( !isInputContainerNode(node) || @@ -155,7 +158,7 @@ function extractSourceCodeInput( isRequired = !!evaluatedInputOpts.get('required'); } if (evaluatedInputOpts.has('transform') && evaluatedInputOpts.get('transform') != null) { - transformResult = parseTransformOfInput(evaluatedInputOpts, node, reflector, refEmitter); + transformResult = parseTransformOfInput(evaluatedInputOpts, node, reflector); } } } @@ -180,20 +183,32 @@ function parseTransformOfInput( evaluatedInputOpts: ResolvedValueMap, node: InputNode, reflector: ReflectionHost, - refEmitter: ReferenceEmitter, ): DecoratorInputTransform | null { const transformValue = evaluatedInputOpts.get('transform'); if (!(transformValue instanceof DynamicValue) && !(transformValue instanceof Reference)) { return null; } + // For parsing the transform, we don't need a real reference emitter, as + // the emitter is only used for verifying that the transform type could be + // copied into e.g. an `ngInputAccept` class member. + const noopRefEmitter = new ReferenceEmitter([ + { + emit: () => ({ + kind: ReferenceEmitKind.Success as const, + expression: NULL_EXPR, + importedFile: null, + }), + }, + ]); + try { return parseDecoratorInputTransformFunction( node.parent as ClassDeclaration, node.name.text, transformValue, reflector, - refEmitter, + noopRefEmitter, CompilationMode.FULL, ); } catch (e: unknown) { diff --git a/packages/core/schematics/migrations/signal-migration/src/migration.ts b/packages/core/schematics/migrations/signal-migration/src/migration.ts index c392c04cf96..476b7d0eedc 100644 --- a/packages/core/schematics/migrations/signal-migration/src/migration.ts +++ b/packages/core/schematics/migrations/signal-migration/src/migration.ts @@ -30,7 +30,7 @@ import { } from './passes/problematic_patterns/incompatibility'; import {MigrationConfig} from './migration_config'; import {ClassFieldUniqueKey} from './passes/reference_resolution/known_fields'; -import {createNgtscProgram} from '../../../utils/tsurge/helpers/ngtsc_program'; +import {createBaseProgramInfo} from '../../../utils/tsurge/helpers/create_program'; /** * Tsurge migration for migrating Angular `@Input()` declarations to @@ -50,9 +50,9 @@ export class SignalInputMigration extends TsurgeComplexMigration< super(); } - // Override the default ngtsc program creation, to add extra flags. + // Override the default program creation, to add extra flags. override createProgram(tsconfigAbsPath: string, fs?: FileSystem): BaseProgramInfo { - return createNgtscProgram(tsconfigAbsPath, fs, { + return createBaseProgramInfo(tsconfigAbsPath, fs, { _compilePoisonedComponents: true, // We want to migrate non-exported classes too. compileNonExportedClasses: true, @@ -85,7 +85,6 @@ export class SignalInputMigration extends TsurgeComplexMigration< // Extend the program info with the analysis information we need in every phase. prepareAnalysisDeps(info: ProgramInfo): AnalysisProgramInfo { - assert(info.ngCompiler !== null, 'Expected `NgCompiler` to be configured.'); const analysisInfo = { ...info, ...prepareAnalysisInfo(info.program, info.ngCompiler, info.programAbsoluteRootFileNames), diff --git a/packages/core/schematics/migrations/signal-migration/src/passes/1_identify_inputs.ts b/packages/core/schematics/migrations/signal-migration/src/passes/1_identify_inputs.ts index 5a3d1fe39d8..2cf9e8a9cab 100644 --- a/packages/core/schematics/migrations/signal-migration/src/passes/1_identify_inputs.ts +++ b/packages/core/schematics/migrations/signal-migration/src/passes/1_identify_inputs.ts @@ -32,7 +32,6 @@ export function pass1__IdentifySourceFileAndDeclarationInputs( reflector: TypeScriptReflectionHost, dtsMetadataReader: DtsMetadataReader, evaluator: PartialEvaluator, - refEmitter: ReferenceEmitter, knownDecoratorInputs: KnownInputs, result: MigrationResult, ) { @@ -43,7 +42,6 @@ export function pass1__IdentifySourceFileAndDeclarationInputs( reflector, dtsMetadataReader, evaluator, - refEmitter, ); if (decoratorInput !== null) { assert(isInputContainerNode(node), 'Expected input to be declared on accessor or property.'); diff --git a/packages/core/schematics/migrations/signal-migration/src/passes/2_find_source_file_references.ts b/packages/core/schematics/migrations/signal-migration/src/passes/2_find_source_file_references.ts index 8e5d6d49484..91114ad68dc 100644 --- a/packages/core/schematics/migrations/signal-migration/src/passes/2_find_source_file_references.ts +++ b/packages/core/schematics/migrations/signal-migration/src/passes/2_find_source_file_references.ts @@ -31,9 +31,9 @@ export function pass2_IdentifySourceFileReferences( programInfo: ProgramInfo, checker: ts.TypeChecker, reflector: ReflectionHost, - resourceLoader: ResourceLoader, + resourceLoader: ResourceLoader | null, evaluator: PartialEvaluator, - templateTypeChecker: TemplateTypeChecker, + templateTypeChecker: TemplateTypeChecker | null, groupedTsAstVisitor: GroupedTsAstVisitor, knownInputs: KnownInputs, result: MigrationResult, diff --git a/packages/core/schematics/migrations/signal-migration/src/passes/4_check_inheritance.ts b/packages/core/schematics/migrations/signal-migration/src/passes/4_check_inheritance.ts index 63859eeb586..512fc19522c 100644 --- a/packages/core/schematics/migrations/signal-migration/src/passes/4_check_inheritance.ts +++ b/packages/core/schematics/migrations/signal-migration/src/passes/4_check_inheritance.ts @@ -34,7 +34,7 @@ import {checkInheritanceOfKnownFields} from './problematic_patterns/check_inheri */ export function pass4__checkInheritanceOfInputs( inheritanceGraph: InheritanceGraph, - metaRegistry: MetadataReader, + metaRegistry: MetadataReader | null, knownInputs: KnownInputs, ) { checkInheritanceOfKnownFields(inheritanceGraph, metaRegistry, knownInputs, { diff --git a/packages/core/schematics/migrations/signal-migration/src/passes/problematic_patterns/check_inheritance.ts b/packages/core/schematics/migrations/signal-migration/src/passes/problematic_patterns/check_inheritance.ts index 567a48af177..136cbc46573 100644 --- a/packages/core/schematics/migrations/signal-migration/src/passes/problematic_patterns/check_inheritance.ts +++ b/packages/core/schematics/migrations/signal-migration/src/passes/problematic_patterns/check_inheritance.ts @@ -43,7 +43,7 @@ export interface InheritanceTracker { */ export function checkInheritanceOfKnownFields( inheritanceGraph: InheritanceGraph, - metaRegistry: MetadataReader, + metaRegistry: MetadataReader | null, fields: KnownFields & InheritanceTracker, opts: { getFieldsForClass: (node: ts.ClassDeclaration) => D[]; @@ -62,21 +62,23 @@ export function checkInheritanceOfKnownFields( assert(ts.isClassDeclaration(inputClass), 'Expected input graph node to be always a class.'); const classFields = opts.getFieldsForClass(inputClass); + const inputFieldNamesFromMetadataArray = new Set(); // Iterate through derived class chains and determine all inputs that are overridden // via class metadata fields. e.g `@Component#inputs`. This is later used to mark a // potential similar class input as incompatible— because those cannot be migrated. - const inputFieldNamesFromMetadataArray = new Set(); - for (const derivedClasses of inheritanceGraph.traceDerivedClasses(inputClass)) { - const derivedMeta = - ts.isClassDeclaration(derivedClasses) && derivedClasses.name !== undefined - ? metaRegistry.getDirectiveMetadata(new Reference(derivedClasses as ClassDeclaration)) - : null; + if (metaRegistry !== null) { + for (const derivedClasses of inheritanceGraph.traceDerivedClasses(inputClass)) { + const derivedMeta = + ts.isClassDeclaration(derivedClasses) && derivedClasses.name !== undefined + ? metaRegistry.getDirectiveMetadata(new Reference(derivedClasses as ClassDeclaration)) + : null; - if (derivedMeta !== null && derivedMeta.inputFieldNamesFromMetadataArray !== null) { - derivedMeta.inputFieldNamesFromMetadataArray.forEach((b) => - inputFieldNamesFromMetadataArray.add(b), - ); + if (derivedMeta !== null && derivedMeta.inputFieldNamesFromMetadataArray !== null) { + derivedMeta.inputFieldNamesFromMetadataArray.forEach((b) => + inputFieldNamesFromMetadataArray.add(b), + ); + } } } diff --git a/packages/core/schematics/migrations/signal-migration/src/passes/reference_resolution/index.ts b/packages/core/schematics/migrations/signal-migration/src/passes/reference_resolution/index.ts index 114a1b2bcc6..06b8d667974 100644 --- a/packages/core/schematics/migrations/signal-migration/src/passes/reference_resolution/index.ts +++ b/packages/core/schematics/migrations/signal-migration/src/passes/reference_resolution/index.ts @@ -40,9 +40,9 @@ export function createFindAllSourceFileReferencesVisitor, fieldNamesToConsiderForReferenceLookup: Set | null, result: ReferenceResult, @@ -67,7 +67,9 @@ export function createFindAllSourceFileReferencesVisitor { let lastTime = currentTimeInMs(); - if (ts.isClassDeclaration(node)) { + // Note: If there is no template type checker and resource loader, we aren't processing + // an Angular program, and can skip template detection. + if (ts.isClassDeclaration(node) && templateTypeChecker !== null && resourceLoader !== null) { identifyTemplateReferences( programInfo, node, diff --git a/packages/core/schematics/migrations/signal-migration/src/phase_analysis.ts b/packages/core/schematics/migrations/signal-migration/src/phase_analysis.ts index 467a9c8409b..af805908643 100644 --- a/packages/core/schematics/migrations/signal-migration/src/phase_analysis.ts +++ b/packages/core/schematics/migrations/signal-migration/src/phase_analysis.ts @@ -45,7 +45,6 @@ export function executeAnalysisPhase( templateTypeChecker, resourceLoader, evaluator, - refEmitter, }: AnalysisProgramInfo, ) { // Pass 1 @@ -61,7 +60,6 @@ export function executeAnalysisPhase( reflector, dtsMetadataReader, evaluator, - refEmitter, knownInputs, result, ), diff --git a/packages/core/schematics/migrations/signal-queries-migration/migration.ts b/packages/core/schematics/migrations/signal-queries-migration/migration.ts index feaa8e1c636..765b5dc916d 100644 --- a/packages/core/schematics/migrations/signal-queries-migration/migration.ts +++ b/packages/core/schematics/migrations/signal-queries-migration/migration.ts @@ -100,12 +100,16 @@ export class SignalQueriesMigration extends TsurgeComplexMigration< } override async analyze(info: ProgramInfo): Promise> { - assert(info.ngCompiler !== null, 'Expected queries migration to have an Angular program.'); - // Pre-Analyze the program and get access to the template type checker. - const {templateTypeChecker} = info.ngCompiler['ensureAnalyzed'](); - // Generate all type check blocks. - templateTypeChecker.generateAllTypeCheckBlocks(); + const {templateTypeChecker} = info.ngCompiler?.['ensureAnalyzed']() ?? { + templateTypeChecker: null, + }; + const resourceLoader = info.ngCompiler?.['resourceManager'] ?? null; + + // Generate all type check blocks, if we have Angular template information. + if (templateTypeChecker !== null) { + templateTypeChecker.generateAllTypeCheckBlocks(); + } const {sourceFiles, program} = info; const checker = program.getTypeChecker(); @@ -226,7 +230,7 @@ export class SignalQueriesMigration extends TsurgeComplexMigration< info, checker, reflector, - info.ngCompiler['resourceManager'], + resourceLoader, evaluator, templateTypeChecker, allFieldsOrKnownQueries, @@ -391,10 +395,13 @@ export class SignalQueriesMigration extends TsurgeComplexMigration< } override async migrate(globalMetadata: GlobalUnitData, info: ProgramInfo) { - assert(info.ngCompiler !== null, 'Expected queries migration to have an Angular program.'); - // Pre-Analyze the program and get access to the template type checker. - const {templateTypeChecker, metaReader} = info.ngCompiler['ensureAnalyzed'](); + const {templateTypeChecker, metaReader} = info.ngCompiler?.['ensureAnalyzed']() ?? { + templateTypeChecker: null, + metaReader: null, + }; + const resourceLoader = info.ngCompiler?.['resourceManager'] ?? null; + const {program, sourceFiles} = info; const checker = program.getTypeChecker(); const reflector = new TypeScriptReflectionHost(checker); @@ -472,7 +479,7 @@ export class SignalQueriesMigration extends TsurgeComplexMigration< info, checker, reflector, - info.ngCompiler['resourceManager'], + resourceLoader, evaluator, templateTypeChecker, knownQueries, diff --git a/packages/core/schematics/utils/tsurge/base_migration.ts b/packages/core/schematics/utils/tsurge/base_migration.ts index 60d30eb4e68..bc20ed12f7e 100644 --- a/packages/core/schematics/utils/tsurge/base_migration.ts +++ b/packages/core/schematics/utils/tsurge/base_migration.ts @@ -8,11 +8,10 @@ import {absoluteFrom, FileSystem} from '@angular/compiler-cli/src/ngtsc/file_system'; import {isShim} from '@angular/compiler-cli/src/ngtsc/shims'; -import {createNgtscProgram} from './helpers/ngtsc_program'; import {BaseProgramInfo, ProgramInfo} from './program_info'; import {getRootDirs} from '@angular/compiler-cli/src/ngtsc/util/src/typescript'; import {Serializable} from './helpers/serializable'; -import {createProgramInfo} from './helpers/create_program_info'; +import {createBaseProgramInfo} from './helpers/create_program'; /** * Type describing statistics that could be tracked @@ -42,7 +41,7 @@ export abstract class TsurgeBaseMigration