refactor(compiler-cli): extract NgCompilerAdapter interface (#37118)

`NgCompiler` is the heart of ngtsc and can be used to analyze and compile
Angular programs in a variety of environments. Most of these integrations
rely on `NgProgram` and the creation of an `NgCompilerHost` in order to
create a `ts.Program` with the right shape for `NgCompiler`.

However, certain environments (such as the Angular Language Service) have
their own mechanisms for creating `ts.Program`s that don't make use of a
`ts.CompilerHost`. In such environments, an `NgCompilerHost` does not make
sense.

This commit breaks the dependency of `NgCompiler` on `NgCompilerHost` and
extracts the specific interface of the host on which `NgCompiler` depends
into a new interface, `NgCompilerAdapter`. This interface includes methods
from `ts.CompilerHost`, the `ExtendedTsCompilerHost`, as well as APIs from
`NgCompilerHost`.

A consumer such as the language service can implement this API without
needing to jump through hoops to create an `NgCompilerHost` implementation
that somehow wraps its specific environment.

PR Close #37118
This commit is contained in:
Alex Rickabaugh 2020-05-14 11:24:59 -07:00 committed by atscott
parent 965a688c97
commit e648a0c4ca
20 changed files with 185 additions and 88 deletions

View File

@ -20,7 +20,7 @@ ts_library(
"//packages/compiler-cli/src/ngtsc/reflection", "//packages/compiler-cli/src/ngtsc/reflection",
"//packages/compiler-cli/src/ngtsc/routing", "//packages/compiler-cli/src/ngtsc/routing",
"//packages/compiler-cli/src/ngtsc/scope", "//packages/compiler-cli/src/ngtsc/scope",
"//packages/compiler-cli/src/ngtsc/shims", "//packages/compiler-cli/src/ngtsc/shims:api",
"//packages/compiler-cli/src/ngtsc/transform", "//packages/compiler-cli/src/ngtsc/transform",
"//packages/compiler-cli/src/ngtsc/typecheck", "//packages/compiler-cli/src/ngtsc/typecheck",
"//packages/compiler-cli/src/ngtsc/util", "//packages/compiler-cli/src/ngtsc/util",

View File

@ -16,7 +16,7 @@ import {PartialEvaluator, ResolvedValue, ResolvedValueArray} from '../../partial
import {ClassDeclaration, Decorator, ReflectionHost, reflectObjectLiteral, typeNodeToValueExpr} from '../../reflection'; import {ClassDeclaration, Decorator, ReflectionHost, reflectObjectLiteral, typeNodeToValueExpr} from '../../reflection';
import {NgModuleRouteAnalyzer} from '../../routing'; import {NgModuleRouteAnalyzer} from '../../routing';
import {LocalModuleScopeRegistry, ScopeData} from '../../scope'; import {LocalModuleScopeRegistry, ScopeData} from '../../scope';
import {FactoryTracker} from '../../shims'; import {FactoryTracker} from '../../shims/api';
import {AnalysisOutput, CompileResult, DecoratorHandler, DetectResult, HandlerPrecedence, ResolveResult} from '../../transform'; import {AnalysisOutput, CompileResult, DecoratorHandler, DetectResult, HandlerPrecedence, ResolveResult} from '../../transform';
import {getSourceFile} from '../../util/src/typescript'; import {getSourceFile} from '../../util/src/typescript';

View File

@ -43,6 +43,7 @@ ts_library(
srcs = glob(["api/**/*.ts"]), srcs = glob(["api/**/*.ts"]),
deps = [ deps = [
"//packages/compiler-cli/src/ngtsc/file_system", "//packages/compiler-cli/src/ngtsc/file_system",
"//packages/compiler-cli/src/ngtsc/shims:api",
"@npm//typescript", "@npm//typescript",
], ],
) )

View File

@ -6,6 +6,7 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
export * from './src/adapter';
export * from './src/interfaces'; export * from './src/interfaces';
export * from './src/options'; export * from './src/options';
export * from './src/public_options'; export * from './src/public_options';

View File

@ -0,0 +1,99 @@
/**
* @license
* Copyright Google Inc. 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';
import {FactoryTracker} from '../../../shims/api';
import {ExtendedTsCompilerHost, UnifiedModulesHost} from './interfaces';
/**
* Names of methods from `ExtendedTsCompilerHost` that need to be provided by the
* `NgCompilerAdapter`.
*/
export type ExtendedCompilerHostMethods =
// Used to normalize filenames for the host system. Important for proper case-sensitive file
// handling.
'getCanonicalFileName'|
// An optional method of `ts.CompilerHost` where an implementer can override module resolution.
'resolveModuleNames'|
// Retrieve the current working directory. Unlike in `ts.ModuleResolutionHost`, this is a
// required method.
'getCurrentDirectory'|
// Additional methods of `ExtendedTsCompilerHost` related to resource files (e.g. HTML
// templates). These are optional.
'getModifiedResourceFiles'|'readResource'|'resourceNameToFileName';
/**
* Adapter for `NgCompiler` that allows it to be used in various circumstances, such as
* command-line `ngc`, as a plugin to `ts_library` in Bazel, or from the Language Service.
*
* `NgCompilerAdapter` is a subset of the `NgCompilerHost` implementation of `ts.CompilerHost`
* which is relied upon by `NgCompiler`. A consumer of `NgCompiler` can therefore use the
* `NgCompilerHost` or implement `NgCompilerAdapter` itself.
*/
export interface NgCompilerAdapter extends
// getCurrentDirectory is removed from `ts.ModuleResolutionHost` because it's optional, and
// incompatible with the `ts.CompilerHost` version which isn't. The combination of these two
// still satisfies `ts.ModuleResolutionHost`.
Omit<ts.ModuleResolutionHost, 'getCurrentDirectory'>,
Pick<ExtendedTsCompilerHost, 'getCurrentDirectory'|ExtendedCompilerHostMethods> {
/**
* A path to a single file which represents the entrypoint of an Angular Package Format library,
* if the current program is one.
*
* This is used to emit a flat module index if requested, and can be left `null` if that is not
* required.
*/
readonly entryPoint: AbsoluteFsPath|null;
/**
* An array of `ts.Diagnostic`s that occurred during construction of the `ts.Program`.
*/
readonly constructionDiagnostics: ts.Diagnostic[];
/**
* A `Set` of `ts.SourceFile`s which are internal to the program and should not be emitted as JS
* files.
*
* Often these are shim files such as `ngtypecheck` shims used for template type-checking in
* command-line ngc.
*/
readonly ignoreForEmit: Set<ts.SourceFile>;
/**
* A tracker for usage of symbols in `.ngfactory` shims.
*
* This can be left `null` if such shims are not a part of the `ts.Program`.
*/
readonly factoryTracker: FactoryTracker|null;
/**
* A specialized interface provided in some environments (such as Bazel) which overrides how
* import specifiers are generated.
*
* If not required, this can be `null`.
*/
readonly unifiedModulesHost: UnifiedModulesHost|null;
/**
* Resolved list of root directories explicitly set in, or inferred from, the tsconfig.
*/
readonly rootDirs: ReadonlyArray<AbsoluteFsPath>;
/**
* Distinguishes between shim files added by Angular to the compilation process (both those
* intended for output, like ngfactory files, as well as internal shims like ngtypecheck files)
* and original files in the user's program.
*
* This is mostly used to limit type-checking operations to only user files. It should return
* `true` if a file was written by the user, and `false` if a file was added by the compiler.
*/
isShim(sf: ts.SourceFile): boolean;
}

View File

@ -22,7 +22,7 @@ import {ModuleWithProvidersScanner} from '../../modulewithproviders';
import {PartialEvaluator} from '../../partial_evaluator'; import {PartialEvaluator} from '../../partial_evaluator';
import {NOOP_PERF_RECORDER, PerfRecorder} from '../../perf'; import {NOOP_PERF_RECORDER, PerfRecorder} from '../../perf';
import {TypeScriptReflectionHost} from '../../reflection'; import {TypeScriptReflectionHost} from '../../reflection';
import {HostResourceLoader} from '../../resource'; import {AdapterResourceLoader} from '../../resource';
import {entryPointKeyFor, NgModuleRouteAnalyzer} from '../../routing'; import {entryPointKeyFor, NgModuleRouteAnalyzer} from '../../routing';
import {ComponentScopeReader, LocalModuleScopeRegistry, MetadataDtsModuleScopeResolver} from '../../scope'; import {ComponentScopeReader, LocalModuleScopeRegistry, MetadataDtsModuleScopeResolver} from '../../scope';
import {generatedFactoryTransform} from '../../shims'; import {generatedFactoryTransform} from '../../shims';
@ -30,11 +30,7 @@ import {ivySwitchTransform} from '../../switch';
import {aliasTransformFactory, declarationTransformFactory, DecoratorHandler, DtsTransformRegistry, ivyTransformFactory, TraitCompiler} from '../../transform'; import {aliasTransformFactory, declarationTransformFactory, DecoratorHandler, DtsTransformRegistry, ivyTransformFactory, TraitCompiler} from '../../transform';
import {isTemplateDiagnostic, TemplateTypeChecker, TypeCheckContext, TypeCheckingConfig, TypeCheckingProgramStrategy} from '../../typecheck'; import {isTemplateDiagnostic, TemplateTypeChecker, TypeCheckContext, TypeCheckingConfig, TypeCheckingProgramStrategy} from '../../typecheck';
import {getSourceFileOrNull, isDtsPath, resolveModuleName} from '../../util/src/typescript'; import {getSourceFileOrNull, isDtsPath, resolveModuleName} from '../../util/src/typescript';
import {LazyRoute, NgCompilerOptions} from '../api'; import {LazyRoute, NgCompilerAdapter, NgCompilerOptions} from '../api';
import {NgCompilerHost} from './host';
/** /**
* State information about a compilation which is only generated once some data is requested from * State information about a compilation which is only generated once some data is requested from
@ -94,18 +90,18 @@ export class NgCompiler {
private nextProgram: ts.Program; private nextProgram: ts.Program;
private entryPoint: ts.SourceFile|null; private entryPoint: ts.SourceFile|null;
private moduleResolver: ModuleResolver; private moduleResolver: ModuleResolver;
private resourceManager: HostResourceLoader; private resourceManager: AdapterResourceLoader;
private cycleAnalyzer: CycleAnalyzer; private cycleAnalyzer: CycleAnalyzer;
readonly incrementalDriver: IncrementalDriver; readonly incrementalDriver: IncrementalDriver;
readonly ignoreForDiagnostics: Set<ts.SourceFile>; readonly ignoreForDiagnostics: Set<ts.SourceFile>;
readonly ignoreForEmit: Set<ts.SourceFile>; readonly ignoreForEmit: Set<ts.SourceFile>;
constructor( constructor(
private host: NgCompilerHost, private options: NgCompilerOptions, private adapter: NgCompilerAdapter, private options: NgCompilerOptions,
private tsProgram: ts.Program, private tsProgram: ts.Program,
private typeCheckingProgramStrategy: TypeCheckingProgramStrategy, private typeCheckingProgramStrategy: TypeCheckingProgramStrategy,
oldProgram: ts.Program|null = null, private perfRecorder: PerfRecorder = NOOP_PERF_RECORDER) { oldProgram: ts.Program|null = null, private perfRecorder: PerfRecorder = NOOP_PERF_RECORDER) {
this.constructionDiagnostics.push(...this.host.diagnostics); this.constructionDiagnostics.push(...this.adapter.constructionDiagnostics);
const incompatibleTypeCheckOptionsDiagnostic = verifyCompatibleTypeCheckOptions(this.options); const incompatibleTypeCheckOptionsDiagnostic = verifyCompatibleTypeCheckOptions(this.options);
if (incompatibleTypeCheckOptionsDiagnostic !== null) { if (incompatibleTypeCheckOptionsDiagnostic !== null) {
this.constructionDiagnostics.push(incompatibleTypeCheckOptionsDiagnostic); this.constructionDiagnostics.push(incompatibleTypeCheckOptionsDiagnostic);
@ -115,18 +111,19 @@ export class NgCompiler {
this.closureCompilerEnabled = !!this.options.annotateForClosureCompiler; this.closureCompilerEnabled = !!this.options.annotateForClosureCompiler;
this.entryPoint = this.entryPoint =
host.entryPoint !== null ? getSourceFileOrNull(tsProgram, host.entryPoint) : null; adapter.entryPoint !== null ? getSourceFileOrNull(tsProgram, adapter.entryPoint) : null;
const moduleResolutionCache = ts.createModuleResolutionCache( const moduleResolutionCache = ts.createModuleResolutionCache(
this.host.getCurrentDirectory(), fileName => this.host.getCanonicalFileName(fileName)); this.adapter.getCurrentDirectory(),
fileName => this.adapter.getCanonicalFileName(fileName));
this.moduleResolver = this.moduleResolver =
new ModuleResolver(tsProgram, this.options, this.host, moduleResolutionCache); new ModuleResolver(tsProgram, this.options, this.adapter, moduleResolutionCache);
this.resourceManager = new HostResourceLoader(host, this.options); this.resourceManager = new AdapterResourceLoader(adapter, this.options);
this.cycleAnalyzer = new CycleAnalyzer(new ImportGraph(this.moduleResolver)); this.cycleAnalyzer = new CycleAnalyzer(new ImportGraph(this.moduleResolver));
let modifiedResourceFiles: Set<string>|null = null; let modifiedResourceFiles: Set<string>|null = null;
if (this.host.getModifiedResourceFiles !== undefined) { if (this.adapter.getModifiedResourceFiles !== undefined) {
modifiedResourceFiles = this.host.getModifiedResourceFiles() || null; modifiedResourceFiles = this.adapter.getModifiedResourceFiles() || null;
} }
if (oldProgram === null) { if (oldProgram === null) {
@ -146,9 +143,9 @@ export class NgCompiler {
setIncrementalDriver(tsProgram, this.incrementalDriver); setIncrementalDriver(tsProgram, this.incrementalDriver);
this.ignoreForDiagnostics = this.ignoreForDiagnostics =
new Set(tsProgram.getSourceFiles().filter(sf => this.host.isShim(sf))); new Set(tsProgram.getSourceFiles().filter(sf => this.adapter.isShim(sf)));
this.ignoreForEmit = this.host.ignoreForEmit; this.ignoreForEmit = this.adapter.ignoreForEmit;
} }
/** /**
@ -279,7 +276,7 @@ export class NgCompiler {
const containingFile = this.tsProgram.getRootFileNames()[0]; const containingFile = this.tsProgram.getRootFileNames()[0];
const [entryPath, moduleName] = entryRoute.split('#'); const [entryPath, moduleName] = entryRoute.split('#');
const resolvedModule = const resolvedModule =
resolveModuleName(entryPath, containingFile, this.options, this.host, null); resolveModuleName(entryPath, containingFile, this.options, this.adapter, null);
if (resolvedModule) { if (resolvedModule) {
entryRoute = entryPointKeyFor(resolvedModule.resolvedFileName, moduleName); entryRoute = entryPointKeyFor(resolvedModule.resolvedFileName, moduleName);
@ -326,8 +323,9 @@ export class NgCompiler {
afterDeclarations.push(aliasTransformFactory(compilation.traitCompiler.exportStatements)); afterDeclarations.push(aliasTransformFactory(compilation.traitCompiler.exportStatements));
} }
if (this.host.factoryTracker !== null) { if (this.adapter.factoryTracker !== null) {
before.push(generatedFactoryTransform(this.host.factoryTracker.sourceInfo, importRewriter)); before.push(
generatedFactoryTransform(this.adapter.factoryTracker.sourceInfo, importRewriter));
} }
before.push(ivySwitchTransform); before.push(ivySwitchTransform);
@ -499,7 +497,7 @@ export class NgCompiler {
const typeCheckSpan = this.perfRecorder.start('typeCheckDiagnostics'); const typeCheckSpan = this.perfRecorder.start('typeCheckDiagnostics');
const diagnostics: ts.Diagnostic[] = []; const diagnostics: ts.Diagnostic[] = [];
for (const sf of this.tsProgram.getSourceFiles()) { for (const sf of this.tsProgram.getSourceFiles()) {
if (sf.isDeclarationFile || this.host.isShim(sf)) { if (sf.isDeclarationFile || this.adapter.isShim(sf)) {
continue; continue;
} }
@ -600,7 +598,7 @@ export class NgCompiler {
// Construct the ReferenceEmitter. // Construct the ReferenceEmitter.
let refEmitter: ReferenceEmitter; let refEmitter: ReferenceEmitter;
let aliasingHost: AliasingHost|null = null; let aliasingHost: AliasingHost|null = null;
if (this.host.unifiedModulesHost === null || !this.options._useHostForImportGeneration) { if (this.adapter.unifiedModulesHost === null || !this.options._useHostForImportGeneration) {
let localImportStrategy: ReferenceEmitStrategy; let localImportStrategy: ReferenceEmitStrategy;
// The strategy used for local, in-project imports depends on whether TS has been configured // The strategy used for local, in-project imports depends on whether TS has been configured
@ -613,7 +611,7 @@ export class NgCompiler {
// rootDirs logic is in effect - use the `LogicalProjectStrategy` for in-project relative // rootDirs logic is in effect - use the `LogicalProjectStrategy` for in-project relative
// imports. // imports.
localImportStrategy = new LogicalProjectStrategy( localImportStrategy = new LogicalProjectStrategy(
reflector, new LogicalFileSystem([...this.host.rootDirs], this.host)); reflector, new LogicalFileSystem([...this.adapter.rootDirs], this.adapter));
} else { } else {
// Plain relative imports are all that's needed. // Plain relative imports are all that's needed.
localImportStrategy = new RelativePathStrategy(reflector); localImportStrategy = new RelativePathStrategy(reflector);
@ -648,9 +646,9 @@ export class NgCompiler {
// Then use aliased references (this is a workaround to StrictDeps checks). // Then use aliased references (this is a workaround to StrictDeps checks).
new AliasStrategy(), new AliasStrategy(),
// Then use fileNameToModuleName to emit imports. // Then use fileNameToModuleName to emit imports.
new UnifiedModulesStrategy(reflector, this.host.unifiedModulesHost), new UnifiedModulesStrategy(reflector, this.adapter.unifiedModulesHost),
]); ]);
aliasingHost = new UnifiedModulesAliasingHost(this.host.unifiedModulesHost); aliasingHost = new UnifiedModulesAliasingHost(this.adapter.unifiedModulesHost);
} }
const evaluator = new PartialEvaluator(reflector, checker, this.incrementalDriver.depGraph); const evaluator = new PartialEvaluator(reflector, checker, this.incrementalDriver.depGraph);
@ -693,7 +691,7 @@ export class NgCompiler {
const handlers: DecoratorHandler<unknown, unknown, unknown>[] = [ const handlers: DecoratorHandler<unknown, unknown, unknown>[] = [
new ComponentDecoratorHandler( new ComponentDecoratorHandler(
reflector, evaluator, metaRegistry, metaReader, scopeReader, scopeRegistry, isCore, reflector, evaluator, metaRegistry, metaReader, scopeReader, scopeRegistry, isCore,
this.resourceManager, this.host.rootDirs, this.options.preserveWhitespaces || false, this.resourceManager, this.adapter.rootDirs, this.options.preserveWhitespaces || false,
this.options.i18nUseExternalIds !== false, this.options.i18nUseExternalIds !== false,
this.options.enableI18nLegacyMessageIdFormat !== false, this.options.enableI18nLegacyMessageIdFormat !== false,
this.options.i18nNormalizeLineEndingsInICUs, this.moduleResolver, this.cycleAnalyzer, this.options.i18nNormalizeLineEndingsInICUs, this.moduleResolver, this.cycleAnalyzer,
@ -721,7 +719,7 @@ export class NgCompiler {
injectableRegistry), injectableRegistry),
new NgModuleDecoratorHandler( new NgModuleDecoratorHandler(
reflector, evaluator, metaReader, metaRegistry, scopeRegistry, referencesRegistry, isCore, reflector, evaluator, metaReader, metaRegistry, scopeRegistry, referencesRegistry, isCore,
routeAnalyzer, refEmitter, this.host.factoryTracker, defaultImportTracker, routeAnalyzer, refEmitter, this.adapter.factoryTracker, defaultImportTracker,
this.closureCompilerEnabled, injectableRegistry, this.options.i18nInLocale), this.closureCompilerEnabled, injectableRegistry, this.options.i18nInLocale),
]; ];
@ -731,7 +729,7 @@ export class NgCompiler {
const templateTypeChecker = new TemplateTypeChecker( const templateTypeChecker = new TemplateTypeChecker(
this.tsProgram, this.typeCheckingProgramStrategy, traitCompiler, this.tsProgram, this.typeCheckingProgramStrategy, traitCompiler,
this.getTypeCheckingConfig(), refEmitter, reflector, this.host, this.incrementalDriver); this.getTypeCheckingConfig(), refEmitter, reflector, this.adapter, this.incrementalDriver);
return { return {
isCore, isCore,

View File

@ -11,12 +11,12 @@ import * as ts from 'typescript';
import {ErrorCode, ngErrorCode} from '../../diagnostics'; import {ErrorCode, ngErrorCode} from '../../diagnostics';
import {findFlatIndexEntryPoint, FlatIndexGenerator} from '../../entry_point'; import {findFlatIndexEntryPoint, FlatIndexGenerator} from '../../entry_point';
import {AbsoluteFsPath, resolve} from '../../file_system'; import {AbsoluteFsPath, resolve} from '../../file_system';
import {FactoryGenerator, FactoryTracker, isShim, ShimAdapter, ShimReferenceTagger, SummaryGenerator} from '../../shims'; import {FactoryGenerator, isShim, ShimAdapter, ShimReferenceTagger, SummaryGenerator} from '../../shims';
import {PerFileShimGenerator, TopLevelShimGenerator} from '../../shims/api'; import {FactoryTracker, PerFileShimGenerator, TopLevelShimGenerator} from '../../shims/api';
import {TypeCheckShimGenerator} from '../../typecheck'; import {TypeCheckShimGenerator} from '../../typecheck';
import {normalizeSeparators} from '../../util/src/path'; import {normalizeSeparators} from '../../util/src/path';
import {getRootDirs, isDtsPath, isNonDeclarationTsPath} from '../../util/src/typescript'; import {getRootDirs, isDtsPath, isNonDeclarationTsPath} from '../../util/src/typescript';
import {ExtendedTsCompilerHost, NgCompilerOptions, UnifiedModulesHost} from '../api'; import {ExtendedTsCompilerHost, NgCompilerAdapter, NgCompilerOptions, UnifiedModulesHost} from '../api';
// A persistent source of bugs in CompilerHost delegation has been the addition by TS of new, // A persistent source of bugs in CompilerHost delegation has been the addition by TS of new,
// optional methods on ts.CompilerHost. Since these methods are optional, it's not a type error that // optional methods on ts.CompilerHost. Since these methods are optional, it's not a type error that
@ -89,10 +89,10 @@ export class DelegatingCompilerHost implements
* `ExtendedTsCompilerHost` methods whenever present. * `ExtendedTsCompilerHost` methods whenever present.
*/ */
export class NgCompilerHost extends DelegatingCompilerHost implements export class NgCompilerHost extends DelegatingCompilerHost implements
RequiredCompilerHostDelegations, ExtendedTsCompilerHost { RequiredCompilerHostDelegations, ExtendedTsCompilerHost, NgCompilerAdapter {
readonly factoryTracker: FactoryTracker|null = null; readonly factoryTracker: FactoryTracker|null = null;
readonly entryPoint: AbsoluteFsPath|null = null; readonly entryPoint: AbsoluteFsPath|null = null;
readonly diagnostics: ts.Diagnostic[]; readonly constructionDiagnostics: ts.Diagnostic[];
readonly inputFiles: ReadonlyArray<string>; readonly inputFiles: ReadonlyArray<string>;
readonly rootDirs: ReadonlyArray<AbsoluteFsPath>; readonly rootDirs: ReadonlyArray<AbsoluteFsPath>;
@ -107,7 +107,7 @@ export class NgCompilerHost extends DelegatingCompilerHost implements
this.factoryTracker = factoryTracker; this.factoryTracker = factoryTracker;
this.entryPoint = entryPoint; this.entryPoint = entryPoint;
this.diagnostics = diagnostics; this.constructionDiagnostics = diagnostics;
this.inputFiles = [...inputFiles, ...shimAdapter.extraInputFiles]; this.inputFiles = [...inputFiles, ...shimAdapter.extraInputFiles];
this.rootDirs = rootDirs; this.rootDirs = rootDirs;
} }

View File

@ -11,7 +11,7 @@ ts_library(
deps = [ deps = [
"//packages/compiler-cli/src/ngtsc/diagnostics", "//packages/compiler-cli/src/ngtsc/diagnostics",
"//packages/compiler-cli/src/ngtsc/file_system", "//packages/compiler-cli/src/ngtsc/file_system",
"//packages/compiler-cli/src/ngtsc/shims", "//packages/compiler-cli/src/ngtsc/shims:api",
"//packages/compiler-cli/src/ngtsc/util", "//packages/compiler-cli/src/ngtsc/util",
"@npm//@types/node", "@npm//@types/node",
"@npm//typescript", "@npm//typescript",

View File

@ -11,7 +11,7 @@
import * as ts from 'typescript'; import * as ts from 'typescript';
import {AbsoluteFsPath, dirname, join} from '../../file_system'; import {AbsoluteFsPath, dirname, join} from '../../file_system';
import {TopLevelShimGenerator} from '../../shims'; import {TopLevelShimGenerator} from '../../shims/api';
import {relativePathBetween} from '../../util/src/path'; import {relativePathBetween} from '../../util/src/path';
export class FlatIndexGenerator implements TopLevelShimGenerator { export class FlatIndexGenerator implements TopLevelShimGenerator {

View File

@ -59,7 +59,9 @@ export class LogicalFileSystem {
*/ */
private cache: Map<AbsoluteFsPath, LogicalProjectPath|null> = new Map(); private cache: Map<AbsoluteFsPath, LogicalProjectPath|null> = new Map();
constructor(rootDirs: AbsoluteFsPath[], private compilerHost: ts.CompilerHost) { constructor(
rootDirs: AbsoluteFsPath[],
private compilerHost: Pick<ts.CompilerHost, 'getCanonicalFileName'>) {
// Make a copy and sort it by length in reverse order (longest first). This speeds up lookups, // Make a copy and sort it by length in reverse order (longest first). This speeds up lookups,
// since there's no need to keep going through the array once a match is found. // since there's no need to keep going through the array once a match is found.
this.rootDirs = rootDirs.concat([]).sort((a, b) => b.length - a.length); this.rootDirs = rootDirs.concat([]).sort((a, b) => b.length - a.length);

View File

@ -6,6 +6,7 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import * as ts from 'typescript'; import * as ts from 'typescript';
import {absoluteFrom} from '../../file_system'; import {absoluteFrom} from '../../file_system';
import {getSourceFileOrNull, resolveModuleName} from '../../util/src/typescript'; import {getSourceFileOrNull, resolveModuleName} from '../../util/src/typescript';
@ -18,8 +19,8 @@ import {getSourceFileOrNull, resolveModuleName} from '../../util/src/typescript'
export class ModuleResolver { export class ModuleResolver {
constructor( constructor(
private program: ts.Program, private compilerOptions: ts.CompilerOptions, private program: ts.Program, private compilerOptions: ts.CompilerOptions,
private host: ts.CompilerHost, private moduleResolutionCache: ts.ModuleResolutionCache|null) { private host: ts.ModuleResolutionHost&Pick<ts.CompilerHost, 'resolveModuleNames'>,
} private moduleResolutionCache: ts.ModuleResolutionCache|null) {}
resolveModule(moduleName: string, containingFile: string): ts.SourceFile|null { resolveModule(moduleName: string, containingFile: string): ts.SourceFile|null {
const resolved = resolveModuleName( const resolved = resolveModuleName(

View File

@ -6,4 +6,4 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
export {HostResourceLoader} from './src/loader'; export {AdapterResourceLoader} from './src/loader';

View File

@ -9,26 +9,21 @@
import * as ts from 'typescript'; import * as ts from 'typescript';
import {ResourceLoader} from '../../annotations'; import {ResourceLoader} from '../../annotations';
import {ExtendedTsCompilerHost} from '../../core/api'; import {NgCompilerAdapter} from '../../core/api';
import {AbsoluteFsPath, join, PathSegment} from '../../file_system'; import {AbsoluteFsPath, join, PathSegment} from '../../file_system';
import {getRootDirs} from '../../util/src/typescript';
const CSS_PREPROCESSOR_EXT = /(\.scss|\.sass|\.less|\.styl)$/; const CSS_PREPROCESSOR_EXT = /(\.scss|\.sass|\.less|\.styl)$/;
/** /**
* `ResourceLoader` which delegates to a `CompilerHost` resource loading method. * `ResourceLoader` which delegates to an `NgCompilerAdapter`'s resource loading methods.
*/ */
export class HostResourceLoader implements ResourceLoader { export class AdapterResourceLoader implements ResourceLoader {
private cache = new Map<string, string>(); private cache = new Map<string, string>();
private fetching = new Map<string, Promise<void>>(); private fetching = new Map<string, Promise<void>>();
private rootDirs: AbsoluteFsPath[]; canPreload = !!this.adapter.readResource;
canPreload = !!this.host.readResource; constructor(private adapter: NgCompilerAdapter, private options: ts.CompilerOptions) {}
constructor(private host: ExtendedTsCompilerHost, private options: ts.CompilerOptions) {
this.rootDirs = getRootDirs(host, options);
}
/** /**
* Resolve the url of a resource relative to the file that contains the reference to it. * Resolve the url of a resource relative to the file that contains the reference to it.
@ -44,8 +39,8 @@ export class HostResourceLoader implements ResourceLoader {
*/ */
resolve(url: string, fromFile: string): string { resolve(url: string, fromFile: string): string {
let resolvedUrl: string|null = null; let resolvedUrl: string|null = null;
if (this.host.resourceNameToFileName) { if (this.adapter.resourceNameToFileName) {
resolvedUrl = this.host.resourceNameToFileName(url, fromFile); resolvedUrl = this.adapter.resourceNameToFileName(url, fromFile);
} else { } else {
resolvedUrl = this.fallbackResolve(url, fromFile); resolvedUrl = this.fallbackResolve(url, fromFile);
} }
@ -67,7 +62,7 @@ export class HostResourceLoader implements ResourceLoader {
* @throws An Error if pre-loading is not available. * @throws An Error if pre-loading is not available.
*/ */
preload(resolvedUrl: string): Promise<void>|undefined { preload(resolvedUrl: string): Promise<void>|undefined {
if (!this.host.readResource) { if (!this.adapter.readResource) {
throw new Error( throw new Error(
'HostResourceLoader: the CompilerHost provided does not support pre-loading resources.'); 'HostResourceLoader: the CompilerHost provided does not support pre-loading resources.');
} }
@ -77,7 +72,7 @@ export class HostResourceLoader implements ResourceLoader {
return this.fetching.get(resolvedUrl); return this.fetching.get(resolvedUrl);
} }
const result = this.host.readResource(resolvedUrl); const result = this.adapter.readResource(resolvedUrl);
if (typeof result === 'string') { if (typeof result === 'string') {
this.cache.set(resolvedUrl, result); this.cache.set(resolvedUrl, result);
return undefined; return undefined;
@ -104,8 +99,8 @@ export class HostResourceLoader implements ResourceLoader {
return this.cache.get(resolvedUrl)!; return this.cache.get(resolvedUrl)!;
} }
const result = this.host.readResource ? this.host.readResource(resolvedUrl) : const result = this.adapter.readResource ? this.adapter.readResource(resolvedUrl) :
this.host.readFile(resolvedUrl); this.adapter.readFile(resolvedUrl);
if (typeof result !== 'string') { if (typeof result !== 'string') {
throw new Error(`HostResourceLoader: loader(${resolvedUrl}) returned a Promise`); throw new Error(`HostResourceLoader: loader(${resolvedUrl}) returned a Promise`);
} }
@ -134,7 +129,7 @@ export class HostResourceLoader implements ResourceLoader {
} }
for (const candidate of candidateLocations) { for (const candidate of candidateLocations) {
if (this.host.fileExists(candidate)) { if (this.adapter.fileExists(candidate)) {
return candidate; return candidate;
} else if (CSS_PREPROCESSOR_EXT.test(candidate)) { } else if (CSS_PREPROCESSOR_EXT.test(candidate)) {
/** /**
@ -143,7 +138,7 @@ export class HostResourceLoader implements ResourceLoader {
* again. * again.
*/ */
const cssFallbackUrl = candidate.replace(CSS_PREPROCESSOR_EXT, '.css'); const cssFallbackUrl = candidate.replace(CSS_PREPROCESSOR_EXT, '.css');
if (this.host.fileExists(cssFallbackUrl)) { if (this.adapter.fileExists(cssFallbackUrl)) {
return cssFallbackUrl; return cssFallbackUrl;
} }
} }
@ -154,7 +149,7 @@ export class HostResourceLoader implements ResourceLoader {
private getRootedCandidateLocations(url: string): AbsoluteFsPath[] { private getRootedCandidateLocations(url: string): AbsoluteFsPath[] {
// The path already starts with '/', so add a '.' to make it relative. // The path already starts with '/', so add a '.' to make it relative.
const segment: PathSegment = ('.' + url) as PathSegment; const segment: PathSegment = ('.' + url) as PathSegment;
return this.rootDirs.map(rootDir => join(rootDir, segment)); return this.adapter.rootDirs.map(rootDir => join(rootDir, segment));
} }
/** /**
@ -172,7 +167,7 @@ export class HostResourceLoader implements ResourceLoader {
ts.ResolvedModuleWithFailedLookupLocations&{failedLookupLocations: ReadonlyArray<string>}; ts.ResolvedModuleWithFailedLookupLocations&{failedLookupLocations: ReadonlyArray<string>};
// clang-format off // clang-format off
const failedLookup = ts.resolveModuleName(url + '.$ngresource$', fromFile, this.options, this.host) as ResolvedModuleWithFailedLookupLocations; const failedLookup = ts.resolveModuleName(url + '.$ngresource$', fromFile, this.options, this.adapter) as ResolvedModuleWithFailedLookupLocations;
// clang-format on // clang-format on
if (failedLookup.failedLookupLocations === undefined) { if (failedLookup.failedLookupLocations === undefined) {
throw new Error( throw new Error(

View File

@ -49,3 +49,22 @@ export interface PerFileShimGenerator {
sf: ts.SourceFile, genFilePath: AbsoluteFsPath, sf: ts.SourceFile, genFilePath: AbsoluteFsPath,
priorShimSf: ts.SourceFile|null): ts.SourceFile; priorShimSf: ts.SourceFile|null): ts.SourceFile;
} }
/**
* Maintains a mapping of which symbols in a .ngfactory file have been used.
*
* .ngfactory files are generated with one symbol per defined class in the source file, regardless
* of whether the classes in the source files are NgModules (because that isn't known at the time
* the factory files are generated). A `FactoryTracker` supports removing factory symbols which
* didn't end up being NgModules, by tracking the ones which are.
*/
export interface FactoryTracker {
readonly sourceInfo: Map<string, FactoryInfo>;
track(sf: ts.SourceFile, factorySymbolName: string): void;
}
export interface FactoryInfo {
sourceFilePath: string;
moduleSymbolNames: Set<string>;
}

View File

@ -8,9 +8,8 @@
/// <reference types="node" /> /// <reference types="node" />
export {PerFileShimGenerator, TopLevelShimGenerator} from './api';
export {ShimAdapter} from './src/adapter'; export {ShimAdapter} from './src/adapter';
export {copyFileShimData, isShim} from './src/expando'; export {copyFileShimData, isShim} from './src/expando';
export {FactoryGenerator, FactoryInfo, FactoryTracker, generatedFactoryTransform} from './src/factory_generator'; export {FactoryGenerator, generatedFactoryTransform} from './src/factory_generator';
export {ShimReferenceTagger} from './src/reference_tagger'; export {ShimReferenceTagger} from './src/reference_tagger';
export {SummaryGenerator} from './src/summary_generator'; export {SummaryGenerator} from './src/summary_generator';

View File

@ -9,27 +9,13 @@ import * as ts from 'typescript';
import {absoluteFromSourceFile, AbsoluteFsPath, basename} from '../../file_system'; import {absoluteFromSourceFile, AbsoluteFsPath, basename} from '../../file_system';
import {ImportRewriter} from '../../imports'; import {ImportRewriter} from '../../imports';
import {PerFileShimGenerator} from '../api'; import {FactoryInfo, FactoryTracker, PerFileShimGenerator} from '../api';
import {generatedModuleName} from './util'; import {generatedModuleName} from './util';
const TS_DTS_SUFFIX = /(\.d)?\.ts$/; const TS_DTS_SUFFIX = /(\.d)?\.ts$/;
const STRIP_NG_FACTORY = /(.*)NgFactory$/; const STRIP_NG_FACTORY = /(.*)NgFactory$/;
/**
* Maintains a mapping of which symbols in a .ngfactory file have been used.
*
* .ngfactory files are generated with one symbol per defined class in the source file, regardless
* of whether the classes in the source files are NgModules (because that isn't known at the time
* the factory files are generated). A `FactoryTracker` supports removing factory symbols which
* didn't end up being NgModules, by tracking the ones which are.
*/
export interface FactoryTracker {
readonly sourceInfo: Map<string, FactoryInfo>;
track(sf: ts.SourceFile, factorySymbolName: string): void;
}
/** /**
* Generates ts.SourceFiles which contain variable declarations for NgFactories for every exported * Generates ts.SourceFiles which contain variable declarations for NgFactories for every exported
* class of an input ts.SourceFile. * class of an input ts.SourceFile.
@ -118,11 +104,6 @@ function isExported(decl: ts.Declaration): boolean {
decl.modifiers.some(mod => mod.kind == ts.SyntaxKind.ExportKeyword); decl.modifiers.some(mod => mod.kind == ts.SyntaxKind.ExportKeyword);
} }
export interface FactoryInfo {
sourceFilePath: string;
moduleSymbolNames: Set<string>;
}
export function generatedFactoryTransform( export function generatedFactoryTransform(
factoryMap: Map<string, FactoryInfo>, factoryMap: Map<string, FactoryInfo>,
importRewriter: ImportRewriter): ts.TransformerFactory<ts.SourceFile> { importRewriter: ImportRewriter): ts.TransformerFactory<ts.SourceFile> {

View File

@ -39,7 +39,7 @@ export class TemplateTypeChecker {
private typeCheckingStrategy: TypeCheckingProgramStrategy, private typeCheckingStrategy: TypeCheckingProgramStrategy,
private typeCheckAdapter: ProgramTypeCheckAdapter, private config: TypeCheckingConfig, private typeCheckAdapter: ProgramTypeCheckAdapter, private config: TypeCheckingConfig,
private refEmitter: ReferenceEmitter, private reflector: ReflectionHost, private refEmitter: ReferenceEmitter, private reflector: ReflectionHost,
private compilerHost: ts.CompilerHost, private compilerHost: Pick<ts.CompilerHost, 'getCanonicalFileName'>,
private priorBuild: IncrementalBuild<unknown, FileTypeCheckingData>) {} private priorBuild: IncrementalBuild<unknown, FileTypeCheckingData>) {}
/** /**

View File

@ -113,7 +113,8 @@ export class TypeCheckContext {
private fileMap = new Map<AbsoluteFsPath, PendingFileTypeCheckingData>(); private fileMap = new Map<AbsoluteFsPath, PendingFileTypeCheckingData>();
constructor( constructor(
private config: TypeCheckingConfig, private compilerHost: ts.CompilerHost, private config: TypeCheckingConfig,
private compilerHost: Pick<ts.CompilerHost, 'getCanonicalFileName'>,
private refEmitter: ReferenceEmitter, private reflector: ReflectionHost) {} private refEmitter: ReferenceEmitter, private reflector: ReflectionHost) {}
/** /**

View File

@ -34,7 +34,7 @@ export class TypeCheckFile extends Environment {
constructor( constructor(
readonly fileName: AbsoluteFsPath, config: TypeCheckingConfig, refEmitter: ReferenceEmitter, readonly fileName: AbsoluteFsPath, config: TypeCheckingConfig, refEmitter: ReferenceEmitter,
reflector: ReflectionHost, compilerHost: ts.CompilerHost) { reflector: ReflectionHost, compilerHost: Pick<ts.CompilerHost, 'getCanonicalFileName'>) {
super( super(
config, new ImportManager(new NoopImportRewriter(), 'i'), refEmitter, reflector, config, new ImportManager(new NoopImportRewriter(), 'i'), refEmitter, reflector,
ts.createSourceFile( ts.createSourceFile(

View File

@ -122,7 +122,7 @@ export function nodeDebugInfo(node: ts.Node): string {
*/ */
export function resolveModuleName( export function resolveModuleName(
moduleName: string, containingFile: string, compilerOptions: ts.CompilerOptions, moduleName: string, containingFile: string, compilerOptions: ts.CompilerOptions,
compilerHost: ts.CompilerHost, compilerHost: ts.ModuleResolutionHost&Pick<ts.CompilerHost, 'resolveModuleNames'>,
moduleResolutionCache: ts.ModuleResolutionCache|null): ts.ResolvedModule|undefined { moduleResolutionCache: ts.ModuleResolutionCache|null): ts.ResolvedModule|undefined {
if (compilerHost.resolveModuleNames) { if (compilerHost.resolveModuleNames) {
// FIXME: Additional parameters are required in TS3.6, but ignored in 3.5. // FIXME: Additional parameters are required in TS3.6, but ignored in 3.5.