diff --git a/packages/compiler-cli/BUILD.bazel b/packages/compiler-cli/BUILD.bazel index 501d2cae2d..29e00a0f90 100644 --- a/packages/compiler-cli/BUILD.bazel +++ b/packages/compiler-cli/BUILD.bazel @@ -33,6 +33,7 @@ ts_library( "//packages/compiler-cli/src/ngtsc/incremental", "//packages/compiler-cli/src/ngtsc/indexer", "//packages/compiler-cli/src/ngtsc/perf", + "//packages/compiler-cli/src/ngtsc/program_driver", "//packages/compiler-cli/src/ngtsc/reflection", "//packages/compiler-cli/src/ngtsc/shims", "//packages/compiler-cli/src/ngtsc/translator", diff --git a/packages/compiler-cli/src/ngtsc/core/BUILD.bazel b/packages/compiler-cli/src/ngtsc/core/BUILD.bazel index a111461889..901a47fa50 100644 --- a/packages/compiler-cli/src/ngtsc/core/BUILD.bazel +++ b/packages/compiler-cli/src/ngtsc/core/BUILD.bazel @@ -26,6 +26,7 @@ ts_library( "//packages/compiler-cli/src/ngtsc/modulewithproviders", "//packages/compiler-cli/src/ngtsc/partial_evaluator", "//packages/compiler-cli/src/ngtsc/perf", + "//packages/compiler-cli/src/ngtsc/program_driver", "//packages/compiler-cli/src/ngtsc/reflection", "//packages/compiler-cli/src/ngtsc/resource", "//packages/compiler-cli/src/ngtsc/routing", diff --git a/packages/compiler-cli/src/ngtsc/core/src/compiler.ts b/packages/compiler-cli/src/ngtsc/core/src/compiler.ts index f99ceb9ebb..8a0ae5b4b3 100644 --- a/packages/compiler-cli/src/ngtsc/core/src/compiler.ts +++ b/packages/compiler-cli/src/ngtsc/core/src/compiler.ts @@ -13,7 +13,7 @@ import {ComponentDecoratorHandler, DirectiveDecoratorHandler, InjectableDecorato import {CycleAnalyzer, CycleHandlingStrategy, ImportGraph} from '../../cycles'; import {COMPILER_ERRORS_WITH_GUIDES, ERROR_DETAILS_PAGE_BASE_URL, ErrorCode, ngErrorCode} from '../../diagnostics'; import {checkForPrivateExports, ReferenceGraph} from '../../entry_point'; -import {LogicalFileSystem, resolve} from '../../file_system'; +import {AbsoluteFsPath, LogicalFileSystem, resolve} from '../../file_system'; import {AbsoluteModuleStrategy, AliasingHost, AliasStrategy, DefaultImportTracker, ImportRewriter, LocalIdentifierStrategy, LogicalProjectStrategy, ModuleResolver, NoopImportRewriter, PrivateExportAliasingHost, R3SymbolsImportRewriter, Reference, ReferenceEmitStrategy, ReferenceEmitter, RelativePathStrategy, UnifiedModulesAliasingHost, UnifiedModulesStrategy} from '../../imports'; import {IncrementalBuildStrategy, IncrementalDriver} from '../../incremental'; import {SemanticSymbol} from '../../incremental/semantic_graph'; @@ -21,9 +21,8 @@ import {generateAnalysis, IndexedComponent, IndexingContext} from '../../indexer import {ComponentResources, CompoundMetadataReader, CompoundMetadataRegistry, DtsMetadataReader, InjectableClassRegistry, LocalMetadataRegistry, MetadataReader, ResourceRegistry} from '../../metadata'; import {ModuleWithProvidersScanner} from '../../modulewithproviders'; import {PartialEvaluator} from '../../partial_evaluator'; -import {ActivePerfRecorder} from '../../perf'; -import {PerfCheckpoint, PerfEvent, PerfPhase} from '../../perf/src/api'; -import {DelegatingPerfRecorder} from '../../perf/src/recorder'; +import {ActivePerfRecorder, DelegatingPerfRecorder, PerfCheckpoint, PerfEvent, PerfPhase} from '../../perf'; +import {ProgramDriver, UpdateMode} from '../../program_driver'; import {DeclarationNode, isNamedClassDeclaration, TypeScriptReflectionHost} from '../../reflection'; import {AdapterResourceLoader} from '../../resource'; import {entryPointKeyFor, NgModuleRouteAnalyzer} from '../../routing'; @@ -32,7 +31,7 @@ import {generatedFactoryTransform} from '../../shims'; import {ivySwitchTransform} from '../../switch'; import {aliasTransformFactory, CompilationMode, declarationTransformFactory, DecoratorHandler, DtsTransformRegistry, ivyTransformFactory, TraitCompiler} from '../../transform'; import {TemplateTypeCheckerImpl} from '../../typecheck'; -import {OptimizeFor, TemplateTypeChecker, TypeCheckingConfig, TypeCheckingProgramStrategy} from '../../typecheck/api'; +import {OptimizeFor, TemplateTypeChecker, TypeCheckingConfig} from '../../typecheck/api'; import {getSourceFileOrNull, isDtsPath, resolveModuleName} from '../../util/src/typescript'; import {LazyRoute, NgCompilerAdapter, NgCompilerOptions} from '../api'; @@ -78,7 +77,7 @@ export interface FreshCompilationTicket { kind: CompilationTicketKind.Fresh; options: NgCompilerOptions; incrementalBuildStrategy: IncrementalBuildStrategy; - typeCheckingProgramStrategy: TypeCheckingProgramStrategy; + programDriver: ProgramDriver; enableTemplateTypeChecker: boolean; usePoisonedData: boolean; tsProgram: ts.Program; @@ -94,7 +93,7 @@ export interface IncrementalTypeScriptCompilationTicket { oldProgram: ts.Program; newProgram: ts.Program; incrementalBuildStrategy: IncrementalBuildStrategy; - typeCheckingProgramStrategy: TypeCheckingProgramStrategy; + programDriver: ProgramDriver; newDriver: IncrementalDriver; enableTemplateTypeChecker: boolean; usePoisonedData: boolean; @@ -123,15 +122,15 @@ export type CompilationTicket = FreshCompilationTicket|IncrementalTypeScriptComp */ export function freshCompilationTicket( tsProgram: ts.Program, options: NgCompilerOptions, - incrementalBuildStrategy: IncrementalBuildStrategy, - typeCheckingProgramStrategy: TypeCheckingProgramStrategy, perfRecorder: ActivePerfRecorder|null, - enableTemplateTypeChecker: boolean, usePoisonedData: boolean): CompilationTicket { + incrementalBuildStrategy: IncrementalBuildStrategy, programDriver: ProgramDriver, + perfRecorder: ActivePerfRecorder|null, enableTemplateTypeChecker: boolean, + usePoisonedData: boolean): CompilationTicket { return { kind: CompilationTicketKind.Fresh, tsProgram, options, incrementalBuildStrategy, - typeCheckingProgramStrategy, + programDriver, enableTemplateTypeChecker, usePoisonedData, perfRecorder: perfRecorder ?? ActivePerfRecorder.zeroedToNow(), @@ -144,17 +143,16 @@ export function freshCompilationTicket( */ export function incrementalFromCompilerTicket( oldCompiler: NgCompiler, newProgram: ts.Program, - incrementalBuildStrategy: IncrementalBuildStrategy, - typeCheckingProgramStrategy: TypeCheckingProgramStrategy, modifiedResourceFiles: Set, - perfRecorder: ActivePerfRecorder|null): CompilationTicket { - const oldProgram = oldCompiler.getNextProgram(); + incrementalBuildStrategy: IncrementalBuildStrategy, programDriver: ProgramDriver, + modifiedResourceFiles: Set, perfRecorder: ActivePerfRecorder|null): CompilationTicket { + const oldProgram = oldCompiler.getCurrentProgram(); const oldDriver = oldCompiler.incrementalStrategy.getIncrementalDriver(oldProgram); if (oldDriver === null) { // No incremental step is possible here, since no IncrementalDriver was found for the old // program. return freshCompilationTicket( - newProgram, oldCompiler.options, incrementalBuildStrategy, typeCheckingProgramStrategy, - perfRecorder, oldCompiler.enableTemplateTypeChecker, oldCompiler.usePoisonedData); + newProgram, oldCompiler.options, incrementalBuildStrategy, programDriver, perfRecorder, + oldCompiler.enableTemplateTypeChecker, oldCompiler.usePoisonedData); } if (perfRecorder === null) { @@ -170,7 +168,7 @@ export function incrementalFromCompilerTicket( usePoisonedData: oldCompiler.usePoisonedData, options: oldCompiler.options, incrementalBuildStrategy, - typeCheckingProgramStrategy, + programDriver, newDriver, oldProgram, newProgram, @@ -185,13 +183,12 @@ export function incrementalFromCompilerTicket( export function incrementalFromDriverTicket( oldProgram: ts.Program, oldDriver: IncrementalDriver, newProgram: ts.Program, options: NgCompilerOptions, incrementalBuildStrategy: IncrementalBuildStrategy, - typeCheckingProgramStrategy: TypeCheckingProgramStrategy, modifiedResourceFiles: Set, + programDriver: ProgramDriver, modifiedResourceFiles: Set, perfRecorder: ActivePerfRecorder|null, enableTemplateTypeChecker: boolean, usePoisonedData: boolean): CompilationTicket { if (perfRecorder === null) { perfRecorder = ActivePerfRecorder.zeroedToNow(); } - const newDriver = IncrementalDriver.reconcile( oldProgram, oldDriver, newProgram, modifiedResourceFiles, perfRecorder); return { @@ -201,7 +198,7 @@ export function incrementalFromDriverTicket( options, incrementalBuildStrategy, newDriver, - typeCheckingProgramStrategy, + programDriver, enableTemplateTypeChecker, usePoisonedData, perfRecorder, @@ -255,7 +252,7 @@ export class NgCompiler { private nonTemplateDiagnostics: ts.Diagnostic[]|null = null; private closureCompilerEnabled: boolean; - private nextProgram: ts.Program; + private currentProgram: ts.Program; private entryPoint: ts.SourceFile|null; private moduleResolver: ModuleResolver; private resourceManager: AdapterResourceLoader; @@ -286,7 +283,7 @@ export class NgCompiler { adapter, ticket.options, ticket.tsProgram, - ticket.typeCheckingProgramStrategy, + ticket.programDriver, ticket.incrementalBuildStrategy, IncrementalDriver.fresh(ticket.tsProgram), ticket.enableTemplateTypeChecker, @@ -298,7 +295,7 @@ export class NgCompiler { adapter, ticket.options, ticket.newProgram, - ticket.typeCheckingProgramStrategy, + ticket.programDriver, ticket.incrementalBuildStrategy, ticket.newDriver, ticket.enableTemplateTypeChecker, @@ -315,8 +312,8 @@ export class NgCompiler { private constructor( private adapter: NgCompilerAdapter, readonly options: NgCompilerOptions, - private tsProgram: ts.Program, - readonly typeCheckingProgramStrategy: TypeCheckingProgramStrategy, + private inputProgram: ts.Program, + readonly programDriver: ProgramDriver, readonly incrementalStrategy: IncrementalBuildStrategy, readonly incrementalDriver: IncrementalDriver, readonly enableTemplateTypeChecker: boolean, @@ -329,11 +326,11 @@ export class NgCompiler { this.constructionDiagnostics.push(incompatibleTypeCheckOptionsDiagnostic); } - this.nextProgram = tsProgram; + this.currentProgram = inputProgram; this.closureCompilerEnabled = !!this.options.annotateForClosureCompiler; this.entryPoint = - adapter.entryPoint !== null ? getSourceFileOrNull(tsProgram, adapter.entryPoint) : null; + adapter.entryPoint !== null ? getSourceFileOrNull(inputProgram, adapter.entryPoint) : null; const moduleResolutionCache = ts.createModuleResolutionCache( this.adapter.getCurrentDirectory(), @@ -343,19 +340,19 @@ export class NgCompiler { // way into all kinds of places inside TS internal objects. this.adapter.getCanonicalFileName.bind(this.adapter)); this.moduleResolver = - new ModuleResolver(tsProgram, this.options, this.adapter, moduleResolutionCache); + new ModuleResolver(inputProgram, this.options, this.adapter, moduleResolutionCache); this.resourceManager = new AdapterResourceLoader(adapter, this.options); - this.cycleAnalyzer = - new CycleAnalyzer(new ImportGraph(tsProgram.getTypeChecker(), this.delegatingPerfRecorder)); - this.incrementalStrategy.setIncrementalDriver(this.incrementalDriver, tsProgram); + this.cycleAnalyzer = new CycleAnalyzer( + new ImportGraph(inputProgram.getTypeChecker(), this.delegatingPerfRecorder)); + this.incrementalStrategy.setIncrementalDriver(this.incrementalDriver, inputProgram); this.ignoreForDiagnostics = - new Set(tsProgram.getSourceFiles().filter(sf => this.adapter.isShim(sf))); + new Set(inputProgram.getSourceFiles().filter(sf => this.adapter.isShim(sf))); this.ignoreForEmit = this.adapter.ignoreForEmit; let dtsFileCount = 0; let nonDtsFileCount = 0; - for (const sf of tsProgram.getSourceFiles()) { + for (const sf of inputProgram.getSourceFiles()) { if (sf.isDeclarationFile) { dtsFileCount++; } else { @@ -462,16 +459,22 @@ export class NgCompiler { } /** - * Get the `ts.Program` to use as a starting point when spawning a subsequent incremental - * compilation. + * Get the current `ts.Program` known to this `NgCompiler`. * - * The `NgCompiler` spawns an internal incremental TypeScript compilation (inheriting the - * consumer's `ts.Program` into a new one for the purposes of template type-checking). After this - * operation, the consumer's `ts.Program` is no longer usable for starting a new incremental - * compilation. `getNextProgram` retrieves the `ts.Program` which can be used instead. + * Compilation begins with an input `ts.Program`, and during template type-checking operations new + * `ts.Program`s may be produced using the `ProgramDriver`. The most recent such `ts.Program` to + * be produced is available here. + * + * This `ts.Program` serves two key purposes: + * + * * As an incremental starting point for creating the next `ts.Program` based on files that the + * user has changed (for clients using the TS compiler program APIs). + * + * * As the "before" point for an incremental compilation invocation, to determine what's changed + * between the old and new programs (for all compilations). */ - getNextProgram(): ts.Program { - return this.nextProgram; + getCurrentProgram(): ts.Program { + return this.currentProgram; } getTemplateTypeChecker(): TemplateTypeChecker { @@ -533,7 +536,7 @@ export class NgCompiler { this.compilation = this.makeCompilation(); const promises: Promise[] = []; - for (const sf of this.tsProgram.getSourceFiles()) { + for (const sf of this.inputProgram.getSourceFiles()) { if (sf.isDeclarationFile) { continue; } @@ -579,7 +582,7 @@ export class NgCompiler { // // In all cases above, the `containingFile` argument is ignored, so we can just take the first // of the root files. - const containingFile = this.tsProgram.getRootFileNames()[0]; + const containingFile = this.inputProgram.getRootFileNames()[0]; const [entryPath, moduleName] = entryRoute.split('#'); const resolvedModule = resolveModuleName(entryPath, containingFile, this.options, this.adapter, null); @@ -602,7 +605,7 @@ export class NgCompiler { } { const compilation = this.ensureAnalyzed(); - const coreImportsFrom = compilation.isCore ? getR3SymbolsFile(this.tsProgram) : null; + const coreImportsFrom = compilation.isCore ? getR3SymbolsFile(this.inputProgram) : null; let importRewriter: ImportRewriter; if (coreImportsFrom !== null) { importRewriter = new R3SymbolsImportRewriter(coreImportsFrom.fileName); @@ -661,7 +664,7 @@ export class NgCompiler { private analyzeSync(): void { this.perfRecorder.inPhase(PerfPhase.Analysis, () => { this.compilation = this.makeCompilation(); - for (const sf of this.tsProgram.getSourceFiles()) { + for (const sf of this.inputProgram.getSourceFiles()) { if (sf.isDeclarationFile) { continue; } @@ -703,7 +706,7 @@ export class NgCompiler { // is not disabled when `strictTemplates` is enabled. const strictTemplates = !!this.options.strictTemplates; - const useInlineTypeConstructors = this.typeCheckingProgramStrategy.supportsInlineOperations; + const useInlineTypeConstructors = this.programDriver.supportsInlineOperations; // First select a type-checking configuration, based on whether full template type-checking is // requested. @@ -816,7 +819,7 @@ export class NgCompiler { // Get the diagnostics. const diagnostics: ts.Diagnostic[] = []; - for (const sf of this.tsProgram.getSourceFiles()) { + for (const sf of this.inputProgram.getSourceFiles()) { if (sf.isDeclarationFile || this.adapter.isShim(sf)) { continue; } @@ -825,9 +828,9 @@ export class NgCompiler { ...compilation.templateTypeChecker.getDiagnosticsForFile(sf, OptimizeFor.WholeProgram)); } - const program = this.typeCheckingProgramStrategy.getProgram(); + const program = this.programDriver.getProgram(); this.incrementalStrategy.setIncrementalDriver(this.incrementalDriver, program); - this.nextProgram = program; + this.currentProgram = program; return diagnostics; } @@ -842,9 +845,9 @@ export class NgCompiler { diagnostics.push(...compilation.templateTypeChecker.getDiagnosticsForFile(sf, optimizeFor)); } - const program = this.typeCheckingProgramStrategy.getProgram(); + const program = this.programDriver.getProgram(); this.incrementalStrategy.setIncrementalDriver(this.incrementalDriver, program); - this.nextProgram = program; + this.currentProgram = program; return diagnostics; } @@ -855,7 +858,7 @@ export class NgCompiler { this.nonTemplateDiagnostics = [...compilation.traitCompiler.diagnostics]; if (this.entryPoint !== null && compilation.exportReferenceGraph !== null) { this.nonTemplateDiagnostics.push(...checkForPrivateExports( - this.entryPoint, this.tsProgram.getTypeChecker(), compilation.exportReferenceGraph)); + this.entryPoint, this.inputProgram.getTypeChecker(), compilation.exportReferenceGraph)); } } return this.nonTemplateDiagnostics; @@ -872,7 +875,7 @@ export class NgCompiler { } private makeCompilation(): LazyCompilationState { - const checker = this.tsProgram.getTypeChecker(); + const checker = this.inputProgram.getTypeChecker(); const reflector = new TypeScriptReflectionHost(checker); @@ -904,7 +907,7 @@ export class NgCompiler { // First, try to use local identifiers if available. new LocalIdentifierStrategy(), // Next, attempt to use an absolute import. - new AbsoluteModuleStrategy(this.tsProgram, checker, this.moduleResolver, reflector), + new AbsoluteModuleStrategy(this.inputProgram, checker, this.moduleResolver, reflector), // Finally, check if the reference is being written into a file within the project's .ts // sources, and use a relative import if so. If this fails, ReferenceEmitter will throw // an error. @@ -966,7 +969,7 @@ export class NgCompiler { const mwpScanner = new ModuleWithProvidersScanner(reflector, evaluator, refEmitter); - const isCore = isAngularCorePackage(this.tsProgram); + const isCore = isAngularCorePackage(this.inputProgram); const defaultImportTracker = new DefaultImportTracker(); const resourceRegistry = new ResourceRegistry(); @@ -1024,10 +1027,18 @@ export class NgCompiler { this.options.compileNonExportedClasses !== false, compilationMode, dtsTransforms, semanticDepGraphUpdater); + // Template type-checking may use the `ProgramDriver` to produce new `ts.Program`(s). If this + // happens, they need to be tracked by the `NgCompiler`. + const notifyingDriver = + new NotifyingProgramDriverWrapper(this.programDriver, (program: ts.Program) => { + this.incrementalStrategy.setIncrementalDriver(this.incrementalDriver, program); + this.currentProgram = program; + }); + const templateTypeChecker = new TemplateTypeCheckerImpl( - this.tsProgram, this.typeCheckingProgramStrategy, traitCompiler, - this.getTypeCheckingConfig(), refEmitter, reflector, this.adapter, this.incrementalDriver, - scopeRegistry, typeCheckScopeRegistry, this.delegatingPerfRecorder); + this.inputProgram, notifyingDriver, traitCompiler, this.getTypeCheckingConfig(), refEmitter, + reflector, this.adapter, this.incrementalDriver, scopeRegistry, typeCheckScopeRegistry, + this.delegatingPerfRecorder); return { isCore, @@ -1141,3 +1152,21 @@ class ReferenceGraphAdapter implements ReferencesRegistry { } } } + +class NotifyingProgramDriverWrapper implements ProgramDriver { + constructor( + private delegate: ProgramDriver, private notifyNewProgram: (program: ts.Program) => void) {} + + get supportsInlineOperations() { + return this.delegate.supportsInlineOperations; + } + + getProgram(): ts.Program { + return this.delegate.getProgram(); + } + + updateFiles(contents: Map, updateMode: UpdateMode): void { + this.delegate.updateFiles(contents, updateMode); + this.notifyNewProgram(this.delegate.getProgram()); + } +} diff --git a/packages/compiler-cli/src/ngtsc/core/test/BUILD.bazel b/packages/compiler-cli/src/ngtsc/core/test/BUILD.bazel index 92a81665dc..9ff0a5b750 100644 --- a/packages/compiler-cli/src/ngtsc/core/test/BUILD.bazel +++ b/packages/compiler-cli/src/ngtsc/core/test/BUILD.bazel @@ -15,6 +15,7 @@ ts_library( "//packages/compiler-cli/src/ngtsc/file_system", "//packages/compiler-cli/src/ngtsc/file_system/testing", "//packages/compiler-cli/src/ngtsc/incremental", + "//packages/compiler-cli/src/ngtsc/program_driver", "//packages/compiler-cli/src/ngtsc/reflection", "//packages/compiler-cli/src/ngtsc/typecheck", "//packages/compiler-cli/src/ngtsc/typecheck/api", diff --git a/packages/compiler-cli/src/ngtsc/core/test/compiler_test.ts b/packages/compiler-cli/src/ngtsc/core/test/compiler_test.ts index e7f59a2103..141f90bc99 100644 --- a/packages/compiler-cli/src/ngtsc/core/test/compiler_test.ts +++ b/packages/compiler-cli/src/ngtsc/core/test/compiler_test.ts @@ -11,9 +11,9 @@ import * as ts from 'typescript'; import {absoluteFrom as _, FileSystem, getFileSystem, getSourceFileOrError, NgtscCompilerHost, setFileSystem} from '../../file_system'; import {runInEachFileSystem} from '../../file_system/testing'; import {IncrementalBuildStrategy, NoopIncrementalBuildStrategy} from '../../incremental'; +import {ProgramDriver, TsCreateProgramDriver} from '../../program_driver'; import {ClassDeclaration, isNamedClassDeclaration} from '../../reflection'; -import {ReusedProgramStrategy} from '../../typecheck'; -import {OptimizeFor, TypeCheckingProgramStrategy} from '../../typecheck/api'; +import {OptimizeFor} from '../../typecheck/api'; import {NgCompilerOptions} from '../api'; @@ -22,7 +22,7 @@ import {NgCompilerHost} from '../src/host'; function makeFreshCompiler( host: NgCompilerHost, options: NgCompilerOptions, program: ts.Program, - programStrategy: TypeCheckingProgramStrategy, incrementalStrategy: IncrementalBuildStrategy, + programStrategy: ProgramDriver, incrementalStrategy: IncrementalBuildStrategy, enableTemplateTypeChecker: boolean, usePoisonedData: boolean): NgCompiler { const ticket = freshCompilationTicket( program, options, incrementalStrategy, programStrategy, /* perfRecorder */ null, @@ -61,7 +61,7 @@ runInEachFileSystem(() => { const host = NgCompilerHost.wrap(baseHost, [COMPONENT], options, /* oldProgram */ null); const program = ts.createProgram({host, options, rootNames: host.inputFiles}); const compiler = makeFreshCompiler( - host, options, program, new ReusedProgramStrategy(program, host, options, []), + host, options, program, new TsCreateProgramDriver(program, host, options, []), new NoopIncrementalBuildStrategy(), /** enableTemplateTypeChecker */ false, /* usePoisonedData */ false); @@ -113,7 +113,7 @@ runInEachFileSystem(() => { const CmpC = getClass(getSourceFileOrError(program, cmpCFile), 'CmpC'); const compiler = makeFreshCompiler( - host, options, program, new ReusedProgramStrategy(program, host, options, []), + host, options, program, new TsCreateProgramDriver(program, host, options, []), new NoopIncrementalBuildStrategy(), /** enableTemplateTypeChecker */ false, /* usePoisonedData */ false); const components = compiler.getComponentsWithTemplateFile(templateFile); @@ -165,7 +165,7 @@ runInEachFileSystem(() => { const CmpA = getClass(getSourceFileOrError(program, cmpAFile), 'CmpA'); const CmpC = getClass(getSourceFileOrError(program, cmpCFile), 'CmpC'); const compiler = makeFreshCompiler( - host, options, program, new ReusedProgramStrategy(program, host, options, []), + host, options, program, new TsCreateProgramDriver(program, host, options, []), new NoopIncrementalBuildStrategy(), /** enableTemplateTypeChecker */ false, /* usePoisonedData */ false); const components = compiler.getComponentsWithStyleFile(styleFile); @@ -199,7 +199,7 @@ runInEachFileSystem(() => { const program = ts.createProgram({host, options, rootNames: host.inputFiles}); const CmpA = getClass(getSourceFileOrError(program, cmpAFile), 'CmpA'); const compiler = makeFreshCompiler( - host, options, program, new ReusedProgramStrategy(program, host, options, []), + host, options, program, new TsCreateProgramDriver(program, host, options, []), new NoopIncrementalBuildStrategy(), /** enableTemplateTypeChecker */ false, /* usePoisonedData */ false); const resources = compiler.getComponentResources(CmpA); @@ -235,7 +235,7 @@ runInEachFileSystem(() => { const program = ts.createProgram({host, options, rootNames: host.inputFiles}); const CmpA = getClass(getSourceFileOrError(program, cmpAFile), 'CmpA'); const compiler = makeFreshCompiler( - host, options, program, new ReusedProgramStrategy(program, host, options, []), + host, options, program, new TsCreateProgramDriver(program, host, options, []), new NoopIncrementalBuildStrategy(), /** enableTemplateTypeChecker */ false, /* usePoisonedData */ false); const resources = compiler.getComponentResources(CmpA); @@ -267,7 +267,7 @@ runInEachFileSystem(() => { const host = NgCompilerHost.wrap(baseHost, [COMPONENT], options, /* oldProgram */ null); const program = ts.createProgram({host, options, rootNames: host.inputFiles}); const compiler = makeFreshCompiler( - host, options, program, new ReusedProgramStrategy(program, host, options, []), + host, options, program, new TsCreateProgramDriver(program, host, options, []), new NoopIncrementalBuildStrategy(), /** enableTemplateTypeChecker */ false, /* usePoisonedData */ false); @@ -301,7 +301,7 @@ runInEachFileSystem(() => { const host = NgCompilerHost.wrap(baseHost, [COMPONENT], options, /* oldProgram */ null); const program = ts.createProgram({host, options, rootNames: host.inputFiles}); const compilerA = makeFreshCompiler( - host, options, program, new ReusedProgramStrategy(program, host, options, []), + host, options, program, new TsCreateProgramDriver(program, host, options, []), new NoopIncrementalBuildStrategy(), /** enableTemplateTypeChecker */ false, /* usePoisonedData */ false); diff --git a/packages/compiler-cli/src/ngtsc/program.ts b/packages/compiler-cli/src/ngtsc/program.ts index 1eb8ae18e7..a0074dd7e7 100644 --- a/packages/compiler-cli/src/ngtsc/program.ts +++ b/packages/compiler-cli/src/ngtsc/program.ts @@ -18,9 +18,9 @@ import {absoluteFrom, AbsoluteFsPath, getFileSystem} from './file_system'; import {TrackedIncrementalBuildStrategy} from './incremental'; import {IndexedComponent} from './indexer'; import {ActivePerfRecorder, PerfCheckpoint as PerfCheckpoint, PerfEvent, PerfPhase} from './perf'; +import {TsCreateProgramDriver} from './program_driver'; import {DeclarationNode} from './reflection'; import {retagAllTsFiles, untagAllTsFiles} from './shims'; -import {ReusedProgramStrategy} from './typecheck'; import {OptimizeFor} from './typecheck/api'; @@ -95,7 +95,7 @@ export class NgtscProgram implements api.Program { // the program. untagAllTsFiles(this.tsProgram); - const reusedProgramStrategy = new ReusedProgramStrategy( + const programDriver = new TsCreateProgramDriver( this.tsProgram, this.host, this.options, this.host.shimExtensionPrefixes); this.incrementalStrategy = oldProgram !== undefined ? @@ -114,14 +114,14 @@ export class NgtscProgram implements api.Program { let ticket: CompilationTicket; if (oldProgram === undefined) { ticket = freshCompilationTicket( - this.tsProgram, options, this.incrementalStrategy, reusedProgramStrategy, perfRecorder, + this.tsProgram, options, this.incrementalStrategy, programDriver, perfRecorder, /* enableTemplateTypeChecker */ false, /* usePoisonedData */ false); } else { ticket = incrementalFromCompilerTicket( oldProgram.compiler, this.tsProgram, this.incrementalStrategy, - reusedProgramStrategy, + programDriver, modifiedResourceFiles, perfRecorder, ); @@ -223,7 +223,7 @@ export class NgtscProgram implements api.Program { const diagnostics = sf === undefined ? this.compiler.getDiagnostics() : this.compiler.getDiagnosticsForFile(sf, OptimizeFor.WholeProgram); - this.reuseTsProgram = this.compiler.getNextProgram(); + this.reuseTsProgram = this.compiler.getCurrentProgram(); return diagnostics; } diff --git a/packages/compiler-cli/src/ngtsc/program_driver/BUILD.bazel b/packages/compiler-cli/src/ngtsc/program_driver/BUILD.bazel new file mode 100644 index 0000000000..cc52a91289 --- /dev/null +++ b/packages/compiler-cli/src/ngtsc/program_driver/BUILD.bazel @@ -0,0 +1,17 @@ +load("//tools:defaults.bzl", "ts_library") + +package(default_visibility = ["//visibility:public"]) + +ts_library( + name = "program_driver", + srcs = ["index.ts"] + glob([ + "src/*.ts", + ]), + module_name = "@angular/compiler-cli/src/ngtsc/program_driver", + deps = [ + "//packages/compiler-cli/src/ngtsc/file_system", + "//packages/compiler-cli/src/ngtsc/shims", + "//packages/compiler-cli/src/ngtsc/util", + "@npm//typescript", + ], +) diff --git a/packages/compiler-cli/src/ngtsc/program_driver/README.md b/packages/compiler-cli/src/ngtsc/program_driver/README.md new file mode 100644 index 0000000000..9d230f5bbb --- /dev/null +++ b/packages/compiler-cli/src/ngtsc/program_driver/README.md @@ -0,0 +1,18 @@ +# The Program Driver interface + +`ProgramDriver` is a small but important interface which allows the template type-checking machinery to request changes to the current `ts.Program`, and to receive a new `ts.Program` with those changes applied. This is used to add template type-checking code to the current `ts.Program`, eventually allowing for diagnostics to be produced within that code. This operation is abstracted behind this interface because different clients create `ts.Program`s differently. The Language Service, for example, creates `ts.Program`s from the current editor state on request, while the TS compiler API creates them explicitly. + +When running using the TS APIs, it's important that each new `ts.Program` be created incrementally from the previous `ts.Program`. Under a normal compilation, this means that programs alternate between template type checking programs and user programs: + +* `ts.Program#1` is created from user input (the user's source files). +* `ts.Program#2` is created incrementally on top of #1 for template type-checking, and adds private TCB code. +* `ts.Program#3` is created incrementally on top of #2 when the user makes changes to files on disk (incremental build). +* `ts.Program#4` is created incrementally on top of #3 to adjust template type-checking code according to the user's changes. + +The `TsCreateProgramDriver` performs this operation for template type-checking `ts.Program`s built by the command-line compiler or by the CLI. The latest template type-checking program is then exposed via the `NgCompiler`'s `getCurrentProgram()` operation, and new user programs are expected to be created incrementally on top of the previous template type-checking program. + +## Programs and the compiler as a service + +Not all clients of the compiler follow the incremental tick-tock scenario above. When the compiler is used as a service, new `ts.Program`s may be generated in response to various queries, either directly to `NgCompiler` or via the `TemplateTypeChecker`. Internally, the compiler will use the current `ProgramDriver` to create these additional `ts.Program`s as needed. + +Incremental builds (new user code changes) may also require changing the `ts.Program`, using the compiler's incremental ticket process. If the `TsCreateProgramDriver` is used, the client is responsible for ensuring that any new incremental `ts.Program`s are created on top of the current program from the previous compilation, which can be obtained via `NgCompiler`'s `getCurrentProgram()`. \ No newline at end of file diff --git a/packages/compiler-cli/src/ngtsc/program_driver/index.ts b/packages/compiler-cli/src/ngtsc/program_driver/index.ts new file mode 100644 index 0000000000..5478afc976 --- /dev/null +++ b/packages/compiler-cli/src/ngtsc/program_driver/index.ts @@ -0,0 +1,10 @@ +/** + * @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 + */ + +export * from './src/api'; +export {TsCreateProgramDriver} from './src/ts_create_program_driver'; diff --git a/packages/compiler-cli/src/ngtsc/program_driver/src/api.ts b/packages/compiler-cli/src/ngtsc/program_driver/src/api.ts new file mode 100644 index 0000000000..d8008489a8 --- /dev/null +++ b/packages/compiler-cli/src/ngtsc/program_driver/src/api.ts @@ -0,0 +1,44 @@ +/** + * @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 * as ts from 'typescript'; +import {AbsoluteFsPath} from '../../file_system'; + +export interface ProgramDriver { + /** + * Whether this strategy supports modifying user files (inline modifications) in addition to + * modifying type-checking shims. + */ + readonly supportsInlineOperations: boolean; + + /** + * Retrieve the latest version of the program, containing all the updates made thus far. + */ + getProgram(): ts.Program; + + /** + * Incorporate a set of changes to either augment or completely replace the type-checking code + * included in the type-checking program. + */ + updateFiles(contents: Map, updateMode: UpdateMode): void; +} + +export enum UpdateMode { + /** + * A complete update creates a completely new overlay of type-checking code on top of the user's + * original program, which doesn't include type-checking code from previous calls to + * `updateFiles`. + */ + Complete, + + /** + * An incremental update changes the contents of some files in the type-checking program without + * reverting any prior changes. + */ + Incremental, +} diff --git a/packages/compiler-cli/src/ngtsc/typecheck/src/host.ts b/packages/compiler-cli/src/ngtsc/program_driver/src/ts_create_program_driver.ts similarity index 61% rename from packages/compiler-cli/src/ngtsc/typecheck/src/host.ts rename to packages/compiler-cli/src/ngtsc/program_driver/src/ts_create_program_driver.ts index 41b6d9dfdc..74acda952e 100644 --- a/packages/compiler-cli/src/ngtsc/typecheck/src/host.ts +++ b/packages/compiler-cli/src/ngtsc/program_driver/src/ts_create_program_driver.ts @@ -8,9 +8,20 @@ import * as ts from 'typescript'; -import {copyFileShimData, ShimReferenceTagger} from '../../shims'; +/** + * @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 {AbsoluteFsPath} from '../../file_system'; +import {copyFileShimData, retagAllTsFiles, ShimReferenceTagger, untagAllTsFiles} from '../../shims'; import {RequiredDelegations} from '../../util/src/typescript'; +import {ProgramDriver, UpdateMode} from './api'; + /** * Delegates all methods of `ts.CompilerHost` to a delegate, with the exception of * `getSourceFile`, `fileExists` and `writeFile` which are implemented in `TypeCheckProgramHost`. @@ -51,10 +62,9 @@ export class DelegatingCompilerHost implements } /** - * A `ts.CompilerHost` which augments source files with type checking code from a - * `TypeCheckContext`. + * A `ts.CompilerHost` which augments source files. */ -export class TypeCheckProgramHost extends DelegatingCompilerHost { +class UpdatedProgramHost extends DelegatingCompilerHost { /** * Map of source file names to `ts.SourceFile` instances. */ @@ -63,12 +73,12 @@ export class TypeCheckProgramHost extends DelegatingCompilerHost { /** * The `ShimReferenceTagger` responsible for tagging `ts.SourceFile`s loaded via this host. * - * The `TypeCheckProgramHost` is used in the creation of a new `ts.Program`. Even though this new + * The `UpdatedProgramHost` is used in the creation of a new `ts.Program`. Even though this new * program is based on a prior one, TypeScript will still start from the root files and enumerate * all source files to include in the new program. This means that just like during the original * program's creation, these source files must be tagged with references to per-file shims in * order for those shims to be loaded, and then cleaned up afterwards. Thus the - * `TypeCheckProgramHost` has its own `ShimReferenceTagger` to perform this function. + * `UpdatedProgramHost` has its own `ShimReferenceTagger` to perform this function. */ private shimTagger = new ShimReferenceTagger(this.shimExtensionPrefixes); @@ -127,3 +137,72 @@ export class TypeCheckProgramHost extends DelegatingCompilerHost { return this.sfMap.has(fileName) || this.delegate.fileExists(fileName); } } + + +/** + * Updates a `ts.Program` instance with a new one that incorporates specific changes, using the + * TypeScript compiler APIs for incremental program creation. + */ +export class TsCreateProgramDriver implements ProgramDriver { + /** + * A map of source file paths to replacement `ts.SourceFile`s for those paths. + * + * Effectively, this tracks the delta between the user's program (represented by the + * `originalHost`) and the template type-checking program being managed. + */ + private sfMap = new Map(); + + private program: ts.Program = this.originalProgram; + + constructor( + private originalProgram: ts.Program, private originalHost: ts.CompilerHost, + private options: ts.CompilerOptions, private shimExtensionPrefixes: string[]) {} + + readonly supportsInlineOperations = true; + + getProgram(): ts.Program { + return this.program; + } + + updateFiles(contents: Map, updateMode: UpdateMode): void { + if (contents.size === 0) { + // No changes have been requested. Is it safe to skip updating entirely? + // If UpdateMode is Incremental, then yes. If UpdateMode is Complete, then it's safe to skip + // only if there are no active changes already (that would be cleared by the update). + + if (updateMode !== UpdateMode.Complete || this.sfMap.size === 0) { + // No changes would be made to the `ts.Program` anyway, so it's safe to do nothing here. + return; + } + } + + if (updateMode === UpdateMode.Complete) { + this.sfMap.clear(); + } + + for (const [filePath, text] of contents.entries()) { + this.sfMap.set(filePath, ts.createSourceFile(filePath, text, ts.ScriptTarget.Latest, true)); + } + + const host = new UpdatedProgramHost( + this.sfMap, this.originalProgram, this.originalHost, this.shimExtensionPrefixes); + const oldProgram = this.program; + + // Retag the old program's `ts.SourceFile`s with shim tags, to allow TypeScript to reuse the + // most data. + retagAllTsFiles(oldProgram); + + this.program = ts.createProgram({ + host, + rootNames: this.program.getRootFileNames(), + options: this.options, + oldProgram, + }); + host.postProgramCreationCleanup(); + + // And untag them afterwards. We explicitly untag both programs here, because the oldProgram + // may still be used for emit and needs to not contain tags. + untagAllTsFiles(this.program); + untagAllTsFiles(oldProgram); + } +} diff --git a/packages/compiler-cli/src/ngtsc/tsc_plugin.ts b/packages/compiler-cli/src/ngtsc/tsc_plugin.ts index 292c472a19..5a214876b1 100644 --- a/packages/compiler-cli/src/ngtsc/tsc_plugin.ts +++ b/packages/compiler-cli/src/ngtsc/tsc_plugin.ts @@ -12,10 +12,10 @@ import {CompilationTicket, freshCompilationTicket, incrementalFromDriverTicket, import {NgCompilerOptions, UnifiedModulesHost} from './core/api'; import {NodeJSFileSystem, setFileSystem} from './file_system'; import {PatchedProgramIncrementalBuildStrategy} from './incremental'; -import {ActivePerfRecorder, NOOP_PERF_RECORDER, PerfPhase} from './perf'; +import {ActivePerfRecorder, PerfPhase} from './perf'; +import {TsCreateProgramDriver} from './program_driver'; import {untagAllTsFiles} from './shims'; import {OptimizeFor} from './typecheck/api'; -import {ReusedProgramStrategy} from './typecheck/src/augmented_program'; // The following is needed to fix a the chicken-and-egg issue where the sync (into g3) script will // refuse to accept this file unless the following string appears: @@ -106,7 +106,7 @@ export class NgTscPlugin implements TscPlugin { } this.host.postProgramCreationCleanup(); untagAllTsFiles(program); - const typeCheckStrategy = new ReusedProgramStrategy( + const programDriver = new TsCreateProgramDriver( program, this.host, this.options, this.host.shimExtensionPrefixes); const strategy = new PatchedProgramIncrementalBuildStrategy(); const oldDriver = oldProgram !== undefined ? strategy.getIncrementalDriver(oldProgram) : null; @@ -122,12 +122,12 @@ export class NgTscPlugin implements TscPlugin { if (oldProgram === undefined || oldDriver === null) { ticket = freshCompilationTicket( - program, this.options, strategy, typeCheckStrategy, perfRecorder, + program, this.options, strategy, programDriver, perfRecorder, /* enableTemplateTypeChecker */ false, /* usePoisonedData */ false); } else { strategy.toNextBuildStrategy().getIncrementalDriver(oldProgram); ticket = incrementalFromDriverTicket( - oldProgram, oldDriver, program, this.options, strategy, typeCheckStrategy, + oldProgram, oldDriver, program, this.options, strategy, programDriver, modifiedResourceFiles, perfRecorder, false, false); } this._compiler = NgCompiler.fromTicket(ticket, this.host); @@ -149,7 +149,7 @@ export class NgTscPlugin implements TscPlugin { } getNextProgram(): ts.Program { - return this.compiler.getNextProgram(); + return this.compiler.getCurrentProgram(); } createTransformers(): ts.CustomTransformers { diff --git a/packages/compiler-cli/src/ngtsc/typecheck/BUILD.bazel b/packages/compiler-cli/src/ngtsc/typecheck/BUILD.bazel index a258bcd9be..47a64194b2 100644 --- a/packages/compiler-cli/src/ngtsc/typecheck/BUILD.bazel +++ b/packages/compiler-cli/src/ngtsc/typecheck/BUILD.bazel @@ -16,6 +16,7 @@ ts_library( "//packages/compiler-cli/src/ngtsc/incremental:api", "//packages/compiler-cli/src/ngtsc/metadata", "//packages/compiler-cli/src/ngtsc/perf", + "//packages/compiler-cli/src/ngtsc/program_driver", "//packages/compiler-cli/src/ngtsc/reflection", "//packages/compiler-cli/src/ngtsc/scope", "//packages/compiler-cli/src/ngtsc/shims", diff --git a/packages/compiler-cli/src/ngtsc/typecheck/api/api.ts b/packages/compiler-cli/src/ngtsc/typecheck/api/api.ts index 8f3c5a7d07..42c947784f 100644 --- a/packages/compiler-cli/src/ngtsc/typecheck/api/api.ts +++ b/packages/compiler-cli/src/ngtsc/typecheck/api/api.ts @@ -352,63 +352,3 @@ export interface FullTemplateMapping { templateSourceMapping: TemplateSourceMapping; span: ParseSourceSpan; } - -/** - * Abstracts the operation of determining which shim file will host a particular component's - * template type-checking code. - * - * Different consumers of the type checking infrastructure may choose different approaches to - * optimize for their specific use case (for example, the command-line compiler optimizes for - * efficient `ts.Program` reuse in watch mode). - */ -export interface ComponentToShimMappingStrategy { - /** - * Given a component, determine a path to the shim file into which that component's type checking - * code will be generated. - * - * A major constraint is that components in different input files must not share the same shim - * file. The behavior of the template type-checking system is undefined if this is violated. - */ - shimPathForComponent(node: ts.ClassDeclaration): AbsoluteFsPath; -} - -/** - * Strategy used to manage a `ts.Program` which contains template type-checking code and update it - * over time. - * - * This abstraction allows both the Angular compiler itself as well as the language service to - * implement efficient template type-checking using common infrastructure. - */ -export interface TypeCheckingProgramStrategy extends ComponentToShimMappingStrategy { - /** - * Whether this strategy supports modifying user files (inline modifications) in addition to - * modifying type-checking shims. - */ - readonly supportsInlineOperations: boolean; - - /** - * Retrieve the latest version of the program, containing all the updates made thus far. - */ - getProgram(): ts.Program; - - /** - * Incorporate a set of changes to either augment or completely replace the type-checking code - * included in the type-checking program. - */ - updateFiles(contents: Map, updateMode: UpdateMode): void; -} - -export enum UpdateMode { - /** - * A complete update creates a completely new overlay of type-checking code on top of the user's - * original program, which doesn't include type-checking code from previous calls to - * `updateFiles`. - */ - Complete, - - /** - * An incremental update changes the contents of some files in the type-checking program without - * reverting any prior changes. - */ - Incremental, -} diff --git a/packages/compiler-cli/src/ngtsc/typecheck/index.ts b/packages/compiler-cli/src/ngtsc/typecheck/index.ts index aaa0b33c99..1eb7540123 100644 --- a/packages/compiler-cli/src/ngtsc/typecheck/index.ts +++ b/packages/compiler-cli/src/ngtsc/typecheck/index.ts @@ -6,9 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -export {ReusedProgramStrategy} from './src/augmented_program'; export {FileTypeCheckingData, TemplateTypeCheckerImpl} from './src/checker'; export {TypeCheckContextImpl} from './src/context'; -export {TypeCheckProgramHost} from './src/host'; export {TypeCheckShimGenerator} from './src/shim'; export {typeCheckFilePath} from './src/type_check_file'; diff --git a/packages/compiler-cli/src/ngtsc/typecheck/src/augmented_program.ts b/packages/compiler-cli/src/ngtsc/typecheck/src/augmented_program.ts deleted file mode 100644 index 6176b36a12..0000000000 --- a/packages/compiler-cli/src/ngtsc/typecheck/src/augmented_program.ts +++ /dev/null @@ -1,88 +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 * as ts from 'typescript'; - -import {absoluteFromSourceFile, AbsoluteFsPath} from '../../file_system'; -import {retagAllTsFiles, untagAllTsFiles} from '../../shims'; -import {TypeCheckingProgramStrategy, UpdateMode} from '../api'; - -import {TypeCheckProgramHost} from './host'; -import {TypeCheckShimGenerator} from './shim'; - -/** - * Implements a template type-checking program using `ts.createProgram` and TypeScript's program - * reuse functionality. - */ -export class ReusedProgramStrategy implements TypeCheckingProgramStrategy { - /** - * A map of source file paths to replacement `ts.SourceFile`s for those paths. - * - * Effectively, this tracks the delta between the user's program (represented by the - * `originalHost`) and the template type-checking program being managed. - */ - private sfMap = new Map(); - - private program: ts.Program = this.originalProgram; - - constructor( - private originalProgram: ts.Program, private originalHost: ts.CompilerHost, - private options: ts.CompilerOptions, private shimExtensionPrefixes: string[]) {} - - readonly supportsInlineOperations = true; - - getProgram(): ts.Program { - return this.program; - } - - updateFiles(contents: Map, updateMode: UpdateMode): void { - if (contents.size === 0) { - // No changes have been requested. Is it safe to skip updating entirely? - // If UpdateMode is Incremental, then yes. If UpdateMode is Complete, then it's safe to skip - // only if there are no active changes already (that would be cleared by the update). - - if (updateMode !== UpdateMode.Complete || this.sfMap.size === 0) { - // No changes would be made to the `ts.Program` anyway, so it's safe to do nothing here. - return; - } - } - - if (updateMode === UpdateMode.Complete) { - this.sfMap.clear(); - } - - for (const [filePath, text] of contents.entries()) { - this.sfMap.set(filePath, ts.createSourceFile(filePath, text, ts.ScriptTarget.Latest, true)); - } - - const host = new TypeCheckProgramHost( - this.sfMap, this.originalProgram, this.originalHost, this.shimExtensionPrefixes); - const oldProgram = this.program; - - // Retag the old program's `ts.SourceFile`s with shim tags, to allow TypeScript to reuse the - // most data. - retagAllTsFiles(oldProgram); - - this.program = ts.createProgram({ - host, - rootNames: this.program.getRootFileNames(), - options: this.options, - oldProgram, - }); - host.postProgramCreationCleanup(); - - // And untag them afterwards. We explicitly untag both programs here, because the oldProgram - // may still be used for emit and needs to not contain tags. - untagAllTsFiles(this.program); - untagAllTsFiles(oldProgram); - } - - shimPathForComponent(node: ts.ClassDeclaration): AbsoluteFsPath { - return TypeCheckShimGenerator.shimFor(absoluteFromSourceFile(node.getSourceFile())); - } -} diff --git a/packages/compiler-cli/src/ngtsc/typecheck/src/checker.ts b/packages/compiler-cli/src/ngtsc/typecheck/src/checker.ts index 92ba82b43f..2490ad92bb 100644 --- a/packages/compiler-cli/src/ngtsc/typecheck/src/checker.ts +++ b/packages/compiler-cli/src/ngtsc/typecheck/src/checker.ts @@ -13,16 +13,18 @@ import {absoluteFrom, absoluteFromSourceFile, AbsoluteFsPath, getSourceFileOrErr import {Reference, ReferenceEmitter} from '../../imports'; import {IncrementalBuild} from '../../incremental/api'; import {PerfCheckpoint, PerfEvent, PerfPhase, PerfRecorder} from '../../perf'; +import {ProgramDriver, UpdateMode} from '../../program_driver'; import {ClassDeclaration, isNamedClassDeclaration, ReflectionHost} from '../../reflection'; import {ComponentScopeReader, TypeCheckScopeRegistry} from '../../scope'; import {isShim} from '../../shims'; import {getSourceFileOrNull} from '../../util/src/typescript'; -import {DirectiveInScope, ElementSymbol, FullTemplateMapping, GlobalCompletion, OptimizeFor, PipeInScope, ProgramTypeCheckAdapter, ShimLocation, Symbol, TemplateId, TemplateSymbol, TemplateTypeChecker, TypeCheckableDirectiveMeta, TypeCheckingConfig, TypeCheckingProgramStrategy, UpdateMode} from '../api'; +import {DirectiveInScope, ElementSymbol, FullTemplateMapping, GlobalCompletion, OptimizeFor, PipeInScope, ProgramTypeCheckAdapter, ShimLocation, Symbol, TemplateId, TemplateSymbol, TemplateTypeChecker, TypeCheckableDirectiveMeta, TypeCheckingConfig} from '../api'; import {TemplateDiagnostic} from '../diagnostics'; import {CompletionEngine} from './completion'; import {InliningMode, ShimTypeCheckingData, TemplateData, TypeCheckContextImpl, TypeCheckingHost} from './context'; import {shouldReportDiagnostic, translateDiagnostic} from './diagnostics'; +import {TypeCheckShimGenerator} from './shim'; import {TemplateSourceManager} from './source'; import {findTypeCheckBlock, getTemplateMapping, TemplateSourceResolver} from './tcb_util'; import {SymbolBuilder} from './template_symbol_builder'; @@ -76,8 +78,7 @@ export class TemplateTypeCheckerImpl implements TemplateTypeChecker { private isComplete = false; constructor( - private originalProgram: ts.Program, - readonly typeCheckingStrategy: TypeCheckingProgramStrategy, + private originalProgram: ts.Program, readonly programDriver: ProgramDriver, private typeCheckAdapter: ProgramTypeCheckAdapter, private config: TypeCheckingConfig, private refEmitter: ReferenceEmitter, private reflector: ReflectionHost, private compilerHost: Pick, @@ -100,7 +101,7 @@ export class TemplateTypeCheckerImpl implements TemplateTypeChecker { const sf = component.getSourceFile(); const sfPath = absoluteFromSourceFile(sf); - const shimPath = this.typeCheckingStrategy.shimPathForComponent(component); + const shimPath = TypeCheckShimGenerator.shimFor(sfPath); const fileRecord = this.getFileData(sfPath); @@ -112,7 +113,7 @@ export class TemplateTypeCheckerImpl implements TemplateTypeChecker { const shimRecord = fileRecord.shimData.get(shimPath)!; const id = fileRecord.sourceManager.getTemplateId(component); - const program = this.typeCheckingStrategy.getProgram(); + const program = this.programDriver.getProgram(); const shimSf = getSourceFileOrNull(program, shimPath); if (shimSf === null || !fileRecord.shimData.has(shimPath)) { @@ -157,7 +158,7 @@ export class TemplateTypeCheckerImpl implements TemplateTypeChecker { } const {fileRecord} = records; - const shimSf = this.typeCheckingStrategy.getProgram().getSourceFile(absoluteFrom(shimPath)); + const shimSf = this.programDriver.getProgram().getSourceFile(absoluteFrom(shimPath)); if (shimSf === undefined) { return null; } @@ -187,7 +188,7 @@ export class TemplateTypeCheckerImpl implements TemplateTypeChecker { const sfPath = absoluteFromSourceFile(sf); const fileRecord = this.state.get(sfPath)!; - const typeCheckProgram = this.typeCheckingStrategy.getProgram(); + const typeCheckProgram = this.programDriver.getProgram(); const diagnostics: (ts.Diagnostic|null)[] = []; if (fileRecord.hasInlines) { @@ -217,7 +218,7 @@ export class TemplateTypeCheckerImpl implements TemplateTypeChecker { return this.perf.inPhase(PerfPhase.TtcDiagnostics, () => { const sf = component.getSourceFile(); const sfPath = absoluteFromSourceFile(sf); - const shimPath = this.typeCheckingStrategy.shimPathForComponent(component); + const shimPath = TypeCheckShimGenerator.shimFor(sfPath); const fileRecord = this.getFileData(sfPath); @@ -228,7 +229,7 @@ export class TemplateTypeCheckerImpl implements TemplateTypeChecker { const templateId = fileRecord.sourceManager.getTemplateId(component); const shimRecord = fileRecord.shimData.get(shimPath)!; - const typeCheckProgram = this.typeCheckingStrategy.getProgram(); + const typeCheckProgram = this.programDriver.getProgram(); const diagnostics: (TemplateDiagnostic|null)[] = []; if (shimRecord.hasInlines) { @@ -285,7 +286,7 @@ export class TemplateTypeCheckerImpl implements TemplateTypeChecker { const sf = clazz.getSourceFile(); const sfPath = absoluteFromSourceFile(sf); - const shimPath = this.typeCheckingStrategy.shimPathForComponent(clazz); + const shimPath = TypeCheckShimGenerator.shimFor(sfPath); const fileData = this.getFileData(sfPath); const templateId = fileData.sourceManager.getTemplateId(clazz); @@ -374,8 +375,7 @@ export class TemplateTypeCheckerImpl implements TemplateTypeChecker { return; } - const host = - new SingleFileTypeCheckingHost(sfPath, fileData, this.typeCheckingStrategy, this); + const host = new SingleFileTypeCheckingHost(sfPath, fileData, this); const ctx = this.newContext(host); this.typeCheckAdapter.typeCheck(sf, ctx); @@ -389,19 +389,18 @@ export class TemplateTypeCheckerImpl implements TemplateTypeChecker { private ensureShimForComponent(component: ts.ClassDeclaration): void { const sf = component.getSourceFile(); const sfPath = absoluteFromSourceFile(sf); + const shimPath = TypeCheckShimGenerator.shimFor(sfPath); this.maybeAdoptPriorResultsForFile(sf); const fileData = this.getFileData(sfPath); - const shimPath = this.typeCheckingStrategy.shimPathForComponent(component); if (fileData.shimData.has(shimPath)) { // All data for this component is available. return; } - const host = - new SingleShimTypeCheckingHost(sfPath, fileData, this.typeCheckingStrategy, this, shimPath); + const host = new SingleShimTypeCheckingHost(sfPath, fileData, this, shimPath); const ctx = this.newContext(host); this.typeCheckAdapter.typeCheck(sf, ctx); @@ -409,11 +408,10 @@ export class TemplateTypeCheckerImpl implements TemplateTypeChecker { } private newContext(host: TypeCheckingHost): TypeCheckContextImpl { - const inlining = this.typeCheckingStrategy.supportsInlineOperations ? InliningMode.InlineOps : - InliningMode.Error; + const inlining = + this.programDriver.supportsInlineOperations ? InliningMode.InlineOps : InliningMode.Error; return new TypeCheckContextImpl( - this.config, this.compilerHost, this.typeCheckingStrategy, this.refEmitter, this.reflector, - host, inlining, this.perf); + this.config, this.compilerHost, this.refEmitter, this.reflector, host, inlining, this.perf); } /** @@ -446,7 +444,7 @@ export class TemplateTypeCheckerImpl implements TemplateTypeChecker { if (updates.size > 0) { this.perf.eventCount(PerfEvent.UpdateTypeCheckProgram); } - this.typeCheckingStrategy.updateFiles(updates, UpdateMode.Incremental); + this.programDriver.updateFiles(updates, UpdateMode.Incremental); this.priorBuild.recordSuccessfulTypeCheck(this.state); this.perf.memory(PerfCheckpoint.TtcUpdateProgram); }); @@ -485,7 +483,7 @@ export class TemplateTypeCheckerImpl implements TemplateTypeChecker { const builder = new SymbolBuilder( shimPath, tcb, data, this.componentScopeReader, - () => this.typeCheckingStrategy.getProgram().getTypeChecker()); + () => this.programDriver.getProgram().getTypeChecker()); this.symbolBuilderCache.set(component, builder); return builder; } @@ -571,7 +569,7 @@ export class TemplateTypeCheckerImpl implements TemplateTypeChecker { isPoisoned: scope.compilation.isPoisoned, }; - const typeChecker = this.typeCheckingStrategy.getProgram().getTypeChecker(); + const typeChecker = this.programDriver.getProgram().getTypeChecker(); for (const dir of scope.compilation.directives) { if (dir.selector === null) { // Skip this directive, it can't be added to a template anyway. @@ -664,8 +662,9 @@ class WholeProgramTypeCheckingHost implements TypeCheckingHost { } shouldCheckComponent(node: ts.ClassDeclaration): boolean { - const fileData = this.impl.getFileData(absoluteFromSourceFile(node.getSourceFile())); - const shimPath = this.impl.typeCheckingStrategy.shimPathForComponent(node); + const sfPath = absoluteFromSourceFile(node.getSourceFile()); + const shimPath = TypeCheckShimGenerator.shimFor(sfPath); + const fileData = this.impl.getFileData(sfPath); // The component needs to be checked unless the shim which would contain it already exists. return !fileData.shimData.has(shimPath); } @@ -691,7 +690,7 @@ class SingleFileTypeCheckingHost implements TypeCheckingHost { constructor( protected sfPath: AbsoluteFsPath, protected fileData: FileTypeCheckingData, - protected strategy: TypeCheckingProgramStrategy, protected impl: TemplateTypeCheckerImpl) {} + protected impl: TemplateTypeCheckerImpl) {} private assertPath(sfPath: AbsoluteFsPath): void { if (this.sfPath !== sfPath) { @@ -708,7 +707,7 @@ class SingleFileTypeCheckingHost implements TypeCheckingHost { if (this.sfPath !== absoluteFromSourceFile(node.getSourceFile())) { return false; } - const shimPath = this.strategy.shimPathForComponent(node); + const shimPath = TypeCheckShimGenerator.shimFor(this.sfPath); // Only need to generate a TCB for the class if no shim exists for it currently. return !this.fileData.shimData.has(shimPath); @@ -747,9 +746,9 @@ class SingleFileTypeCheckingHost implements TypeCheckingHost { */ class SingleShimTypeCheckingHost extends SingleFileTypeCheckingHost { constructor( - sfPath: AbsoluteFsPath, fileData: FileTypeCheckingData, strategy: TypeCheckingProgramStrategy, - impl: TemplateTypeCheckerImpl, private shimPath: AbsoluteFsPath) { - super(sfPath, fileData, strategy, impl); + sfPath: AbsoluteFsPath, fileData: FileTypeCheckingData, impl: TemplateTypeCheckerImpl, + private shimPath: AbsoluteFsPath) { + super(sfPath, fileData, impl); } shouldCheckNode(node: ts.ClassDeclaration): boolean { @@ -758,7 +757,7 @@ class SingleShimTypeCheckingHost extends SingleFileTypeCheckingHost { } // Only generate a TCB for the component if it maps to the requested shim file. - const shimPath = this.strategy.shimPathForComponent(node); + const shimPath = TypeCheckShimGenerator.shimFor(this.sfPath); if (shimPath !== this.shimPath) { return false; } diff --git a/packages/compiler-cli/src/ngtsc/typecheck/src/context.ts b/packages/compiler-cli/src/ngtsc/typecheck/src/context.ts index b06bdcfce3..4493872196 100644 --- a/packages/compiler-cli/src/ngtsc/typecheck/src/context.ts +++ b/packages/compiler-cli/src/ngtsc/typecheck/src/context.ts @@ -15,12 +15,13 @@ import {NoopImportRewriter, Reference, ReferenceEmitter} from '../../imports'; import {PerfEvent, PerfRecorder} from '../../perf'; import {ClassDeclaration, ReflectionHost} from '../../reflection'; import {ImportManager} from '../../translator'; -import {ComponentToShimMappingStrategy, TemplateId, TemplateSourceMapping, TypeCheckableDirectiveMeta, TypeCheckBlockMetadata, TypeCheckContext, TypeCheckingConfig, TypeCtorMetadata} from '../api'; +import {TemplateId, TemplateSourceMapping, TypeCheckableDirectiveMeta, TypeCheckBlockMetadata, TypeCheckContext, TypeCheckingConfig, TypeCtorMetadata} from '../api'; import {makeTemplateDiagnostic, TemplateDiagnostic} from '../diagnostics'; import {DomSchemaChecker, RegistryDomSchemaChecker} from './dom'; import {Environment} from './environment'; import {OutOfBandDiagnosticRecorder, OutOfBandDiagnosticRecorderImpl} from './oob'; +import {TypeCheckShimGenerator} from './shim'; import {TemplateSourceManager} from './source'; import {requiresInlineTypeCheckBlock} from './tcb_util'; import {generateTypeCheckBlock} from './type_check_block'; @@ -178,7 +179,6 @@ export class TypeCheckContextImpl implements TypeCheckContext { constructor( private config: TypeCheckingConfig, private compilerHost: Pick, - private componentMappingStrategy: ComponentToShimMappingStrategy, private refEmitter: ReferenceEmitter, private reflector: ReflectionHost, private host: TypeCheckingHost, private inlining: InliningMode, private perf: PerfRecorder) { if (inlining === InliningMode.Error && config.useInlineTypeConstructors) { @@ -410,7 +410,7 @@ export class TypeCheckContextImpl implements TypeCheckContext { private pendingShimForComponent(node: ts.ClassDeclaration): PendingShimData { const fileData = this.dataForFile(node.getSourceFile()); - const shimPath = this.componentMappingStrategy.shimPathForComponent(node); + const shimPath = TypeCheckShimGenerator.shimFor(absoluteFromSourceFile(node.getSourceFile())); if (!fileData.shimData.has(shimPath)) { fileData.shimData.set(shimPath, { domSchemaChecker: new RegistryDomSchemaChecker(fileData.sourceManager), diff --git a/packages/compiler-cli/src/ngtsc/typecheck/test/BUILD.bazel b/packages/compiler-cli/src/ngtsc/typecheck/test/BUILD.bazel index 853ecffa1a..f84d17a25d 100644 --- a/packages/compiler-cli/src/ngtsc/typecheck/test/BUILD.bazel +++ b/packages/compiler-cli/src/ngtsc/typecheck/test/BUILD.bazel @@ -18,6 +18,7 @@ ts_library( "//packages/compiler-cli/src/ngtsc/incremental", "//packages/compiler-cli/src/ngtsc/metadata", "//packages/compiler-cli/src/ngtsc/perf", + "//packages/compiler-cli/src/ngtsc/program_driver", "//packages/compiler-cli/src/ngtsc/reflection", "//packages/compiler-cli/src/ngtsc/scope", "//packages/compiler-cli/src/ngtsc/shims", diff --git a/packages/compiler-cli/src/ngtsc/typecheck/test/program_spec.ts b/packages/compiler-cli/src/ngtsc/typecheck/test/program_spec.ts index e1ae444dc2..ea15ed67dd 100644 --- a/packages/compiler-cli/src/ngtsc/typecheck/test/program_spec.ts +++ b/packages/compiler-cli/src/ngtsc/typecheck/test/program_spec.ts @@ -10,10 +10,10 @@ import * as ts from 'typescript'; import {absoluteFrom, AbsoluteFsPath, getSourceFileOrError} from '../../file_system'; import {runInEachFileSystem} from '../../file_system/testing'; +import {TsCreateProgramDriver, UpdateMode} from '../../program_driver'; import {sfExtensionData, ShimReferenceTagger} from '../../shims'; import {expectCompleteReuse, makeProgram} from '../../testing'; -import {OptimizeFor, UpdateMode} from '../api'; -import {ReusedProgramStrategy} from '../src/augmented_program'; +import {OptimizeFor} from '../api'; import {setup} from './test_utils'; @@ -37,7 +37,7 @@ runInEachFileSystem(() => { it('should have complete reuse if no structural changes are made to shims', () => { const {program, host, options, typecheckPath} = makeSingleFileProgramWithTypecheckShim(); - const programStrategy = new ReusedProgramStrategy(program, host, options, ['ngtypecheck']); + const programStrategy = new TsCreateProgramDriver(program, host, options, ['ngtypecheck']); // Update /main.ngtypecheck.ts without changing its shape. Verify that the old program was // reused completely. @@ -49,7 +49,7 @@ runInEachFileSystem(() => { it('should have complete reuse if no structural changes are made to input files', () => { const {program, host, options, mainPath} = makeSingleFileProgramWithTypecheckShim(); - const programStrategy = new ReusedProgramStrategy(program, host, options, ['ngtypecheck']); + const programStrategy = new TsCreateProgramDriver(program, host, options, ['ngtypecheck']); // Update /main.ts without changing its shape. Verify that the old program was reused // completely. diff --git a/packages/compiler-cli/src/ngtsc/typecheck/test/test_utils.ts b/packages/compiler-cli/src/ngtsc/typecheck/test/test_utils.ts index 71dc6a073e..09c0b72bb4 100644 --- a/packages/compiler-cli/src/ngtsc/typecheck/test/test_utils.ts +++ b/packages/compiler-cli/src/ngtsc/typecheck/test/test_utils.ts @@ -15,14 +15,14 @@ import {AbsoluteModuleStrategy, LocalIdentifierStrategy, LogicalProjectStrategy, import {NOOP_INCREMENTAL_BUILD} from '../../incremental'; import {ClassPropertyMapping, CompoundMetadataReader} from '../../metadata'; import {NOOP_PERF_RECORDER} from '../../perf'; +import {TsCreateProgramDriver} from '../../program_driver'; import {ClassDeclaration, isNamedClassDeclaration, TypeScriptReflectionHost} from '../../reflection'; import {ComponentScopeReader, LocalModuleScope, ScopeData, TypeCheckScopeRegistry} from '../../scope'; import {makeProgram} from '../../testing'; import {getRootDirs} from '../../util/src/typescript'; import {ProgramTypeCheckAdapter, TemplateTypeChecker, TypeCheckContext} from '../api'; -import {TemplateId, TemplateSourceMapping, TypeCheckableDirectiveMeta, TypeCheckBlockMetadata, TypeCheckingConfig, UpdateMode} from '../api/api'; +import {TemplateId, TemplateSourceMapping, TypeCheckableDirectiveMeta, TypeCheckBlockMetadata, TypeCheckingConfig} from '../api/api'; import {TemplateDiagnostic} from '../diagnostics'; -import {ReusedProgramStrategy} from '../src/augmented_program'; import {TemplateTypeCheckerImpl} from '../src/checker'; import {DomSchemaChecker} from '../src/dom'; import {Environment} from '../src/environment'; @@ -347,7 +347,7 @@ export function setup(targets: TypeCheckingTarget[], overrides: { } = {}): { templateTypeChecker: TemplateTypeChecker, program: ts.Program, - programStrategy: ReusedProgramStrategy, + programStrategy: TsCreateProgramDriver, } { const files = [ typescriptLibDts(), @@ -462,7 +462,7 @@ export function setup(targets: TypeCheckingTarget[], overrides: { } }); - const programStrategy = new ReusedProgramStrategy(program, host, options, ['ngtypecheck']); + const programStrategy = new TsCreateProgramDriver(program, host, options, ['ngtypecheck']); if (overrides.inlining !== undefined) { (programStrategy as any).supportsInlineOperations = overrides.inlining; } diff --git a/packages/compiler-cli/src/ngtsc/typecheck/test/type_constructor_spec.ts b/packages/compiler-cli/src/ngtsc/typecheck/test/type_constructor_spec.ts index 6cd0410e34..1fa41d2150 100644 --- a/packages/compiler-cli/src/ngtsc/typecheck/test/type_constructor_spec.ts +++ b/packages/compiler-cli/src/ngtsc/typecheck/test/type_constructor_spec.ts @@ -11,11 +11,10 @@ import {absoluteFrom, AbsoluteFsPath, getFileSystem, getSourceFileOrError, Logic import {runInEachFileSystem, TestFile} from '../../file_system/testing'; import {AbsoluteModuleStrategy, LocalIdentifierStrategy, LogicalProjectStrategy, ModuleResolver, Reference, ReferenceEmitter} from '../../imports'; import {NOOP_PERF_RECORDER} from '../../perf'; +import {TsCreateProgramDriver, UpdateMode} from '../../program_driver'; import {isNamedClassDeclaration, TypeScriptReflectionHost} from '../../reflection'; import {getDeclaration, makeProgram} from '../../testing'; import {getRootDirs} from '../../util/src/typescript'; -import {ComponentToShimMappingStrategy, UpdateMode} from '../api'; -import {ReusedProgramStrategy} from '../src/augmented_program'; import {InliningMode, PendingFileTypeCheckingData, TypeCheckContextImpl, TypeCheckingHost} from '../src/context'; import {TemplateSourceManager} from '../src/source'; import {TypeCheckFile} from '../src/type_check_file'; @@ -74,8 +73,8 @@ TestClass.ngTypeCtor({value: 'test'}); new LogicalProjectStrategy(reflectionHost, logicalFs), ]); const ctx = new TypeCheckContextImpl( - ALL_ENABLED_CONFIG, host, new TestMappingStrategy(), emitter, reflectionHost, - new TestTypeCheckingHost(), InliningMode.InlineOps, NOOP_PERF_RECORDER); + ALL_ENABLED_CONFIG, host, emitter, reflectionHost, new TestTypeCheckingHost(), + InliningMode.InlineOps, NOOP_PERF_RECORDER); const TestClass = getDeclaration(program, _('/main.ts'), 'TestClass', isNamedClassDeclaration); const pendingFile = makePendingFile(); @@ -113,8 +112,8 @@ TestClass.ngTypeCtor({value: 'test'}); ]); const pendingFile = makePendingFile(); const ctx = new TypeCheckContextImpl( - ALL_ENABLED_CONFIG, host, new TestMappingStrategy(), emitter, reflectionHost, - new TestTypeCheckingHost(), InliningMode.InlineOps, NOOP_PERF_RECORDER); + ALL_ENABLED_CONFIG, host, emitter, reflectionHost, new TestTypeCheckingHost(), + InliningMode.InlineOps, NOOP_PERF_RECORDER); const TestClass = getDeclaration(program, _('/main.ts'), 'TestClass', isNamedClassDeclaration); ctx.addInlineTypeCtor( @@ -128,7 +127,7 @@ TestClass.ngTypeCtor({value: 'test'}); }, coercedInputFields: new Set(), }); - const programStrategy = new ReusedProgramStrategy(program, host, options, []); + const programStrategy = new TsCreateProgramDriver(program, host, options, []); programStrategy.updateFiles(ctx.finalize(), UpdateMode.Complete); const TestClassWithCtor = getDeclaration( programStrategy.getProgram(), _('/main.ts'), 'TestClass', isNamedClassDeclaration); @@ -158,8 +157,8 @@ TestClass.ngTypeCtor({value: 'test'}); ]); const pendingFile = makePendingFile(); const ctx = new TypeCheckContextImpl( - ALL_ENABLED_CONFIG, host, new TestMappingStrategy(), emitter, reflectionHost, - new TestTypeCheckingHost(), InliningMode.InlineOps, NOOP_PERF_RECORDER); + ALL_ENABLED_CONFIG, host, emitter, reflectionHost, new TestTypeCheckingHost(), + InliningMode.InlineOps, NOOP_PERF_RECORDER); const TestClass = getDeclaration(program, _('/main.ts'), 'TestClass', isNamedClassDeclaration); ctx.addInlineTypeCtor( @@ -173,7 +172,7 @@ TestClass.ngTypeCtor({value: 'test'}); }, coercedInputFields: new Set(['bar']), }); - const programStrategy = new ReusedProgramStrategy(program, host, options, []); + const programStrategy = new TsCreateProgramDriver(program, host, options, []); programStrategy.updateFiles(ctx.finalize(), UpdateMode.Complete); const TestClassWithCtor = getDeclaration( programStrategy.getProgram(), _('/main.ts'), 'TestClass', isNamedClassDeclaration); @@ -216,9 +215,3 @@ class TestTypeCheckingHost implements TypeCheckingHost { recordComplete(): void {} } - -class TestMappingStrategy implements ComponentToShimMappingStrategy { - shimPathForComponent(): AbsoluteFsPath { - return absoluteFrom('/typecheck.ts'); - } -} diff --git a/packages/language-service/ivy/BUILD.bazel b/packages/language-service/ivy/BUILD.bazel index 314728e998..86a8b20334 100644 --- a/packages/language-service/ivy/BUILD.bazel +++ b/packages/language-service/ivy/BUILD.bazel @@ -16,6 +16,7 @@ ts_library( "//packages/compiler-cli/src/ngtsc/incremental", "//packages/compiler-cli/src/ngtsc/metadata", "//packages/compiler-cli/src/ngtsc/perf", + "//packages/compiler-cli/src/ngtsc/program_driver", "//packages/compiler-cli/src/ngtsc/reflection", "//packages/compiler-cli/src/ngtsc/shims", "//packages/compiler-cli/src/ngtsc/typecheck", diff --git a/packages/language-service/ivy/compiler_factory.ts b/packages/language-service/ivy/compiler_factory.ts index 2c634d1026..cc62d14ea2 100644 --- a/packages/language-service/ivy/compiler_factory.ts +++ b/packages/language-service/ivy/compiler_factory.ts @@ -9,8 +9,7 @@ import {CompilationTicket, freshCompilationTicket, incrementalFromCompilerTicket, NgCompiler, resourceChangeTicket} from '@angular/compiler-cli/src/ngtsc/core'; import {NgCompilerOptions} from '@angular/compiler-cli/src/ngtsc/core/api'; import {TrackedIncrementalBuildStrategy} from '@angular/compiler-cli/src/ngtsc/incremental'; -import {ActivePerfRecorder} from '@angular/compiler-cli/src/ngtsc/perf'; -import {TypeCheckingProgramStrategy} from '@angular/compiler-cli/src/ngtsc/typecheck/api'; +import {ProgramDriver} from '@angular/compiler-cli/src/ngtsc/program_driver'; import * as ts from 'typescript/lib/tsserverlibrary'; import {LanguageServiceAdapter} from './adapters'; @@ -32,7 +31,7 @@ export class CompilerFactory { constructor( private readonly adapter: LanguageServiceAdapter, - private readonly programStrategy: TypeCheckingProgramStrategy, + private readonly programStrategy: ProgramDriver, private readonly options: NgCompilerOptions, ) {} diff --git a/packages/language-service/ivy/completions.ts b/packages/language-service/ivy/completions.ts index 49f6b13682..69a0a8bd75 100644 --- a/packages/language-service/ivy/completions.ts +++ b/packages/language-service/ivy/completions.ts @@ -47,7 +47,7 @@ export enum CompletionNodeContext { * @param N type of the template node in question, narrowed accordingly. */ export class CompletionBuilder { - private readonly typeChecker = this.compiler.getNextProgram().getTypeChecker(); + private readonly typeChecker = this.compiler.getCurrentProgram().getTypeChecker(); private readonly templateTypeChecker = this.compiler.getTemplateTypeChecker(); private readonly nodeParent = this.targetDetails.parent; private readonly nodeContext = nodeContextFromTarget(this.targetDetails.context); diff --git a/packages/language-service/ivy/definitions.ts b/packages/language-service/ivy/definitions.ts index e8cf459ed5..087ec372ed 100644 --- a/packages/language-service/ivy/definitions.ts +++ b/packages/language-service/ivy/definitions.ts @@ -282,7 +282,7 @@ export class DefinitionBuilder { function getDefinitionForExpressionAtPosition( fileName: string, position: number, compiler: NgCompiler): ts.DefinitionInfoAndBoundSpan| undefined { - const sf = compiler.getNextProgram().getSourceFile(fileName); + const sf = compiler.getCurrentProgram().getSourceFile(fileName); if (sf === undefined) { return; } diff --git a/packages/language-service/ivy/language_service.ts b/packages/language-service/ivy/language_service.ts index 3ac3d5ff49..25ec273dd7 100644 --- a/packages/language-service/ivy/language_service.ts +++ b/packages/language-service/ivy/language_service.ts @@ -12,9 +12,10 @@ import {NgCompiler} from '@angular/compiler-cli/src/ngtsc/core'; import {ErrorCode, ngErrorCode} from '@angular/compiler-cli/src/ngtsc/diagnostics'; import {absoluteFrom, absoluteFromSourceFile, AbsoluteFsPath} from '@angular/compiler-cli/src/ngtsc/file_system'; import {PerfPhase} from '@angular/compiler-cli/src/ngtsc/perf'; +import {ProgramDriver} from '@angular/compiler-cli/src/ngtsc/program_driver'; import {isNamedClassDeclaration} from '@angular/compiler-cli/src/ngtsc/reflection'; import {TypeCheckShimGenerator} from '@angular/compiler-cli/src/ngtsc/typecheck'; -import {OptimizeFor, TypeCheckingProgramStrategy} from '@angular/compiler-cli/src/ngtsc/typecheck/api'; +import {OptimizeFor} from '@angular/compiler-cli/src/ngtsc/typecheck/api'; import {findFirstMatchingNode} from '@angular/compiler-cli/src/ngtsc/typecheck/src/comments'; import * as ts from 'typescript/lib/tsserverlibrary'; @@ -41,7 +42,7 @@ interface LanguageServiceConfig { export class LanguageService { private options: CompilerOptions; readonly compilerFactory: CompilerFactory; - private readonly strategy: TypeCheckingProgramStrategy; + private readonly programDriver: ProgramDriver; private readonly adapter: LanguageServiceAdapter; private readonly parseConfigHost: LSParseConfigHost; @@ -53,9 +54,9 @@ export class LanguageService { this.parseConfigHost = new LSParseConfigHost(project.projectService.host); this.options = parseNgCompilerOptions(project, this.parseConfigHost, config); logCompilerOptions(project, this.options); - this.strategy = createTypeCheckingProgramStrategy(project); + this.programDriver = createProgramDriver(project); this.adapter = new LanguageServiceAdapter(project); - this.compilerFactory = new CompilerFactory(this.adapter, this.strategy, this.options); + this.compilerFactory = new CompilerFactory(this.adapter, this.programDriver, this.options); this.watchConfigFile(project); } @@ -68,7 +69,7 @@ export class LanguageService { const ttc = compiler.getTemplateTypeChecker(); const diagnostics: ts.Diagnostic[] = []; if (isTypeScriptFile(fileName)) { - const program = compiler.getNextProgram(); + const program = compiler.getCurrentProgram(); const sourceFile = program.getSourceFile(fileName); if (sourceFile) { const ngDiagnostics = compiler.getDiagnosticsForFile(sourceFile, OptimizeFor.SingleFile); @@ -112,7 +113,7 @@ export class LanguageService { getDefinitionAndBoundSpan(fileName: string, position: number): ts.DefinitionInfoAndBoundSpan |undefined { return this.withCompilerAndPerfTracing(PerfPhase.LsDefinition, (compiler) => { - if (!isInAngularContext(compiler.getNextProgram(), fileName, position)) { + if (!isInAngularContext(compiler.getCurrentProgram(), fileName, position)) { return undefined; } return new DefinitionBuilder(this.tsLS, compiler) @@ -123,7 +124,7 @@ export class LanguageService { getTypeDefinitionAtPosition(fileName: string, position: number): readonly ts.DefinitionInfo[]|undefined { return this.withCompilerAndPerfTracing(PerfPhase.LsDefinition, (compiler) => { - if (!isTemplateContext(compiler.getNextProgram(), fileName, position)) { + if (!isTemplateContext(compiler.getCurrentProgram(), fileName, position)) { return undefined; } return new DefinitionBuilder(this.tsLS, compiler) @@ -142,7 +143,7 @@ export class LanguageService { position: number, compiler: NgCompiler, ): ts.QuickInfo|undefined { - if (!isTemplateContext(compiler.getNextProgram(), fileName, position)) { + if (!isTemplateContext(compiler.getCurrentProgram(), fileName, position)) { return undefined; } @@ -166,14 +167,14 @@ export class LanguageService { getReferencesAtPosition(fileName: string, position: number): ts.ReferenceEntry[]|undefined { return this.withCompilerAndPerfTracing(PerfPhase.LsReferencesAndRenames, (compiler) => { - return new ReferencesAndRenameBuilder(this.strategy, this.tsLS, compiler) + return new ReferencesAndRenameBuilder(this.programDriver, this.tsLS, compiler) .getReferencesAtPosition(fileName, position); }); } getRenameInfo(fileName: string, position: number): ts.RenameInfo { return this.withCompilerAndPerfTracing(PerfPhase.LsReferencesAndRenames, (compiler) => { - const renameInfo = new ReferencesAndRenameBuilder(this.strategy, this.tsLS, compiler) + const renameInfo = new ReferencesAndRenameBuilder(this.programDriver, this.tsLS, compiler) .getRenameInfo(absoluteFrom(fileName), position); if (!renameInfo.canRename) { return renameInfo; @@ -189,7 +190,7 @@ export class LanguageService { findRenameLocations(fileName: string, position: number): readonly ts.RenameLocation[]|undefined { return this.withCompilerAndPerfTracing(PerfPhase.LsReferencesAndRenames, (compiler) => { - return new ReferencesAndRenameBuilder(this.strategy, this.tsLS, compiler) + return new ReferencesAndRenameBuilder(this.programDriver, this.tsLS, compiler) .findRenameLocations(fileName, position); }); } @@ -225,7 +226,7 @@ export class LanguageService { private getCompletionsAtPositionImpl( fileName: string, position: number, options: ts.GetCompletionsAtPositionOptions|undefined, compiler: NgCompiler): ts.WithMetadata|undefined { - if (!isTemplateContext(compiler.getNextProgram(), fileName, position)) { + if (!isTemplateContext(compiler.getCurrentProgram(), fileName, position)) { return undefined; } @@ -241,7 +242,7 @@ export class LanguageService { formatOptions: ts.FormatCodeOptions|ts.FormatCodeSettings|undefined, preferences: ts.UserPreferences|undefined): ts.CompletionEntryDetails|undefined { return this.withCompilerAndPerfTracing(PerfPhase.LsCompletions, (compiler) => { - if (!isTemplateContext(compiler.getNextProgram(), fileName, position)) { + if (!isTemplateContext(compiler.getCurrentProgram(), fileName, position)) { return undefined; } @@ -256,7 +257,7 @@ export class LanguageService { getCompletionEntrySymbol(fileName: string, position: number, entryName: string): ts.Symbol |undefined { return this.withCompilerAndPerfTracing(PerfPhase.LsCompletions, (compiler) => { - if (!isTemplateContext(compiler.getNextProgram(), fileName, position)) { + if (!isTemplateContext(compiler.getCurrentProgram(), fileName, position)) { return undefined; } @@ -457,13 +458,9 @@ function parseNgCompilerOptions( return options; } -function createTypeCheckingProgramStrategy(project: ts.server.Project): - TypeCheckingProgramStrategy { +function createProgramDriver(project: ts.server.Project): ProgramDriver { return { supportsInlineOperations: false, - shimPathForComponent(component: ts.ClassDeclaration): AbsoluteFsPath { - return TypeCheckShimGenerator.shimFor(absoluteFromSourceFile(component.getSourceFile())); - }, getProgram(): ts.Program { const program = project.getLanguageService().getProgram(); if (!program) { diff --git a/packages/language-service/ivy/quick_info.ts b/packages/language-service/ivy/quick_info.ts index fb40c07dcb..b35820cacf 100644 --- a/packages/language-service/ivy/quick_info.ts +++ b/packages/language-service/ivy/quick_info.ts @@ -14,7 +14,7 @@ import {createDisplayParts, DisplayInfoKind, SYMBOL_PUNC, SYMBOL_SPACE, SYMBOL_T import {filterAliasImports, getDirectiveMatchesForAttribute, getDirectiveMatchesForElementTag, getTextSpanOfNode} from './utils'; export class QuickInfoBuilder { - private readonly typeChecker = this.compiler.getNextProgram().getTypeChecker(); + private readonly typeChecker = this.compiler.getCurrentProgram().getTypeChecker(); constructor( private readonly tsLS: ts.LanguageService, private readonly compiler: NgCompiler, diff --git a/packages/language-service/ivy/references.ts b/packages/language-service/ivy/references.ts index 5881da4cd5..7e0ba346c5 100644 --- a/packages/language-service/ivy/references.ts +++ b/packages/language-service/ivy/references.ts @@ -9,7 +9,8 @@ import {AbsoluteSourceSpan, AST, BindingPipe, LiteralPrimitive, MethodCall, Pars import {NgCompiler} from '@angular/compiler-cli/src/ngtsc/core'; import {absoluteFrom, absoluteFromSourceFile, AbsoluteFsPath} from '@angular/compiler-cli/src/ngtsc/file_system'; import {PerfPhase} from '@angular/compiler-cli/src/ngtsc/perf'; -import {DirectiveSymbol, ShimLocation, SymbolKind, TemplateTypeChecker, TypeCheckingProgramStrategy} from '@angular/compiler-cli/src/ngtsc/typecheck/api'; +import {ProgramDriver} from '@angular/compiler-cli/src/ngtsc/program_driver'; +import {DirectiveSymbol, ShimLocation, SymbolKind, TemplateTypeChecker} from '@angular/compiler-cli/src/ngtsc/typecheck/api'; import {ExpressionIdentifier, hasExpressionIdentifier} from '@angular/compiler-cli/src/ngtsc/typecheck/src/comments'; import * as ts from 'typescript'; @@ -62,8 +63,8 @@ export class ReferencesAndRenameBuilder { private readonly ttc = this.compiler.getTemplateTypeChecker(); constructor( - private readonly strategy: TypeCheckingProgramStrategy, - private readonly tsLS: ts.LanguageService, private readonly compiler: NgCompiler) {} + private readonly driver: ProgramDriver, private readonly tsLS: ts.LanguageService, + private readonly compiler: NgCompiler) {} getRenameInfo(filePath: string, position: number): Omit|ts.RenameInfoFailure { @@ -146,7 +147,7 @@ export class ReferencesAndRenameBuilder { } private getTsNodeAtPosition(filePath: string, position: number): ts.Node|null { - const sf = this.strategy.getProgram().getSourceFile(filePath); + const sf = this.driver.getProgram().getSourceFile(filePath); if (!sf) { return null; } @@ -376,7 +377,7 @@ export class ReferencesAndRenameBuilder { private convertToTemplateDocumentSpan( shimDocumentSpan: T, templateTypeChecker: TemplateTypeChecker, requiredNodeText?: string): T |null { - const sf = this.strategy.getProgram().getSourceFile(shimDocumentSpan.fileName); + const sf = this.driver.getProgram().getSourceFile(shimDocumentSpan.fileName); if (sf === undefined) { return null; } diff --git a/packages/language-service/ivy/utils.ts b/packages/language-service/ivy/utils.ts index a7ab8156b8..f204763c62 100644 --- a/packages/language-service/ivy/utils.ts +++ b/packages/language-service/ivy/utils.ts @@ -121,7 +121,7 @@ function getInlineTemplateInfoAtPosition( export function getTemplateInfoAtPosition( fileName: string, position: number, compiler: NgCompiler): TemplateInfo|undefined { if (isTypeScriptFile(fileName)) { - const sf = compiler.getNextProgram().getSourceFile(fileName); + const sf = compiler.getCurrentProgram().getSourceFile(fileName); if (sf === undefined) { return undefined; }