Skip to content

Commit e648a0c

Browse files
alxhubatscott
authored andcommitted
refactor(compiler-cli): extract NgCompilerAdapter interface (angular#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 angular#37118
1 parent 965a688 commit e648a0c

File tree

20 files changed

+185
-88
lines changed

20 files changed

+185
-88
lines changed

packages/compiler-cli/src/ngtsc/annotations/BUILD.bazel

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ ts_library(
2020
"//packages/compiler-cli/src/ngtsc/reflection",
2121
"//packages/compiler-cli/src/ngtsc/routing",
2222
"//packages/compiler-cli/src/ngtsc/scope",
23-
"//packages/compiler-cli/src/ngtsc/shims",
23+
"//packages/compiler-cli/src/ngtsc/shims:api",
2424
"//packages/compiler-cli/src/ngtsc/transform",
2525
"//packages/compiler-cli/src/ngtsc/typecheck",
2626
"//packages/compiler-cli/src/ngtsc/util",

packages/compiler-cli/src/ngtsc/annotations/src/ng_module.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import {PartialEvaluator, ResolvedValue, ResolvedValueArray} from '../../partial
1616
import {ClassDeclaration, Decorator, ReflectionHost, reflectObjectLiteral, typeNodeToValueExpr} from '../../reflection';
1717
import {NgModuleRouteAnalyzer} from '../../routing';
1818
import {LocalModuleScopeRegistry, ScopeData} from '../../scope';
19-
import {FactoryTracker} from '../../shims';
19+
import {FactoryTracker} from '../../shims/api';
2020
import {AnalysisOutput, CompileResult, DecoratorHandler, DetectResult, HandlerPrecedence, ResolveResult} from '../../transform';
2121
import {getSourceFile} from '../../util/src/typescript';
2222

packages/compiler-cli/src/ngtsc/core/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ ts_library(
4343
srcs = glob(["api/**/*.ts"]),
4444
deps = [
4545
"//packages/compiler-cli/src/ngtsc/file_system",
46+
"//packages/compiler-cli/src/ngtsc/shims:api",
4647
"@npm//typescript",
4748
],
4849
)

packages/compiler-cli/src/ngtsc/core/api/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9+
export * from './src/adapter';
910
export * from './src/interfaces';
1011
export * from './src/options';
1112
export * from './src/public_options';
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
/**
2+
* @license
3+
* Copyright Google Inc. All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
import * as ts from 'typescript';
10+
11+
import {AbsoluteFsPath} from '../../../file_system';
12+
import {FactoryTracker} from '../../../shims/api';
13+
14+
import {ExtendedTsCompilerHost, UnifiedModulesHost} from './interfaces';
15+
16+
/**
17+
* Names of methods from `ExtendedTsCompilerHost` that need to be provided by the
18+
* `NgCompilerAdapter`.
19+
*/
20+
export type ExtendedCompilerHostMethods =
21+
// Used to normalize filenames for the host system. Important for proper case-sensitive file
22+
// handling.
23+
'getCanonicalFileName'|
24+
// An optional method of `ts.CompilerHost` where an implementer can override module resolution.
25+
'resolveModuleNames'|
26+
// Retrieve the current working directory. Unlike in `ts.ModuleResolutionHost`, this is a
27+
// required method.
28+
'getCurrentDirectory'|
29+
// Additional methods of `ExtendedTsCompilerHost` related to resource files (e.g. HTML
30+
// templates). These are optional.
31+
'getModifiedResourceFiles'|'readResource'|'resourceNameToFileName';
32+
33+
/**
34+
* Adapter for `NgCompiler` that allows it to be used in various circumstances, such as
35+
* command-line `ngc`, as a plugin to `ts_library` in Bazel, or from the Language Service.
36+
*
37+
* `NgCompilerAdapter` is a subset of the `NgCompilerHost` implementation of `ts.CompilerHost`
38+
* which is relied upon by `NgCompiler`. A consumer of `NgCompiler` can therefore use the
39+
* `NgCompilerHost` or implement `NgCompilerAdapter` itself.
40+
*/
41+
export interface NgCompilerAdapter extends
42+
// getCurrentDirectory is removed from `ts.ModuleResolutionHost` because it's optional, and
43+
// incompatible with the `ts.CompilerHost` version which isn't. The combination of these two
44+
// still satisfies `ts.ModuleResolutionHost`.
45+
Omit<ts.ModuleResolutionHost, 'getCurrentDirectory'>,
46+
Pick<ExtendedTsCompilerHost, 'getCurrentDirectory'|ExtendedCompilerHostMethods> {
47+
/**
48+
* A path to a single file which represents the entrypoint of an Angular Package Format library,
49+
* if the current program is one.
50+
*
51+
* This is used to emit a flat module index if requested, and can be left `null` if that is not
52+
* required.
53+
*/
54+
readonly entryPoint: AbsoluteFsPath|null;
55+
56+
/**
57+
* An array of `ts.Diagnostic`s that occurred during construction of the `ts.Program`.
58+
*/
59+
readonly constructionDiagnostics: ts.Diagnostic[];
60+
61+
/**
62+
* A `Set` of `ts.SourceFile`s which are internal to the program and should not be emitted as JS
63+
* files.
64+
*
65+
* Often these are shim files such as `ngtypecheck` shims used for template type-checking in
66+
* command-line ngc.
67+
*/
68+
readonly ignoreForEmit: Set<ts.SourceFile>;
69+
70+
/**
71+
* A tracker for usage of symbols in `.ngfactory` shims.
72+
*
73+
* This can be left `null` if such shims are not a part of the `ts.Program`.
74+
*/
75+
readonly factoryTracker: FactoryTracker|null;
76+
77+
/**
78+
* A specialized interface provided in some environments (such as Bazel) which overrides how
79+
* import specifiers are generated.
80+
*
81+
* If not required, this can be `null`.
82+
*/
83+
readonly unifiedModulesHost: UnifiedModulesHost|null;
84+
85+
/**
86+
* Resolved list of root directories explicitly set in, or inferred from, the tsconfig.
87+
*/
88+
readonly rootDirs: ReadonlyArray<AbsoluteFsPath>;
89+
90+
/**
91+
* Distinguishes between shim files added by Angular to the compilation process (both those
92+
* intended for output, like ngfactory files, as well as internal shims like ngtypecheck files)
93+
* and original files in the user's program.
94+
*
95+
* This is mostly used to limit type-checking operations to only user files. It should return
96+
* `true` if a file was written by the user, and `false` if a file was added by the compiler.
97+
*/
98+
isShim(sf: ts.SourceFile): boolean;
99+
}

packages/compiler-cli/src/ngtsc/core/src/compiler.ts

Lines changed: 26 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -22,19 +22,15 @@ import {ModuleWithProvidersScanner} from '../../modulewithproviders';
2222
import {PartialEvaluator} from '../../partial_evaluator';
2323
import {NOOP_PERF_RECORDER, PerfRecorder} from '../../perf';
2424
import {TypeScriptReflectionHost} from '../../reflection';
25-
import {HostResourceLoader} from '../../resource';
25+
import {AdapterResourceLoader} from '../../resource';
2626
import {entryPointKeyFor, NgModuleRouteAnalyzer} from '../../routing';
2727
import {ComponentScopeReader, LocalModuleScopeRegistry, MetadataDtsModuleScopeResolver} from '../../scope';
2828
import {generatedFactoryTransform} from '../../shims';
2929
import {ivySwitchTransform} from '../../switch';
3030
import {aliasTransformFactory, declarationTransformFactory, DecoratorHandler, DtsTransformRegistry, ivyTransformFactory, TraitCompiler} from '../../transform';
3131
import {isTemplateDiagnostic, TemplateTypeChecker, TypeCheckContext, TypeCheckingConfig, TypeCheckingProgramStrategy} from '../../typecheck';
3232
import {getSourceFileOrNull, isDtsPath, resolveModuleName} from '../../util/src/typescript';
33-
import {LazyRoute, NgCompilerOptions} from '../api';
34-
35-
import {NgCompilerHost} from './host';
36-
37-
33+
import {LazyRoute, NgCompilerAdapter, NgCompilerOptions} from '../api';
3834

3935
/**
4036
* State information about a compilation which is only generated once some data is requested from
@@ -94,18 +90,18 @@ export class NgCompiler {
9490
private nextProgram: ts.Program;
9591
private entryPoint: ts.SourceFile|null;
9692
private moduleResolver: ModuleResolver;
97-
private resourceManager: HostResourceLoader;
93+
private resourceManager: AdapterResourceLoader;
9894
private cycleAnalyzer: CycleAnalyzer;
9995
readonly incrementalDriver: IncrementalDriver;
10096
readonly ignoreForDiagnostics: Set<ts.SourceFile>;
10197
readonly ignoreForEmit: Set<ts.SourceFile>;
10298

10399
constructor(
104-
private host: NgCompilerHost, private options: NgCompilerOptions,
100+
private adapter: NgCompilerAdapter, private options: NgCompilerOptions,
105101
private tsProgram: ts.Program,
106102
private typeCheckingProgramStrategy: TypeCheckingProgramStrategy,
107103
oldProgram: ts.Program|null = null, private perfRecorder: PerfRecorder = NOOP_PERF_RECORDER) {
108-
this.constructionDiagnostics.push(...this.host.diagnostics);
104+
this.constructionDiagnostics.push(...this.adapter.constructionDiagnostics);
109105
const incompatibleTypeCheckOptionsDiagnostic = verifyCompatibleTypeCheckOptions(this.options);
110106
if (incompatibleTypeCheckOptionsDiagnostic !== null) {
111107
this.constructionDiagnostics.push(incompatibleTypeCheckOptionsDiagnostic);
@@ -115,18 +111,19 @@ export class NgCompiler {
115111
this.closureCompilerEnabled = !!this.options.annotateForClosureCompiler;
116112

117113
this.entryPoint =
118-
host.entryPoint !== null ? getSourceFileOrNull(tsProgram, host.entryPoint) : null;
114+
adapter.entryPoint !== null ? getSourceFileOrNull(tsProgram, adapter.entryPoint) : null;
119115

120116
const moduleResolutionCache = ts.createModuleResolutionCache(
121-
this.host.getCurrentDirectory(), fileName => this.host.getCanonicalFileName(fileName));
117+
this.adapter.getCurrentDirectory(),
118+
fileName => this.adapter.getCanonicalFileName(fileName));
122119
this.moduleResolver =
123-
new ModuleResolver(tsProgram, this.options, this.host, moduleResolutionCache);
124-
this.resourceManager = new HostResourceLoader(host, this.options);
120+
new ModuleResolver(tsProgram, this.options, this.adapter, moduleResolutionCache);
121+
this.resourceManager = new AdapterResourceLoader(adapter, this.options);
125122
this.cycleAnalyzer = new CycleAnalyzer(new ImportGraph(this.moduleResolver));
126123

127124
let modifiedResourceFiles: Set<string>|null = null;
128-
if (this.host.getModifiedResourceFiles !== undefined) {
129-
modifiedResourceFiles = this.host.getModifiedResourceFiles() || null;
125+
if (this.adapter.getModifiedResourceFiles !== undefined) {
126+
modifiedResourceFiles = this.adapter.getModifiedResourceFiles() || null;
130127
}
131128

132129
if (oldProgram === null) {
@@ -146,9 +143,9 @@ export class NgCompiler {
146143
setIncrementalDriver(tsProgram, this.incrementalDriver);
147144

148145
this.ignoreForDiagnostics =
149-
new Set(tsProgram.getSourceFiles().filter(sf => this.host.isShim(sf)));
146+
new Set(tsProgram.getSourceFiles().filter(sf => this.adapter.isShim(sf)));
150147

151-
this.ignoreForEmit = this.host.ignoreForEmit;
148+
this.ignoreForEmit = this.adapter.ignoreForEmit;
152149
}
153150

154151
/**
@@ -279,7 +276,7 @@ export class NgCompiler {
279276
const containingFile = this.tsProgram.getRootFileNames()[0];
280277
const [entryPath, moduleName] = entryRoute.split('#');
281278
const resolvedModule =
282-
resolveModuleName(entryPath, containingFile, this.options, this.host, null);
279+
resolveModuleName(entryPath, containingFile, this.options, this.adapter, null);
283280

284281
if (resolvedModule) {
285282
entryRoute = entryPointKeyFor(resolvedModule.resolvedFileName, moduleName);
@@ -326,8 +323,9 @@ export class NgCompiler {
326323
afterDeclarations.push(aliasTransformFactory(compilation.traitCompiler.exportStatements));
327324
}
328325

329-
if (this.host.factoryTracker !== null) {
330-
before.push(generatedFactoryTransform(this.host.factoryTracker.sourceInfo, importRewriter));
326+
if (this.adapter.factoryTracker !== null) {
327+
before.push(
328+
generatedFactoryTransform(this.adapter.factoryTracker.sourceInfo, importRewriter));
331329
}
332330
before.push(ivySwitchTransform);
333331

@@ -499,7 +497,7 @@ export class NgCompiler {
499497
const typeCheckSpan = this.perfRecorder.start('typeCheckDiagnostics');
500498
const diagnostics: ts.Diagnostic[] = [];
501499
for (const sf of this.tsProgram.getSourceFiles()) {
502-
if (sf.isDeclarationFile || this.host.isShim(sf)) {
500+
if (sf.isDeclarationFile || this.adapter.isShim(sf)) {
503501
continue;
504502
}
505503

@@ -600,7 +598,7 @@ export class NgCompiler {
600598
// Construct the ReferenceEmitter.
601599
let refEmitter: ReferenceEmitter;
602600
let aliasingHost: AliasingHost|null = null;
603-
if (this.host.unifiedModulesHost === null || !this.options._useHostForImportGeneration) {
601+
if (this.adapter.unifiedModulesHost === null || !this.options._useHostForImportGeneration) {
604602
let localImportStrategy: ReferenceEmitStrategy;
605603

606604
// The strategy used for local, in-project imports depends on whether TS has been configured
@@ -613,7 +611,7 @@ export class NgCompiler {
613611
// rootDirs logic is in effect - use the `LogicalProjectStrategy` for in-project relative
614612
// imports.
615613
localImportStrategy = new LogicalProjectStrategy(
616-
reflector, new LogicalFileSystem([...this.host.rootDirs], this.host));
614+
reflector, new LogicalFileSystem([...this.adapter.rootDirs], this.adapter));
617615
} else {
618616
// Plain relative imports are all that's needed.
619617
localImportStrategy = new RelativePathStrategy(reflector);
@@ -648,9 +646,9 @@ export class NgCompiler {
648646
// Then use aliased references (this is a workaround to StrictDeps checks).
649647
new AliasStrategy(),
650648
// Then use fileNameToModuleName to emit imports.
651-
new UnifiedModulesStrategy(reflector, this.host.unifiedModulesHost),
649+
new UnifiedModulesStrategy(reflector, this.adapter.unifiedModulesHost),
652650
]);
653-
aliasingHost = new UnifiedModulesAliasingHost(this.host.unifiedModulesHost);
651+
aliasingHost = new UnifiedModulesAliasingHost(this.adapter.unifiedModulesHost);
654652
}
655653

656654
const evaluator = new PartialEvaluator(reflector, checker, this.incrementalDriver.depGraph);
@@ -693,7 +691,7 @@ export class NgCompiler {
693691
const handlers: DecoratorHandler<unknown, unknown, unknown>[] = [
694692
new ComponentDecoratorHandler(
695693
reflector, evaluator, metaRegistry, metaReader, scopeReader, scopeRegistry, isCore,
696-
this.resourceManager, this.host.rootDirs, this.options.preserveWhitespaces || false,
694+
this.resourceManager, this.adapter.rootDirs, this.options.preserveWhitespaces || false,
697695
this.options.i18nUseExternalIds !== false,
698696
this.options.enableI18nLegacyMessageIdFormat !== false,
699697
this.options.i18nNormalizeLineEndingsInICUs, this.moduleResolver, this.cycleAnalyzer,
@@ -721,7 +719,7 @@ export class NgCompiler {
721719
injectableRegistry),
722720
new NgModuleDecoratorHandler(
723721
reflector, evaluator, metaReader, metaRegistry, scopeRegistry, referencesRegistry, isCore,
724-
routeAnalyzer, refEmitter, this.host.factoryTracker, defaultImportTracker,
722+
routeAnalyzer, refEmitter, this.adapter.factoryTracker, defaultImportTracker,
725723
this.closureCompilerEnabled, injectableRegistry, this.options.i18nInLocale),
726724
];
727725

@@ -731,7 +729,7 @@ export class NgCompiler {
731729

732730
const templateTypeChecker = new TemplateTypeChecker(
733731
this.tsProgram, this.typeCheckingProgramStrategy, traitCompiler,
734-
this.getTypeCheckingConfig(), refEmitter, reflector, this.host, this.incrementalDriver);
732+
this.getTypeCheckingConfig(), refEmitter, reflector, this.adapter, this.incrementalDriver);
735733

736734
return {
737735
isCore,

packages/compiler-cli/src/ngtsc/core/src/host.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,12 @@ import * as ts from 'typescript';
1111
import {ErrorCode, ngErrorCode} from '../../diagnostics';
1212
import {findFlatIndexEntryPoint, FlatIndexGenerator} from '../../entry_point';
1313
import {AbsoluteFsPath, resolve} from '../../file_system';
14-
import {FactoryGenerator, FactoryTracker, isShim, ShimAdapter, ShimReferenceTagger, SummaryGenerator} from '../../shims';
15-
import {PerFileShimGenerator, TopLevelShimGenerator} from '../../shims/api';
14+
import {FactoryGenerator, isShim, ShimAdapter, ShimReferenceTagger, SummaryGenerator} from '../../shims';
15+
import {FactoryTracker, PerFileShimGenerator, TopLevelShimGenerator} from '../../shims/api';
1616
import {TypeCheckShimGenerator} from '../../typecheck';
1717
import {normalizeSeparators} from '../../util/src/path';
1818
import {getRootDirs, isDtsPath, isNonDeclarationTsPath} from '../../util/src/typescript';
19-
import {ExtendedTsCompilerHost, NgCompilerOptions, UnifiedModulesHost} from '../api';
19+
import {ExtendedTsCompilerHost, NgCompilerAdapter, NgCompilerOptions, UnifiedModulesHost} from '../api';
2020

2121
// A persistent source of bugs in CompilerHost delegation has been the addition by TS of new,
2222
// 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
8989
* `ExtendedTsCompilerHost` methods whenever present.
9090
*/
9191
export class NgCompilerHost extends DelegatingCompilerHost implements
92-
RequiredCompilerHostDelegations, ExtendedTsCompilerHost {
92+
RequiredCompilerHostDelegations, ExtendedTsCompilerHost, NgCompilerAdapter {
9393
readonly factoryTracker: FactoryTracker|null = null;
9494
readonly entryPoint: AbsoluteFsPath|null = null;
95-
readonly diagnostics: ts.Diagnostic[];
95+
readonly constructionDiagnostics: ts.Diagnostic[];
9696

9797
readonly inputFiles: ReadonlyArray<string>;
9898
readonly rootDirs: ReadonlyArray<AbsoluteFsPath>;
@@ -107,7 +107,7 @@ export class NgCompilerHost extends DelegatingCompilerHost implements
107107

108108
this.factoryTracker = factoryTracker;
109109
this.entryPoint = entryPoint;
110-
this.diagnostics = diagnostics;
110+
this.constructionDiagnostics = diagnostics;
111111
this.inputFiles = [...inputFiles, ...shimAdapter.extraInputFiles];
112112
this.rootDirs = rootDirs;
113113
}

packages/compiler-cli/src/ngtsc/entry_point/BUILD.bazel

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ ts_library(
1111
deps = [
1212
"//packages/compiler-cli/src/ngtsc/diagnostics",
1313
"//packages/compiler-cli/src/ngtsc/file_system",
14-
"//packages/compiler-cli/src/ngtsc/shims",
14+
"//packages/compiler-cli/src/ngtsc/shims:api",
1515
"//packages/compiler-cli/src/ngtsc/util",
1616
"@npm//@types/node",
1717
"@npm//typescript",

packages/compiler-cli/src/ngtsc/entry_point/src/generator.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
import * as ts from 'typescript';
1212

1313
import {AbsoluteFsPath, dirname, join} from '../../file_system';
14-
import {TopLevelShimGenerator} from '../../shims';
14+
import {TopLevelShimGenerator} from '../../shims/api';
1515
import {relativePathBetween} from '../../util/src/path';
1616

1717
export class FlatIndexGenerator implements TopLevelShimGenerator {

packages/compiler-cli/src/ngtsc/file_system/src/logical.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,9 @@ export class LogicalFileSystem {
5959
*/
6060
private cache: Map<AbsoluteFsPath, LogicalProjectPath|null> = new Map();
6161

62-
constructor(rootDirs: AbsoluteFsPath[], private compilerHost: ts.CompilerHost) {
62+
constructor(
63+
rootDirs: AbsoluteFsPath[],
64+
private compilerHost: Pick<ts.CompilerHost, 'getCanonicalFileName'>) {
6365
// Make a copy and sort it by length in reverse order (longest first). This speeds up lookups,
6466
// since there's no need to keep going through the array once a match is found.
6567
this.rootDirs = rootDirs.concat([]).sort((a, b) => b.length - a.length);

0 commit comments

Comments
 (0)