perf(compiler-cli): split Ivy template type-checking into multiple files (#36211)
As a performance optimization, this commit splits the single __ngtypecheck__.ts file which was previously added to the user's program as a container for all template type-checking code into multiple .ngtypecheck shim files, one for each original file in the user's program. In larger applications, the generation, parsing, and checking of this single type-checking file was a huge performance bottleneck, with the file often exceeding 1 MB in text content. Particularly in incremental builds, regenerating this single file for the entire application proved especially expensive. This commit introduces a new strategy for template type-checking code which makes use of a new interface, the `TypeCheckingProgramStrategy`. This interface abstracts the process of creating a new `ts.Program` to type-check a particular compilation, and allows the mechanism there to be kept separate from the more complex logic around dealing with multiple .ngtypecheck files. A new `TemplateTypeChecker` hosts that logic and interacts with the `TypeCheckingProgramStrategy` to actually generate and return diagnostics. The `TypeCheckContext` class, previously the workhorse of template type- checking, is now solely focused on collecting and generating type-checking file contents. A side effect of implementing the new `TypeCheckingProgramStrategy` in this way is that the API is designed to be suitable for use by the Angular Language Service as well. The LS also needs to type-check components, but has its own method for constructing a `ts.Program` with type-checking code. Note that this commit does not make the actual checking of templates at all _incremental_ just yet. That will happen in a future commit. PR Close #36211
This commit is contained in:
parent
4213e8d5f0
commit
b861e9c0ac
|
@ -41,10 +41,6 @@
|
|||
"packages/compiler-cli/src/ngtsc/scope/src/component_scope.ts",
|
||||
"packages/compiler-cli/src/ngtsc/scope/src/local.ts"
|
||||
],
|
||||
[
|
||||
"packages/compiler-cli/src/ngtsc/typecheck/src/context.ts",
|
||||
"packages/compiler-cli/src/ngtsc/typecheck/src/host.ts"
|
||||
],
|
||||
[
|
||||
"packages/compiler-cli/test/helpers/index.ts",
|
||||
"packages/compiler-cli/test/helpers/src/mock_file_loading.ts"
|
||||
|
|
|
@ -29,6 +29,7 @@ ts_library(
|
|||
"//packages/compiler-cli/src/ngtsc/file_system",
|
||||
"//packages/compiler-cli/src/ngtsc/indexer",
|
||||
"//packages/compiler-cli/src/ngtsc/perf",
|
||||
"//packages/compiler-cli/src/ngtsc/typecheck",
|
||||
"@npm//@bazel/typescript",
|
||||
"@npm//@types/node",
|
||||
"@npm//chokidar",
|
||||
|
|
|
@ -42,6 +42,7 @@ ts_library(
|
|||
name = "api",
|
||||
srcs = glob(["api/**/*.ts"]),
|
||||
deps = [
|
||||
"//packages/compiler-cli/src/ngtsc/file_system",
|
||||
"@npm//typescript",
|
||||
],
|
||||
)
|
||||
|
|
|
@ -28,7 +28,7 @@ import {ComponentScopeReader, LocalModuleScopeRegistry, MetadataDtsModuleScopeRe
|
|||
import {generatedFactoryTransform} from '../../shims';
|
||||
import {ivySwitchTransform} from '../../switch';
|
||||
import {aliasTransformFactory, declarationTransformFactory, DecoratorHandler, DtsTransformRegistry, ivyTransformFactory, TraitCompiler} from '../../transform';
|
||||
import {isTemplateDiagnostic, TypeCheckContext, TypeCheckingConfig} from '../../typecheck';
|
||||
import {isTemplateDiagnostic, TemplateTypeChecker, TypeCheckContext, TypeCheckingConfig, TypeCheckingProgramStrategy} from '../../typecheck';
|
||||
import {getSourceFileOrNull, isDtsPath, resolveModuleName} from '../../util/src/typescript';
|
||||
import {LazyRoute, NgCompilerOptions} from '../api';
|
||||
|
||||
|
@ -53,6 +53,7 @@ interface LazyCompilationState {
|
|||
defaultImportTracker: DefaultImportTracker;
|
||||
aliasingHost: AliasingHost|null;
|
||||
refEmitter: ReferenceEmitter;
|
||||
templateTypeChecker: TemplateTypeChecker;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -90,7 +91,6 @@ export class NgCompiler {
|
|||
private diagnostics: ts.Diagnostic[]|null = null;
|
||||
|
||||
private closureCompilerEnabled: boolean;
|
||||
private typeCheckFile: ts.SourceFile;
|
||||
private nextProgram: ts.Program;
|
||||
private entryPoint: ts.SourceFile|null;
|
||||
private moduleResolver: ModuleResolver;
|
||||
|
@ -102,8 +102,9 @@ export class NgCompiler {
|
|||
|
||||
constructor(
|
||||
private host: NgCompilerHost, private options: NgCompilerOptions,
|
||||
private tsProgram: ts.Program, oldProgram: ts.Program|null = null,
|
||||
private perfRecorder: PerfRecorder = NOOP_PERF_RECORDER) {
|
||||
private tsProgram: ts.Program,
|
||||
private typeCheckingProgramStrategy: TypeCheckingProgramStrategy,
|
||||
oldProgram: ts.Program|null = null, private perfRecorder: PerfRecorder = NOOP_PERF_RECORDER) {
|
||||
this.constructionDiagnostics.push(...this.host.diagnostics);
|
||||
const incompatibleTypeCheckOptionsDiagnostic = verifyCompatibleTypeCheckOptions(this.options);
|
||||
if (incompatibleTypeCheckOptionsDiagnostic !== null) {
|
||||
|
@ -116,7 +117,6 @@ export class NgCompiler {
|
|||
this.entryPoint =
|
||||
host.entryPoint !== null ? getSourceFileOrNull(tsProgram, host.entryPoint) : null;
|
||||
|
||||
this.typeCheckFile = getSourceFileOrError(tsProgram, host.typeCheckFile);
|
||||
const moduleResolutionCache = ts.createModuleResolutionCache(
|
||||
this.host.getCurrentDirectory(), fileName => this.host.getCanonicalFileName(fileName));
|
||||
this.moduleResolver =
|
||||
|
@ -380,28 +380,26 @@ export class NgCompiler {
|
|||
this.incrementalDriver.recordSuccessfulAnalysis(traitCompiler);
|
||||
}
|
||||
|
||||
private getTemplateDiagnostics(): ReadonlyArray<ts.Diagnostic> {
|
||||
const host = this.host;
|
||||
|
||||
private get fullTemplateTypeCheck(): boolean {
|
||||
// Determine the strictness level of type checking based on compiler options. As
|
||||
// `strictTemplates` is a superset of `fullTemplateTypeCheck`, the former implies the latter.
|
||||
// Also see `verifyCompatibleTypeCheckOptions` where it is verified that `fullTemplateTypeCheck`
|
||||
// is not disabled when `strictTemplates` is enabled.
|
||||
const strictTemplates = !!this.options.strictTemplates;
|
||||
const fullTemplateTypeCheck = strictTemplates || !!this.options.fullTemplateTypeCheck;
|
||||
return strictTemplates || !!this.options.fullTemplateTypeCheck;
|
||||
}
|
||||
|
||||
// Skip template type-checking if it's disabled.
|
||||
if (this.options.ivyTemplateTypeCheck === false && !fullTemplateTypeCheck) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const compilation = this.ensureAnalyzed();
|
||||
// Run template type-checking.
|
||||
private getTypeCheckingConfig(): TypeCheckingConfig {
|
||||
// Determine the strictness level of type checking based on compiler options. As
|
||||
// `strictTemplates` is a superset of `fullTemplateTypeCheck`, the former implies the latter.
|
||||
// Also see `verifyCompatibleTypeCheckOptions` where it is verified that `fullTemplateTypeCheck`
|
||||
// is not disabled when `strictTemplates` is enabled.
|
||||
const strictTemplates = !!this.options.strictTemplates;
|
||||
|
||||
// First select a type-checking configuration, based on whether full template type-checking is
|
||||
// requested.
|
||||
let typeCheckingConfig: TypeCheckingConfig;
|
||||
if (fullTemplateTypeCheck) {
|
||||
if (this.fullTemplateTypeCheck) {
|
||||
typeCheckingConfig = {
|
||||
applyTemplateContextGuards: strictTemplates,
|
||||
checkQueries: false,
|
||||
|
@ -480,17 +478,36 @@ export class NgCompiler {
|
|||
typeCheckingConfig.strictLiteralTypes = this.options.strictLiteralTypes;
|
||||
}
|
||||
|
||||
return typeCheckingConfig;
|
||||
}
|
||||
|
||||
private getTemplateDiagnostics(): ReadonlyArray<ts.Diagnostic> {
|
||||
const host = this.host;
|
||||
|
||||
// Skip template type-checking if it's disabled.
|
||||
if (this.options.ivyTemplateTypeCheck === false && !this.fullTemplateTypeCheck) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const compilation = this.ensureAnalyzed();
|
||||
|
||||
// Execute the typeCheck phase of each decorator in the program.
|
||||
const prepSpan = this.perfRecorder.start('typeCheckPrep');
|
||||
const ctx = new TypeCheckContext(
|
||||
typeCheckingConfig, compilation.refEmitter!, compilation.reflector, host.typeCheckFile);
|
||||
compilation.traitCompiler.typeCheck(ctx);
|
||||
compilation.templateTypeChecker.refresh();
|
||||
this.perfRecorder.stop(prepSpan);
|
||||
|
||||
// Get the diagnostics.
|
||||
const typeCheckSpan = this.perfRecorder.start('typeCheckDiagnostics');
|
||||
const {diagnostics, program} =
|
||||
ctx.calculateTemplateDiagnostics(this.tsProgram, this.host, this.options);
|
||||
const diagnostics: ts.Diagnostic[] = [];
|
||||
for (const sf of this.tsProgram.getSourceFiles()) {
|
||||
if (sf.isDeclarationFile || this.host.isShim(sf)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
diagnostics.push(...compilation.templateTypeChecker.getDiagnosticsForFile(sf));
|
||||
}
|
||||
|
||||
const program = this.typeCheckingProgramStrategy.getProgram();
|
||||
this.perfRecorder.stop(typeCheckSpan);
|
||||
setIncrementalDriver(program, this.incrementalDriver);
|
||||
this.nextProgram = program;
|
||||
|
@ -709,6 +726,10 @@ export class NgCompiler {
|
|||
handlers, reflector, this.perfRecorder, this.incrementalDriver,
|
||||
this.options.compileNonExportedClasses !== false, dtsTransforms);
|
||||
|
||||
const templateTypeChecker = new TemplateTypeChecker(
|
||||
this.tsProgram, this.typeCheckingProgramStrategy, traitCompiler,
|
||||
this.getTypeCheckingConfig(), refEmitter, reflector);
|
||||
|
||||
return {
|
||||
isCore,
|
||||
traitCompiler,
|
||||
|
@ -722,6 +743,7 @@ export class NgCompiler {
|
|||
defaultImportTracker,
|
||||
aliasingHost,
|
||||
refEmitter,
|
||||
templateTypeChecker,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ import {findFlatIndexEntryPoint, FlatIndexGenerator} from '../../entry_point';
|
|||
import {AbsoluteFsPath, resolve} from '../../file_system';
|
||||
import {FactoryGenerator, FactoryTracker, isShim, ShimAdapter, ShimReferenceTagger, SummaryGenerator} from '../../shims';
|
||||
import {PerFileShimGenerator, TopLevelShimGenerator} from '../../shims/api';
|
||||
import {typeCheckFilePath, TypeCheckShimGenerator} from '../../typecheck';
|
||||
import {TypeCheckShimGenerator} from '../../typecheck';
|
||||
import {normalizeSeparators} from '../../util/src/path';
|
||||
import {getRootDirs, isDtsPath, isNonDeclarationTsPath} from '../../util/src/typescript';
|
||||
import {ExtendedTsCompilerHost, NgCompilerOptions, UnifiedModulesHost} from '../api';
|
||||
|
@ -96,20 +96,17 @@ export class NgCompilerHost extends DelegatingCompilerHost implements
|
|||
|
||||
readonly inputFiles: ReadonlyArray<string>;
|
||||
readonly rootDirs: ReadonlyArray<AbsoluteFsPath>;
|
||||
readonly typeCheckFile: AbsoluteFsPath;
|
||||
|
||||
|
||||
constructor(
|
||||
delegate: ExtendedTsCompilerHost, inputFiles: ReadonlyArray<string>,
|
||||
rootDirs: ReadonlyArray<AbsoluteFsPath>, private shimAdapter: ShimAdapter,
|
||||
private shimTagger: ShimReferenceTagger, entryPoint: AbsoluteFsPath|null,
|
||||
typeCheckFile: AbsoluteFsPath, factoryTracker: FactoryTracker|null,
|
||||
diagnostics: ts.Diagnostic[]) {
|
||||
factoryTracker: FactoryTracker|null, diagnostics: ts.Diagnostic[]) {
|
||||
super(delegate);
|
||||
|
||||
this.factoryTracker = factoryTracker;
|
||||
this.entryPoint = entryPoint;
|
||||
this.typeCheckFile = typeCheckFile;
|
||||
this.diagnostics = diagnostics;
|
||||
this.inputFiles = [...inputFiles, ...shimAdapter.extraInputFiles];
|
||||
this.rootDirs = rootDirs;
|
||||
|
@ -125,6 +122,14 @@ export class NgCompilerHost extends DelegatingCompilerHost implements
|
|||
return this.shimAdapter.ignoreForEmit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the array of shim extension prefixes for which shims were created for each original
|
||||
* file.
|
||||
*/
|
||||
get shimExtensionPrefixes(): string[] {
|
||||
return this.shimAdapter.extensionPrefixes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs cleanup that needs to happen after a `ts.Program` has been created using this host.
|
||||
*/
|
||||
|
@ -169,8 +174,7 @@ export class NgCompilerHost extends DelegatingCompilerHost implements
|
|||
|
||||
const rootDirs = getRootDirs(delegate, options as ts.CompilerOptions);
|
||||
|
||||
const typeCheckFile = typeCheckFilePath(rootDirs);
|
||||
topLevelShimGenerators.push(new TypeCheckShimGenerator(typeCheckFile));
|
||||
perFileShimGenerators.push(new TypeCheckShimGenerator());
|
||||
|
||||
let diagnostics: ts.Diagnostic[] = [];
|
||||
|
||||
|
@ -218,8 +222,8 @@ export class NgCompilerHost extends DelegatingCompilerHost implements
|
|||
const shimTagger =
|
||||
new ShimReferenceTagger(perFileShimGenerators.map(gen => gen.extensionPrefix));
|
||||
return new NgCompilerHost(
|
||||
delegate, inputFiles, rootDirs, shimAdapter, shimTagger, entryPoint, typeCheckFile,
|
||||
factoryTracker, diagnostics);
|
||||
delegate, inputFiles, rootDirs, shimAdapter, shimTagger, entryPoint, factoryTracker,
|
||||
diagnostics);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -14,6 +14,7 @@ ts_library(
|
|||
"//packages/compiler-cli/src/ngtsc/core:api",
|
||||
"//packages/compiler-cli/src/ngtsc/file_system",
|
||||
"//packages/compiler-cli/src/ngtsc/file_system/testing",
|
||||
"//packages/compiler-cli/src/ngtsc/typecheck",
|
||||
"@npm//typescript",
|
||||
],
|
||||
)
|
||||
|
|
|
@ -10,6 +10,7 @@ import * as ts from 'typescript';
|
|||
|
||||
import {absoluteFrom as _, FileSystem, getFileSystem, getSourceFileOrError, NgtscCompilerHost, setFileSystem} from '../../file_system';
|
||||
import {runInEachFileSystem} from '../../file_system/testing';
|
||||
import {ReusedProgramStrategy} from '../../typecheck/src/augmented_program';
|
||||
import {NgCompilerOptions} from '../api';
|
||||
import {NgCompiler} from '../src/compiler';
|
||||
import {NgCompilerHost} from '../src/host';
|
||||
|
@ -45,7 +46,8 @@ runInEachFileSystem(() => {
|
|||
const baseHost = new NgtscCompilerHost(getFileSystem(), options);
|
||||
const host = NgCompilerHost.wrap(baseHost, [COMPONENT], options, /* oldProgram */ null);
|
||||
const program = ts.createProgram({host, options, rootNames: host.inputFiles});
|
||||
const compiler = new NgCompiler(host, options, program);
|
||||
const compiler = new NgCompiler(
|
||||
host, options, program, new ReusedProgramStrategy(program, host, options, []));
|
||||
|
||||
const diags = compiler.getDiagnostics(getSourceFileOrError(program, COMPONENT));
|
||||
expect(diags.length).toBe(1);
|
||||
|
|
|
@ -135,13 +135,13 @@ Currently the compiler does not distinguish these two cases, and conservatively
|
|||
|
||||
## Skipping template type-checking
|
||||
|
||||
For certain kinds of changes, it may be possible to avoid the cost of generating and checking the template type-checking file. Several levels of this can be imagined.
|
||||
For certain kinds of changes, it may be possible to avoid the cost of generating and checking template type-checking files. Several levels of this can be imagined.
|
||||
|
||||
For resource-only changes, only the component(s) which have changed resources need to be re-checked. No other components could be affected, so previously produced diagnostics are still valid.
|
||||
|
||||
For arbitrary source changes, things get a bit more complicated. A change to any .ts file could affect types anywhere in the program (think `declare global ...`). If a set of affected components can be determined (perhaps via the import graph that the cycle analyzer extracts?) and it can be proven that the change does not impact any global types (exactly how to do this is left as an exercise for the reader), then type-checking could be skipped for other components in the mix.
|
||||
|
||||
If the above is too complex, then certain kinds of type changes might allow for the reuse of the text of the template type-checking file, if it can be proven that none of the inputs to its generation have changed. This is useful for two very important reasons.
|
||||
If the above is too complex, then certain kinds of type changes might allow for the reuse of the text of some template type-checking files, if it can be proven that none of the inputs to their generation have changed. This is useful for two very important reasons.
|
||||
|
||||
1) Generating (and subsequently parsing) the template type-checking file itself is expensive.
|
||||
1) Generating (and subsequently parsing) the template type-checking files is expensive.
|
||||
2) Under ideal conditions, after an initial template type-checking program is created, it may be possible to reuse it for emit _and_ type-checking in subsequent builds. This would be a pretty advanced optimization but would save creation of a second `ts.Program` on each valid rebuild.
|
||||
|
|
|
@ -12,11 +12,11 @@ import * as ts from 'typescript';
|
|||
import * as api from '../transformers/api';
|
||||
import {verifySupportedTypeScriptVersion} from '../typescript_support';
|
||||
|
||||
import {NgCompilerHost} from './core';
|
||||
import {NgCompiler, NgCompilerHost} from './core';
|
||||
import {NgCompilerOptions} from './core/api';
|
||||
import {NgCompiler} from './core/src/compiler';
|
||||
import {IndexedComponent} from './indexer';
|
||||
import {NOOP_PERF_RECORDER, PerfRecorder, PerfTracker} from './perf';
|
||||
import {ReusedProgramStrategy} from './typecheck';
|
||||
|
||||
|
||||
|
||||
|
@ -74,9 +74,12 @@ export class NgtscProgram implements api.Program {
|
|||
|
||||
this.host.postProgramCreationCleanup();
|
||||
|
||||
const reusedProgramStrategy = new ReusedProgramStrategy(
|
||||
this.tsProgram, this.host, this.options, this.host.shimExtensionPrefixes);
|
||||
|
||||
// Create the NgCompiler which will drive the rest of the compilation.
|
||||
this.compiler =
|
||||
new NgCompiler(this.host, options, this.tsProgram, reuseProgram, this.perfRecorder);
|
||||
this.compiler = new NgCompiler(
|
||||
this.host, options, this.tsProgram, reusedProgramStrategy, reuseProgram, this.perfRecorder);
|
||||
}
|
||||
|
||||
getTsProgram(): ts.Program {
|
||||
|
|
|
@ -70,6 +70,11 @@ export class ShimAdapter {
|
|||
*/
|
||||
readonly extraInputFiles: ReadonlyArray<AbsoluteFsPath>;
|
||||
|
||||
/**
|
||||
* Extension prefixes of all installed per-file shims.
|
||||
*/
|
||||
readonly extensionPrefixes: string[] = [];
|
||||
|
||||
constructor(
|
||||
private delegate: Pick<ts.CompilerHost, 'getSourceFile'|'fileExists'>,
|
||||
tsRootFiles: AbsoluteFsPath[], topLevelGenerators: TopLevelShimGenerator[],
|
||||
|
@ -86,6 +91,7 @@ export class ShimAdapter {
|
|||
test: regexp,
|
||||
suffix: `.${gen.extensionPrefix}.ts`,
|
||||
});
|
||||
this.extensionPrefixes.push(gen.extensionPrefix);
|
||||
}
|
||||
// Process top-level generators and pre-generate their shims. Accumulate the list of filenames
|
||||
// as extra input files.
|
||||
|
@ -187,7 +193,7 @@ export class ShimAdapter {
|
|||
if (inputFile === undefined || isShim(inputFile)) {
|
||||
// Something strange happened here. This case is also not cached in `notShims`, but this
|
||||
// path is not expected to occur in reality so this shouldn't be a problem.
|
||||
return null;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Actually generate and cache the shim.
|
||||
|
|
|
@ -14,7 +14,7 @@ import {IncrementalBuild} from '../../incremental/api';
|
|||
import {IndexingContext} from '../../indexer';
|
||||
import {PerfRecorder} from '../../perf';
|
||||
import {ClassDeclaration, Decorator, ReflectionHost} from '../../reflection';
|
||||
import {TypeCheckContext} from '../../typecheck';
|
||||
import {ProgramTypeCheckAdapter, TypeCheckContext} from '../../typecheck';
|
||||
import {getSourceFile, isExported} from '../../util/src/typescript';
|
||||
|
||||
import {AnalysisOutput, CompileResult, DecoratorHandler, HandlerFlags, HandlerPrecedence, ResolveResult} from './api';
|
||||
|
@ -67,7 +67,7 @@ export interface ClassRecord {
|
|||
* in the production of `CompileResult`s instructing the compiler to apply various mutations to the
|
||||
* class (like adding fields or type declarations).
|
||||
*/
|
||||
export class TraitCompiler {
|
||||
export class TraitCompiler implements ProgramTypeCheckAdapter {
|
||||
/**
|
||||
* Maps class declarations to their `ClassRecord`, which tracks the Ivy traits being applied to
|
||||
* those classes.
|
||||
|
|
|
@ -12,6 +12,7 @@ import {NgCompiler, NgCompilerHost} from './core';
|
|||
import {NgCompilerOptions, UnifiedModulesHost} from './core/api';
|
||||
import {NodeJSFileSystem, setFileSystem} from './file_system';
|
||||
import {NOOP_PERF_RECORDER} from './perf';
|
||||
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:
|
||||
|
@ -90,8 +91,10 @@ export class NgTscPlugin implements TscPlugin {
|
|||
if (this.host === null || this.options === null) {
|
||||
throw new Error('Lifecycle error: setupCompilation() before wrapHost().');
|
||||
}
|
||||
this._compiler =
|
||||
new NgCompiler(this.host, this.options, program, oldProgram, NOOP_PERF_RECORDER);
|
||||
const typeCheckStrategy = new ReusedProgramStrategy(
|
||||
program, this.host, this.options, this.host.shimExtensionPrefixes);
|
||||
this._compiler = new NgCompiler(
|
||||
this.host, this.options, program, typeCheckStrategy, oldProgram, NOOP_PERF_RECORDER);
|
||||
return {
|
||||
ignoreForDiagnostics: this._compiler.ignoreForDiagnostics,
|
||||
ignoreForEmit: this._compiler.ignoreForEmit,
|
||||
|
|
|
@ -13,6 +13,7 @@ ts_library(
|
|||
"//packages/compiler-cli/src/ngtsc/imports",
|
||||
"//packages/compiler-cli/src/ngtsc/metadata",
|
||||
"//packages/compiler-cli/src/ngtsc/reflection",
|
||||
"//packages/compiler-cli/src/ngtsc/shims",
|
||||
"//packages/compiler-cli/src/ngtsc/shims:api",
|
||||
"//packages/compiler-cli/src/ngtsc/translator",
|
||||
"//packages/compiler-cli/src/ngtsc/util",
|
||||
|
|
|
@ -7,6 +7,8 @@
|
|||
*/
|
||||
|
||||
export * from './src/api';
|
||||
export {ReusedProgramStrategy} from './src/augmented_program';
|
||||
export {TemplateTypeChecker, ProgramTypeCheckAdapter} from './src/checker';
|
||||
export {TypeCheckContext} from './src/context';
|
||||
export {TemplateDiagnostic, isTemplateDiagnostic} from './src/diagnostics';
|
||||
export {TypeCheckShimGenerator} from './src/shim';
|
||||
|
|
|
@ -9,10 +9,12 @@
|
|||
import {BoundTarget, DirectiveMeta, SchemaMetadata} from '@angular/compiler';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {AbsoluteFsPath} from '../../file_system';
|
||||
import {Reference} from '../../imports';
|
||||
import {TemplateGuardMeta} from '../../metadata';
|
||||
import {ClassDeclaration} from '../../reflection';
|
||||
|
||||
|
||||
/**
|
||||
* Extension of `DirectiveMeta` that includes additional information required to type-check the
|
||||
* usage of a particular directive.
|
||||
|
@ -274,3 +276,38 @@ export interface ExternalTemplateSourceMapping {
|
|||
template: string;
|
||||
templateUrl: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
/**
|
||||
* 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<AbsoluteFsPath, string>, 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,
|
||||
}
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
/**
|
||||
* @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 {TypeCheckingProgramStrategy, UpdateMode} from './api';
|
||||
import {TypeCheckProgramHost} from './host';
|
||||
|
||||
/**
|
||||
* 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<string, ts.SourceFile>();
|
||||
|
||||
constructor(
|
||||
private program: ts.Program, private originalHost: ts.CompilerHost,
|
||||
private options: ts.CompilerOptions, private shimExtensionPrefixes: string[]) {}
|
||||
|
||||
getProgram(): ts.Program {
|
||||
return this.program;
|
||||
}
|
||||
|
||||
updateFiles(contents: Map<AbsoluteFsPath, string>, updateMode: UpdateMode): void {
|
||||
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.originalHost, this.shimExtensionPrefixes);
|
||||
this.program = ts.createProgram({
|
||||
host,
|
||||
rootNames: this.program.getRootFileNames(),
|
||||
options: this.options,
|
||||
oldProgram: this.program,
|
||||
});
|
||||
host.postProgramCreationCleanup();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,91 @@
|
|||
/**
|
||||
* @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 {absoluteFromSourceFile, AbsoluteFsPath, getSourceFileOrError} from '../../file_system';
|
||||
import {ReferenceEmitter} from '../../imports';
|
||||
import {ReflectionHost} from '../../reflection';
|
||||
|
||||
import {TypeCheckingConfig, TypeCheckingProgramStrategy, UpdateMode} from './api';
|
||||
import {FileTypeCheckingData, TypeCheckContext} from './context';
|
||||
import {shouldReportDiagnostic, translateDiagnostic} from './diagnostics';
|
||||
|
||||
/**
|
||||
* Interface to trigger generation of type-checking code for a program given a new
|
||||
* `TypeCheckContext`.
|
||||
*/
|
||||
export interface ProgramTypeCheckAdapter {
|
||||
typeCheck(ctx: TypeCheckContext): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Primary template type-checking engine, which performs type-checking using a
|
||||
* `TypeCheckingProgramStrategy` for type-checking program maintenance, and the
|
||||
* `ProgramTypeCheckAdapter` for generation of template type-checking code.
|
||||
*/
|
||||
export class TemplateTypeChecker {
|
||||
private files = new Map<AbsoluteFsPath, FileTypeCheckingData>();
|
||||
|
||||
constructor(
|
||||
private originalProgram: ts.Program,
|
||||
private typeCheckingStrategy: TypeCheckingProgramStrategy,
|
||||
private typeCheckAdapter: ProgramTypeCheckAdapter, private config: TypeCheckingConfig,
|
||||
private refEmitter: ReferenceEmitter, private reflector: ReflectionHost) {}
|
||||
|
||||
/**
|
||||
* Reset the internal type-checking program by generating type-checking code from the user's
|
||||
* program.
|
||||
*/
|
||||
refresh(): void {
|
||||
this.files.clear();
|
||||
|
||||
const ctx =
|
||||
new TypeCheckContext(this.config, this.originalProgram, this.refEmitter, this.reflector);
|
||||
|
||||
// Typecheck all the files.
|
||||
this.typeCheckAdapter.typeCheck(ctx);
|
||||
|
||||
const results = ctx.finalize();
|
||||
this.typeCheckingStrategy.updateFiles(results.updates, UpdateMode.Complete);
|
||||
for (const [file, fileData] of results.perFileData) {
|
||||
this.files.set(file, {...fileData});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve type-checking diagnostics from the given `ts.SourceFile` using the most recent
|
||||
* type-checking program.
|
||||
*/
|
||||
getDiagnosticsForFile(sf: ts.SourceFile): ts.Diagnostic[] {
|
||||
const path = absoluteFromSourceFile(sf);
|
||||
if (!this.files.has(path)) {
|
||||
return [];
|
||||
}
|
||||
const record = this.files.get(path)!;
|
||||
|
||||
const typeCheckProgram = this.typeCheckingStrategy.getProgram();
|
||||
const typeCheckSf = getSourceFileOrError(typeCheckProgram, record.typeCheckFile);
|
||||
const rawDiagnostics = [];
|
||||
rawDiagnostics.push(...typeCheckProgram.getSemanticDiagnostics(typeCheckSf));
|
||||
if (record.hasInlines) {
|
||||
const inlineSf = getSourceFileOrError(typeCheckProgram, path);
|
||||
rawDiagnostics.push(...typeCheckProgram.getSemanticDiagnostics(inlineSf));
|
||||
}
|
||||
|
||||
return rawDiagnostics
|
||||
.map(diag => {
|
||||
if (!shouldReportDiagnostic(diag)) {
|
||||
return null;
|
||||
}
|
||||
return translateDiagnostic(diag, record.sourceResolver);
|
||||
})
|
||||
.filter((diag: ts.Diagnostic|null): diag is ts.Diagnostic => diag !== null)
|
||||
.concat(record.genesisDiagnostics);
|
||||
}
|
||||
}
|
|
@ -9,39 +9,112 @@
|
|||
import {BoundTarget, ParseSourceFile, SchemaMetadata} from '@angular/compiler';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {AbsoluteFsPath} from '../../file_system';
|
||||
import {absoluteFromSourceFile, AbsoluteFsPath} from '../../file_system';
|
||||
import {NoopImportRewriter, Reference, ReferenceEmitter} from '../../imports';
|
||||
import {ClassDeclaration, ReflectionHost} from '../../reflection';
|
||||
import {ImportManager} from '../../translator';
|
||||
|
||||
import {TemplateSourceMapping, TypeCheckableDirectiveMeta, TypeCheckBlockMetadata, TypeCheckingConfig, TypeCtorMetadata} from './api';
|
||||
import {shouldReportDiagnostic, translateDiagnostic} from './diagnostics';
|
||||
import {TemplateSourceResolver} from './diagnostics';
|
||||
import {DomSchemaChecker, RegistryDomSchemaChecker} from './dom';
|
||||
import {Environment} from './environment';
|
||||
import {TypeCheckProgramHost} from './host';
|
||||
import {OutOfBandDiagnosticRecorder, OutOfBandDiagnosticRecorderImpl} from './oob';
|
||||
import {TypeCheckShimGenerator} from './shim';
|
||||
import {TemplateSourceManager} from './source';
|
||||
import {generateTypeCheckBlock, requiresInlineTypeCheckBlock} from './type_check_block';
|
||||
import {TypeCheckFile} from './type_check_file';
|
||||
import {generateInlineTypeCtor, requiresInlineTypeCtor} from './type_constructor';
|
||||
|
||||
/**
|
||||
* Complete type-checking code generated for the user's program, ready for input into the
|
||||
* type-checking engine.
|
||||
*/
|
||||
export interface TypeCheckRequest {
|
||||
/**
|
||||
* Map of source filenames to new contents for those files.
|
||||
*
|
||||
* This includes both contents of type-checking shim files, as well as changes to any user files
|
||||
* which needed to be made to support template type-checking.
|
||||
*/
|
||||
updates: Map<AbsoluteFsPath, string>;
|
||||
|
||||
/**
|
||||
* Map containing additional data for each type-checking shim that is required to support
|
||||
* generation of diagnostics.
|
||||
*/
|
||||
perFileData: Map<AbsoluteFsPath, FileTypeCheckingData>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Data for a type-checking shim which is required to support generation of diagnostics.
|
||||
*/
|
||||
export interface FileTypeCheckingData {
|
||||
/**
|
||||
* Whether the type-checking shim required any inline changes to the original file, which affects
|
||||
* whether the shim can be reused.
|
||||
*/
|
||||
hasInlines: boolean;
|
||||
|
||||
/**
|
||||
* Source mapping information for mapping diagnostics back to the original template.
|
||||
*/
|
||||
sourceResolver: TemplateSourceResolver;
|
||||
|
||||
/**
|
||||
* Any `ts.Diagnostic`s which were produced during the generation of this shim.
|
||||
*
|
||||
* Some diagnostics are produced during creation time and are tracked here.
|
||||
*/
|
||||
genesisDiagnostics: ts.Diagnostic[];
|
||||
|
||||
/**
|
||||
* Path to the shim file.
|
||||
*/
|
||||
typeCheckFile: AbsoluteFsPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Data for a type-checking shim which is still having its code generated.
|
||||
*/
|
||||
export interface PendingFileTypeCheckingData {
|
||||
/**
|
||||
* Whether any inline code has been required by the shim yet.
|
||||
*/
|
||||
hasInlines: boolean;
|
||||
|
||||
/**
|
||||
* `TemplateSourceManager` being used to track source mapping information for this shim.
|
||||
*/
|
||||
sourceManager: TemplateSourceManager;
|
||||
|
||||
/**
|
||||
* Recorder for out-of-band diagnostics which are raised during generation.
|
||||
*/
|
||||
oobRecorder: OutOfBandDiagnosticRecorder;
|
||||
|
||||
/**
|
||||
* The `DomSchemaChecker` in use for this template, which records any schema-related diagnostics.
|
||||
*/
|
||||
domSchemaChecker: DomSchemaChecker;
|
||||
|
||||
/**
|
||||
* Path to the shim file.
|
||||
*/
|
||||
typeCheckFile: TypeCheckFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* A template type checking context for a program.
|
||||
*
|
||||
* The `TypeCheckContext` allows registration of components and their templates which need to be
|
||||
* type checked. It also allows generation of modified `ts.SourceFile`s which contain the type
|
||||
* checking code.
|
||||
* type checked.
|
||||
*/
|
||||
export class TypeCheckContext {
|
||||
private typeCheckFile: TypeCheckFile;
|
||||
private fileMap = new Map<AbsoluteFsPath, PendingFileTypeCheckingData>();
|
||||
|
||||
constructor(
|
||||
private config: TypeCheckingConfig, private refEmitter: ReferenceEmitter,
|
||||
private reflector: ReflectionHost, typeCheckFilePath: AbsoluteFsPath) {
|
||||
this.typeCheckFile = new TypeCheckFile(typeCheckFilePath, config, refEmitter, reflector);
|
||||
}
|
||||
private config: TypeCheckingConfig, private program: ts.Program,
|
||||
private refEmitter: ReferenceEmitter, private reflector: ReflectionHost) {}
|
||||
|
||||
/**
|
||||
* A `Map` of `ts.SourceFile`s that the context has seen to the operations (additions of methods
|
||||
|
@ -55,12 +128,6 @@ export class TypeCheckContext {
|
|||
*/
|
||||
private typeCtorPending = new Set<ts.ClassDeclaration>();
|
||||
|
||||
private sourceManager = new TemplateSourceManager();
|
||||
|
||||
private domSchemaChecker = new RegistryDomSchemaChecker(this.sourceManager);
|
||||
|
||||
private oobRecorder = new OutOfBandDiagnosticRecorderImpl(this.sourceManager);
|
||||
|
||||
/**
|
||||
* Record a template for the given component `node`, with a `SelectorMatcher` for directive
|
||||
* matching.
|
||||
|
@ -75,14 +142,16 @@ export class TypeCheckContext {
|
|||
pipes: Map<string, Reference<ClassDeclaration<ts.ClassDeclaration>>>,
|
||||
schemas: SchemaMetadata[], sourceMapping: TemplateSourceMapping,
|
||||
file: ParseSourceFile): void {
|
||||
const id = this.sourceManager.captureSource(sourceMapping, file);
|
||||
const fileData = this.dataForFile(ref.node.getSourceFile());
|
||||
|
||||
const id = fileData.sourceManager.captureSource(sourceMapping, file);
|
||||
// Get all of the directives used in the template and record type constructors for all of them.
|
||||
for (const dir of boundTarget.getUsedDirectives()) {
|
||||
const dirRef = dir.ref as Reference<ClassDeclaration<ts.ClassDeclaration>>;
|
||||
const dirNode = dirRef.node;
|
||||
if (requiresInlineTypeCtor(dirNode, this.reflector)) {
|
||||
// Add a type constructor operation for the directive.
|
||||
this.addInlineTypeCtor(dirNode.getSourceFile(), dirRef, {
|
||||
this.addInlineTypeCtor(fileData, dirNode.getSourceFile(), dirRef, {
|
||||
fnName: 'ngTypeCtor',
|
||||
// The constructor should have a body if the directive comes from a .ts file, but not if
|
||||
// it comes from a .d.ts file. .d.ts declarations don't have bodies.
|
||||
|
@ -102,11 +171,11 @@ export class TypeCheckContext {
|
|||
if (requiresInlineTypeCheckBlock(ref.node)) {
|
||||
// This class didn't meet the requirements for external type checking, so generate an inline
|
||||
// TCB for the class.
|
||||
this.addInlineTypeCheckBlock(ref, tcbMetadata);
|
||||
this.addInlineTypeCheckBlock(fileData, ref, tcbMetadata);
|
||||
} else {
|
||||
// The class can be type-checked externally as normal.
|
||||
this.typeCheckFile.addTypeCheckBlock(
|
||||
ref, tcbMetadata, this.domSchemaChecker, this.oobRecorder);
|
||||
fileData.typeCheckFile.addTypeCheckBlock(
|
||||
ref, tcbMetadata, fileData.domSchemaChecker, fileData.oobRecorder);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -114,8 +183,8 @@ export class TypeCheckContext {
|
|||
* Record a type constructor for the given `node` with the given `ctorMetadata`.
|
||||
*/
|
||||
addInlineTypeCtor(
|
||||
sf: ts.SourceFile, ref: Reference<ClassDeclaration<ts.ClassDeclaration>>,
|
||||
ctorMeta: TypeCtorMetadata): void {
|
||||
fileData: PendingFileTypeCheckingData, sf: ts.SourceFile,
|
||||
ref: Reference<ClassDeclaration<ts.ClassDeclaration>>, ctorMeta: TypeCtorMetadata): void {
|
||||
if (this.typeCtorPending.has(ref.node)) {
|
||||
return;
|
||||
}
|
||||
|
@ -129,20 +198,20 @@ export class TypeCheckContext {
|
|||
|
||||
// Push a `TypeCtorOp` into the operation queue for the source file.
|
||||
ops.push(new TypeCtorOp(ref, ctorMeta));
|
||||
fileData.hasInlines = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform a `ts.SourceFile` into a version that includes type checking code.
|
||||
*
|
||||
* If this particular source file has no directives that require type constructors, or components
|
||||
* that require type check blocks, then it will be returned directly. Otherwise, a new
|
||||
* `ts.SourceFile` is parsed from modified text of the original. This is necessary to ensure the
|
||||
* added code has correct positional information associated with it.
|
||||
* If this particular `ts.SourceFile` requires changes, the text representing its new contents
|
||||
* will be returned. Otherwise, a `null` return indicates no changes were necessary.
|
||||
*/
|
||||
transform(sf: ts.SourceFile): ts.SourceFile {
|
||||
// If there are no operations pending for this particular file, return it directly.
|
||||
transform(sf: ts.SourceFile): string|null {
|
||||
// If there are no operations pending for this particular file, return `null` to indicate no
|
||||
// changes.
|
||||
if (!this.opMap.has(sf)) {
|
||||
return sf;
|
||||
return null;
|
||||
}
|
||||
|
||||
// Imports may need to be added to the file to support type-checking of directives used in the
|
||||
|
@ -174,65 +243,42 @@ export class TypeCheckContext {
|
|||
.join('\n');
|
||||
code = imports + '\n' + code;
|
||||
|
||||
// Parse the new source file and return it.
|
||||
return ts.createSourceFile(sf.fileName, code, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
|
||||
return code;
|
||||
}
|
||||
|
||||
calculateTemplateDiagnostics(
|
||||
originalProgram: ts.Program, originalHost: ts.CompilerHost,
|
||||
originalOptions: ts.CompilerOptions): {
|
||||
diagnostics: ts.Diagnostic[],
|
||||
program: ts.Program,
|
||||
} {
|
||||
const typeCheckSf = this.typeCheckFile.render();
|
||||
// First, build the map of original source files.
|
||||
const sfMap = new Map<string, ts.SourceFile>();
|
||||
const interestingFiles: ts.SourceFile[] = [typeCheckSf];
|
||||
for (const originalSf of originalProgram.getSourceFiles()) {
|
||||
const sf = this.transform(originalSf);
|
||||
sfMap.set(sf.fileName, sf);
|
||||
if (!sf.isDeclarationFile && this.opMap.has(originalSf)) {
|
||||
interestingFiles.push(sf);
|
||||
finalize(): TypeCheckRequest {
|
||||
// First, build the map of updates to source files.
|
||||
const updates = new Map<AbsoluteFsPath, string>();
|
||||
for (const originalSf of this.opMap.keys()) {
|
||||
const newText = this.transform(originalSf);
|
||||
if (newText !== null) {
|
||||
updates.set(absoluteFromSourceFile(originalSf), newText);
|
||||
}
|
||||
}
|
||||
|
||||
sfMap.set(typeCheckSf.fileName, typeCheckSf);
|
||||
|
||||
const typeCheckProgram = ts.createProgram({
|
||||
host: new TypeCheckProgramHost(sfMap, originalHost),
|
||||
options: originalOptions,
|
||||
oldProgram: originalProgram,
|
||||
rootNames: originalProgram.getRootFileNames(),
|
||||
});
|
||||
|
||||
const diagnostics: ts.Diagnostic[] = [];
|
||||
const collectDiagnostics = (diags: readonly ts.Diagnostic[]): void => {
|
||||
for (const diagnostic of diags) {
|
||||
if (shouldReportDiagnostic(diagnostic)) {
|
||||
const translated = translateDiagnostic(diagnostic, this.sourceManager);
|
||||
|
||||
if (translated !== null) {
|
||||
diagnostics.push(translated);
|
||||
}
|
||||
}
|
||||
}
|
||||
const results: TypeCheckRequest = {
|
||||
updates: updates,
|
||||
perFileData: new Map<AbsoluteFsPath, FileTypeCheckingData>(),
|
||||
};
|
||||
|
||||
for (const sf of interestingFiles) {
|
||||
collectDiagnostics(typeCheckProgram.getSemanticDiagnostics(sf));
|
||||
for (const [sfPath, fileData] of this.fileMap.entries()) {
|
||||
updates.set(fileData.typeCheckFile.fileName, fileData.typeCheckFile.render());
|
||||
results.perFileData.set(sfPath, {
|
||||
genesisDiagnostics: [
|
||||
...fileData.domSchemaChecker.diagnostics,
|
||||
...fileData.oobRecorder.diagnostics,
|
||||
],
|
||||
hasInlines: fileData.hasInlines,
|
||||
sourceResolver: fileData.sourceManager,
|
||||
typeCheckFile: fileData.typeCheckFile.fileName,
|
||||
});
|
||||
}
|
||||
|
||||
diagnostics.push(...this.domSchemaChecker.diagnostics);
|
||||
diagnostics.push(...this.oobRecorder.diagnostics);
|
||||
|
||||
return {
|
||||
diagnostics,
|
||||
program: typeCheckProgram,
|
||||
};
|
||||
return results;
|
||||
}
|
||||
|
||||
private addInlineTypeCheckBlock(
|
||||
ref: Reference<ClassDeclaration<ts.ClassDeclaration>>,
|
||||
fileData: PendingFileTypeCheckingData, ref: Reference<ClassDeclaration<ts.ClassDeclaration>>,
|
||||
tcbMeta: TypeCheckBlockMetadata): void {
|
||||
const sf = ref.node.getSourceFile();
|
||||
if (!this.opMap.has(sf)) {
|
||||
|
@ -240,7 +286,28 @@ export class TypeCheckContext {
|
|||
}
|
||||
const ops = this.opMap.get(sf)!;
|
||||
ops.push(new TcbOp(
|
||||
ref, tcbMeta, this.config, this.reflector, this.domSchemaChecker, this.oobRecorder));
|
||||
ref, tcbMeta, this.config, this.reflector, fileData.domSchemaChecker,
|
||||
fileData.oobRecorder));
|
||||
fileData.hasInlines = true;
|
||||
}
|
||||
|
||||
private dataForFile(sf: ts.SourceFile): PendingFileTypeCheckingData {
|
||||
const sfPath = absoluteFromSourceFile(sf);
|
||||
|
||||
if (!this.fileMap.has(sfPath)) {
|
||||
const sourceManager = new TemplateSourceManager();
|
||||
const data: PendingFileTypeCheckingData = {
|
||||
domSchemaChecker: new RegistryDomSchemaChecker(sourceManager),
|
||||
oobRecorder: new OutOfBandDiagnosticRecorderImpl(sourceManager),
|
||||
typeCheckFile: new TypeCheckFile(
|
||||
TypeCheckShimGenerator.shimFor(sfPath), this.config, this.refEmitter, this.reflector),
|
||||
hasInlines: false,
|
||||
sourceManager,
|
||||
};
|
||||
this.fileMap.set(sfPath, data);
|
||||
}
|
||||
|
||||
return this.fileMap.get(sfPath)!;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -7,7 +7,14 @@
|
|||
*/
|
||||
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {absoluteFrom, absoluteFromSourceFile, AbsoluteFsPath, resolve} from '../../file_system';
|
||||
import {ShimAdapter, ShimReferenceTagger} from '../../shims';
|
||||
|
||||
import {TypeCheckContext} from './context';
|
||||
import {TypeCheckShimGenerator} from './shim';
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* A `ts.CompilerHost` which augments source files with type checking code from a
|
||||
|
@ -19,9 +26,28 @@ export class TypeCheckProgramHost implements ts.CompilerHost {
|
|||
*/
|
||||
private sfMap: Map<string, ts.SourceFile>;
|
||||
|
||||
// A special `ShimAdapter` is constructed to cause fresh type-checking shims to be generated for
|
||||
// every input file in the program.
|
||||
//
|
||||
// tsRootFiles is explicitly passed empty here. This is okay because at type-checking time, shim
|
||||
// root files have already been included in the ts.Program's root files by the `NgCompilerHost`'s
|
||||
// `ShimAdapter`.
|
||||
//
|
||||
// The oldProgram is also explicitly not passed here, even though one exists. This is because:
|
||||
// - ngfactory/ngsummary shims, if present, should be treated effectively as original files. As
|
||||
// they are still marked as shims, they won't have a typecheck shim generated for them, but
|
||||
// otherwise they should be reused as-is.
|
||||
// - ngtypecheck shims are always generated as fresh, and not reused.
|
||||
private shimAdapter = new ShimAdapter(
|
||||
this.delegate, /* tsRootFiles */[], [], [new TypeCheckShimGenerator()],
|
||||
/* oldProgram */ null);
|
||||
private shimTagger = new ShimReferenceTagger(this.shimExtensionPrefixes);
|
||||
|
||||
readonly resolveModuleNames?: ts.CompilerHost['resolveModuleNames'];
|
||||
|
||||
constructor(sfMap: Map<string, ts.SourceFile>, private delegate: ts.CompilerHost) {
|
||||
constructor(
|
||||
sfMap: Map<string, ts.SourceFile>, private delegate: ts.CompilerHost,
|
||||
private shimExtensionPrefixes: string[]) {
|
||||
this.sfMap = sfMap;
|
||||
|
||||
if (delegate.getDirectories !== undefined) {
|
||||
|
@ -38,24 +64,40 @@ export class TypeCheckProgramHost implements ts.CompilerHost {
|
|||
onError?: ((message: string) => void)|undefined,
|
||||
shouldCreateNewSourceFile?: boolean|undefined): ts.SourceFile|undefined {
|
||||
// Look in the cache for the source file.
|
||||
let sf: ts.SourceFile|undefined = this.sfMap.get(fileName);
|
||||
if (sf === undefined) {
|
||||
// There should be no cache misses, but just in case, delegate getSourceFile in the event of
|
||||
// a cache miss.
|
||||
sf = this.delegate.getSourceFile(
|
||||
fileName, languageVersion, onError, shouldCreateNewSourceFile);
|
||||
sf && this.sfMap.set(fileName, sf);
|
||||
let sf: ts.SourceFile;
|
||||
if (this.sfMap.has(fileName)) {
|
||||
sf = this.sfMap.get(fileName)!;
|
||||
} else {
|
||||
// TypeScript doesn't allow returning redirect source files. To avoid unforseen errors we
|
||||
// return the original source file instead of the redirect target.
|
||||
const redirectInfo = (sf as any).redirectInfo;
|
||||
if (redirectInfo !== undefined) {
|
||||
sf = redirectInfo.unredirected;
|
||||
const sfShim = this.shimAdapter.maybeGenerate(absoluteFrom(fileName));
|
||||
|
||||
if (sfShim === undefined) {
|
||||
return undefined;
|
||||
} else if (sfShim === null) {
|
||||
const maybeSf = this.delegate.getSourceFile(
|
||||
fileName, languageVersion, onError, shouldCreateNewSourceFile)!;
|
||||
if (maybeSf === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
sf = maybeSf;
|
||||
} else {
|
||||
sf = sfShim;
|
||||
}
|
||||
}
|
||||
// TypeScript doesn't allow returning redirect source files. To avoid unforseen errors we
|
||||
// return the original source file instead of the redirect target.
|
||||
const redirectInfo = (sf as any).redirectInfo;
|
||||
if (redirectInfo !== undefined) {
|
||||
sf = redirectInfo.unredirected;
|
||||
}
|
||||
|
||||
this.shimTagger.tag(sf);
|
||||
return sf;
|
||||
}
|
||||
|
||||
postProgramCreationCleanup(): void {
|
||||
this.shimTagger.finalize();
|
||||
}
|
||||
|
||||
// The rest of the methods simply delegate to the underlying `ts.CompilerHost`.
|
||||
|
||||
getDefaultLibFileName(options: ts.CompilerOptions): string {
|
||||
|
|
|
@ -8,24 +8,27 @@
|
|||
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {AbsoluteFsPath} from '../../file_system';
|
||||
import {TopLevelShimGenerator} from '../../shims/api';
|
||||
import {absoluteFrom, AbsoluteFsPath, getSourceFileOrError} from '../../file_system';
|
||||
import {PerFileShimGenerator, TopLevelShimGenerator} from '../../shims/api';
|
||||
|
||||
/**
|
||||
* A `ShimGenerator` which adds a type-checking file to the `ts.Program`.
|
||||
* A `ShimGenerator` which adds type-checking files to the `ts.Program`.
|
||||
*
|
||||
* This is a requirement for performant template type-checking, as TypeScript will only reuse
|
||||
* information in the main program when creating the type-checking program if the set of files in
|
||||
* each are exactly the same. Thus, the main program also needs the synthetic type-checking file.
|
||||
* each are exactly the same. Thus, the main program also needs the synthetic type-checking files.
|
||||
*/
|
||||
export class TypeCheckShimGenerator implements TopLevelShimGenerator {
|
||||
constructor(private typeCheckFile: AbsoluteFsPath) {}
|
||||
|
||||
export class TypeCheckShimGenerator implements PerFileShimGenerator {
|
||||
readonly extensionPrefix = 'ngtypecheck';
|
||||
readonly shouldEmit = false;
|
||||
|
||||
makeTopLevelShim(): ts.SourceFile {
|
||||
generateShimForFile(sf: ts.SourceFile, genFilePath: AbsoluteFsPath): ts.SourceFile {
|
||||
return ts.createSourceFile(
|
||||
this.typeCheckFile, 'export const USED_FOR_NG_TYPE_CHECKING = true;',
|
||||
ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
|
||||
genFilePath, 'export const USED_FOR_NG_TYPE_CHECKING = true;', ts.ScriptTarget.Latest, true,
|
||||
ts.ScriptKind.TS);
|
||||
}
|
||||
|
||||
static shimFor(fileName: AbsoluteFsPath): AbsoluteFsPath {
|
||||
return absoluteFrom(fileName.replace(/\.tsx?$/, '.ngtypecheck.ts'));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,7 +33,7 @@ export class TypeCheckFile extends Environment {
|
|||
private tcbStatements: ts.Statement[] = [];
|
||||
|
||||
constructor(
|
||||
private fileName: string, config: TypeCheckingConfig, refEmitter: ReferenceEmitter,
|
||||
readonly fileName: AbsoluteFsPath, config: TypeCheckingConfig, refEmitter: ReferenceEmitter,
|
||||
reflector: ReflectionHost) {
|
||||
super(
|
||||
config, new ImportManager(new NoopImportRewriter(), 'i'), refEmitter, reflector,
|
||||
|
@ -48,7 +48,7 @@ export class TypeCheckFile extends Environment {
|
|||
this.tcbStatements.push(fn);
|
||||
}
|
||||
|
||||
render(): ts.SourceFile {
|
||||
render(): string {
|
||||
let source: string = this.importManager.getAllImports(this.fileName)
|
||||
.map(i => `import * as ${i.qualifier} from '${i.specifier}';`)
|
||||
.join('\n') +
|
||||
|
@ -74,8 +74,7 @@ export class TypeCheckFile extends Environment {
|
|||
// is somehow more expensive than the initial parse.
|
||||
source += '\nexport const IS_A_MODULE = true;\n';
|
||||
|
||||
return ts.createSourceFile(
|
||||
this.fileName, source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
|
||||
return source;
|
||||
}
|
||||
|
||||
getPreludeStatements(): ts.Statement[] {
|
||||
|
|
|
@ -15,7 +15,9 @@ import {AbsoluteModuleStrategy, LocalIdentifierStrategy, LogicalProjectStrategy,
|
|||
import {ClassDeclaration, isNamedClassDeclaration, TypeScriptReflectionHost} from '../../reflection';
|
||||
import {makeProgram} from '../../testing';
|
||||
import {getRootDirs} from '../../util/src/typescript';
|
||||
import {TemplateId, TemplateSourceMapping, TypeCheckableDirectiveMeta, TypeCheckBlockMetadata, TypeCheckingConfig} from '../src/api';
|
||||
import {TemplateId, TemplateSourceMapping, TypeCheckableDirectiveMeta, TypeCheckBlockMetadata, TypeCheckingConfig, UpdateMode} from '../src/api';
|
||||
import {ReusedProgramStrategy} from '../src/augmented_program';
|
||||
import {ProgramTypeCheckAdapter, TemplateTypeChecker} from '../src/checker';
|
||||
import {TypeCheckContext} from '../src/context';
|
||||
import {DomSchemaChecker} from '../src/dom';
|
||||
import {Environment} from '../src/environment';
|
||||
|
@ -236,7 +238,7 @@ export function typecheck(
|
|||
template: string, source: string, declarations: TestDeclaration[] = [],
|
||||
additionalSources: {name: AbsoluteFsPath; contents: string}[] = [],
|
||||
config: Partial<TypeCheckingConfig> = {}, opts: ts.CompilerOptions = {}): ts.Diagnostic[] {
|
||||
const typeCheckFilePath = absoluteFrom('/_typecheck_.ts');
|
||||
const typeCheckFilePath = absoluteFrom('/main.ngtypecheck.ts');
|
||||
const files = [
|
||||
typescriptLibDts(),
|
||||
angularCoreDts(),
|
||||
|
@ -261,8 +263,7 @@ export function typecheck(
|
|||
program, checker, moduleResolver, new TypeScriptReflectionHost(checker)),
|
||||
new LogicalProjectStrategy(reflectionHost, logicalFs),
|
||||
]);
|
||||
const ctx = new TypeCheckContext(
|
||||
{...ALL_ENABLED_CONFIG, ...config}, emitter, reflectionHost, typeCheckFilePath);
|
||||
const fullConfig = {...ALL_ENABLED_CONFIG, ...config};
|
||||
|
||||
const templateUrl = 'synthetic.html';
|
||||
const templateFile = new ParseSourceFile(template, templateUrl);
|
||||
|
@ -294,8 +295,21 @@ export function typecheck(
|
|||
node: clazz.node.name,
|
||||
};
|
||||
|
||||
ctx.addTemplate(clazz, boundTarget, pipes, [], sourceMapping, templateFile);
|
||||
return ctx.calculateTemplateDiagnostics(program, host, options).diagnostics;
|
||||
const checkAdapter = createTypeCheckAdapter((ctx: TypeCheckContext) => {
|
||||
ctx.addTemplate(clazz, boundTarget, pipes, [], sourceMapping, templateFile);
|
||||
});
|
||||
|
||||
const programStrategy = new ReusedProgramStrategy(program, host, options, []);
|
||||
const templateTypeChecker = new TemplateTypeChecker(
|
||||
program, programStrategy, checkAdapter, fullConfig, emitter, reflectionHost);
|
||||
templateTypeChecker.refresh();
|
||||
return templateTypeChecker.getDiagnosticsForFile(sf);
|
||||
}
|
||||
|
||||
function createTypeCheckAdapter(fn: (ctx: TypeCheckContext) => void): ProgramTypeCheckAdapter {
|
||||
return {
|
||||
typeCheck: fn,
|
||||
};
|
||||
}
|
||||
|
||||
function prepareDeclarations(
|
||||
|
|
|
@ -10,13 +10,17 @@ import * as ts from 'typescript';
|
|||
import {absoluteFrom, getSourceFileOrError, LogicalFileSystem} from '../../file_system';
|
||||
import {runInEachFileSystem, TestFile} from '../../file_system/testing';
|
||||
import {AbsoluteModuleStrategy, LocalIdentifierStrategy, LogicalProjectStrategy, ModuleResolver, Reference, ReferenceEmitter} from '../../imports';
|
||||
import {isNamedClassDeclaration, TypeScriptReflectionHost} from '../../reflection';
|
||||
import {isNamedClassDeclaration, ReflectionHost, TypeScriptReflectionHost} from '../../reflection';
|
||||
import {getDeclaration, makeProgram} from '../../testing';
|
||||
import {getRootDirs} from '../../util/src/typescript';
|
||||
import {TypeCheckContext} from '../src/context';
|
||||
import {UpdateMode} from '../src/api';
|
||||
import {ReusedProgramStrategy} from '../src/augmented_program';
|
||||
import {PendingFileTypeCheckingData, TypeCheckContext} from '../src/context';
|
||||
import {RegistryDomSchemaChecker} from '../src/dom';
|
||||
import {TemplateSourceManager} from '../src/source';
|
||||
import {TypeCheckFile} from '../src/type_check_file';
|
||||
|
||||
import {ALL_ENABLED_CONFIG} from './test_utils';
|
||||
import {ALL_ENABLED_CONFIG, NoopOobRecorder} from './test_utils';
|
||||
|
||||
runInEachFileSystem(() => {
|
||||
describe('ngtsc typechecking', () => {
|
||||
|
@ -33,12 +37,6 @@ runInEachFileSystem(() => {
|
|||
type Pick<T, K extends keyof T> = { [P in K]: T[P]; };
|
||||
type NonNullable<T> = T extends null | undefined ? never : T;`
|
||||
};
|
||||
TYPE_CHECK_TS = {
|
||||
name: _('/_typecheck_.ts'),
|
||||
contents: `
|
||||
export const IS_A_MODULE = true;
|
||||
`
|
||||
};
|
||||
});
|
||||
|
||||
it('should not produce an empty SourceFile when there is nothing to typecheck', () => {
|
||||
|
@ -46,13 +44,13 @@ runInEachFileSystem(() => {
|
|||
_('/_typecheck_.ts'), ALL_ENABLED_CONFIG, new ReferenceEmitter([]),
|
||||
/* reflector */ null!);
|
||||
const sf = file.render();
|
||||
expect(sf.statements.length).toBe(1);
|
||||
expect(sf).toContain('export const IS_A_MODULE = true;');
|
||||
});
|
||||
|
||||
describe('ctors', () => {
|
||||
it('compiles a basic type constructor', () => {
|
||||
const files: TestFile[] = [
|
||||
LIB_D_TS, TYPE_CHECK_TS, {
|
||||
LIB_D_TS, {
|
||||
name: _('/main.ts'),
|
||||
contents: `
|
||||
class TestClass<T extends string> {
|
||||
|
@ -74,12 +72,12 @@ TestClass.ngTypeCtor({value: 'test'});
|
|||
new AbsoluteModuleStrategy(program, checker, moduleResolver, reflectionHost),
|
||||
new LogicalProjectStrategy(reflectionHost, logicalFs),
|
||||
]);
|
||||
const ctx =
|
||||
new TypeCheckContext(ALL_ENABLED_CONFIG, emitter, reflectionHost, _('/_typecheck_.ts'));
|
||||
const ctx = new TypeCheckContext(ALL_ENABLED_CONFIG, program, emitter, reflectionHost);
|
||||
const TestClass =
|
||||
getDeclaration(program, _('/main.ts'), 'TestClass', isNamedClassDeclaration);
|
||||
const pendingFile = makePendingFile(reflectionHost);
|
||||
ctx.addInlineTypeCtor(
|
||||
getSourceFileOrError(program, _('/main.ts')), new Reference(TestClass), {
|
||||
pendingFile, getSourceFileOrError(program, _('/main.ts')), new Reference(TestClass), {
|
||||
fnName: 'ngTypeCtor',
|
||||
body: true,
|
||||
fields: {
|
||||
|
@ -89,12 +87,12 @@ TestClass.ngTypeCtor({value: 'test'});
|
|||
},
|
||||
coercedInputFields: new Set(),
|
||||
});
|
||||
ctx.calculateTemplateDiagnostics(program, host, options);
|
||||
ctx.finalize();
|
||||
});
|
||||
|
||||
it('should not consider query fields', () => {
|
||||
const files: TestFile[] = [
|
||||
LIB_D_TS, TYPE_CHECK_TS, {
|
||||
LIB_D_TS, {
|
||||
name: _('/main.ts'),
|
||||
contents: `class TestClass { value: any; }`,
|
||||
}
|
||||
|
@ -110,12 +108,12 @@ TestClass.ngTypeCtor({value: 'test'});
|
|||
new AbsoluteModuleStrategy(program, checker, moduleResolver, reflectionHost),
|
||||
new LogicalProjectStrategy(reflectionHost, logicalFs),
|
||||
]);
|
||||
const ctx =
|
||||
new TypeCheckContext(ALL_ENABLED_CONFIG, emitter, reflectionHost, _('/_typecheck_.ts'));
|
||||
const pendingFile = makePendingFile(reflectionHost);
|
||||
const ctx = new TypeCheckContext(ALL_ENABLED_CONFIG, program, emitter, reflectionHost);
|
||||
const TestClass =
|
||||
getDeclaration(program, _('/main.ts'), 'TestClass', isNamedClassDeclaration);
|
||||
ctx.addInlineTypeCtor(
|
||||
getSourceFileOrError(program, _('/main.ts')), new Reference(TestClass), {
|
||||
pendingFile, getSourceFileOrError(program, _('/main.ts')), new Reference(TestClass), {
|
||||
fnName: 'ngTypeCtor',
|
||||
body: true,
|
||||
fields: {
|
||||
|
@ -125,9 +123,10 @@ TestClass.ngTypeCtor({value: 'test'});
|
|||
},
|
||||
coercedInputFields: new Set(),
|
||||
});
|
||||
const res = ctx.calculateTemplateDiagnostics(program, host, options);
|
||||
const TestClassWithCtor =
|
||||
getDeclaration(res.program, _('/main.ts'), 'TestClass', isNamedClassDeclaration);
|
||||
const programStrategy = new ReusedProgramStrategy(program, host, options, []);
|
||||
programStrategy.updateFiles(ctx.finalize().updates, UpdateMode.Complete);
|
||||
const TestClassWithCtor = getDeclaration(
|
||||
programStrategy.getProgram(), _('/main.ts'), 'TestClass', isNamedClassDeclaration);
|
||||
const typeCtor = TestClassWithCtor.members.find(isTypeCtor)!;
|
||||
expect(typeCtor.getText()).not.toContain('queryField');
|
||||
});
|
||||
|
@ -136,7 +135,7 @@ TestClass.ngTypeCtor({value: 'test'});
|
|||
describe('input type coercion', () => {
|
||||
it('should coerce input types', () => {
|
||||
const files: TestFile[] = [
|
||||
LIB_D_TS, TYPE_CHECK_TS, {
|
||||
LIB_D_TS, {
|
||||
name: _('/main.ts'),
|
||||
contents: `class TestClass { value: any; }`,
|
||||
}
|
||||
|
@ -152,12 +151,12 @@ TestClass.ngTypeCtor({value: 'test'});
|
|||
new AbsoluteModuleStrategy(program, checker, moduleResolver, reflectionHost),
|
||||
new LogicalProjectStrategy(reflectionHost, logicalFs),
|
||||
]);
|
||||
const ctx =
|
||||
new TypeCheckContext(ALL_ENABLED_CONFIG, emitter, reflectionHost, _('/_typecheck_.ts'));
|
||||
const pendingFile = makePendingFile(reflectionHost);
|
||||
const ctx = new TypeCheckContext(ALL_ENABLED_CONFIG, program, emitter, reflectionHost);
|
||||
const TestClass =
|
||||
getDeclaration(program, _('/main.ts'), 'TestClass', isNamedClassDeclaration);
|
||||
ctx.addInlineTypeCtor(
|
||||
getSourceFileOrError(program, _('/main.ts')), new Reference(TestClass), {
|
||||
pendingFile, getSourceFileOrError(program, _('/main.ts')), new Reference(TestClass), {
|
||||
fnName: 'ngTypeCtor',
|
||||
body: true,
|
||||
fields: {
|
||||
|
@ -167,9 +166,10 @@ TestClass.ngTypeCtor({value: 'test'});
|
|||
},
|
||||
coercedInputFields: new Set(['bar']),
|
||||
});
|
||||
const res = ctx.calculateTemplateDiagnostics(program, host, options);
|
||||
const TestClassWithCtor =
|
||||
getDeclaration(res.program, _('/main.ts'), 'TestClass', isNamedClassDeclaration);
|
||||
const programStrategy = new ReusedProgramStrategy(program, host, options, []);
|
||||
programStrategy.updateFiles(ctx.finalize().updates, UpdateMode.Complete);
|
||||
const TestClassWithCtor = getDeclaration(
|
||||
programStrategy.getProgram(), _('/main.ts'), 'TestClass', isNamedClassDeclaration);
|
||||
const typeCtor = TestClassWithCtor.members.find(isTypeCtor)!;
|
||||
const ctorText = typeCtor.getText().replace(/[ \r\n]+/g, ' ');
|
||||
expect(ctorText).toContain(
|
||||
|
@ -182,3 +182,15 @@ TestClass.ngTypeCtor({value: 'test'});
|
|||
return ts.isMethodDeclaration(el) && ts.isIdentifier(el.name) && el.name.text === 'ngTypeCtor';
|
||||
}
|
||||
});
|
||||
|
||||
function makePendingFile(reflector: ReflectionHost): PendingFileTypeCheckingData {
|
||||
const manager = new TemplateSourceManager();
|
||||
return {
|
||||
domSchemaChecker: new RegistryDomSchemaChecker(manager),
|
||||
hasInlines: false,
|
||||
oobRecorder: new NoopOobRecorder(),
|
||||
sourceManager: manager,
|
||||
typeCheckFile: new TypeCheckFile(
|
||||
absoluteFrom('/typecheck.ts'), ALL_ENABLED_CONFIG, new ReferenceEmitter([]), reflector)
|
||||
};
|
||||
}
|
|
@ -57,6 +57,7 @@ export class NgtscTestEnvironment {
|
|||
"noEmitOnError": true,
|
||||
"strictNullChecks": true,
|
||||
"outDir": "built",
|
||||
"rootDir": ".",
|
||||
"baseUrl": ".",
|
||||
"declaration": true,
|
||||
"target": "es5",
|
||||
|
@ -119,6 +120,13 @@ export class NgtscTestEnvironment {
|
|||
this.multiCompileHostExt.flushWrittenFileTracking();
|
||||
}
|
||||
|
||||
getTsProgram(): ts.Program {
|
||||
if (this.oldProgram === null) {
|
||||
throw new Error('No ts.Program has been created yet.');
|
||||
}
|
||||
return this.oldProgram.getTsProgram();
|
||||
}
|
||||
|
||||
/**
|
||||
* Older versions of the CLI do not provide the `CompilerHost.getModifiedResourceFiles()` method.
|
||||
* This results in the `changedResources` set being `null`.
|
||||
|
|
|
@ -106,11 +106,9 @@ runInEachFileSystem(() => {
|
|||
|
||||
env.driveMain();
|
||||
|
||||
// Look for index.js, not app/index.js, because of TypeScript's behavior of stripping off the
|
||||
// common prefix of all input files.
|
||||
const jsContents = env.getContents('index.js');
|
||||
const jsContents = env.getContents('app/index.js');
|
||||
|
||||
// The real goal of this test was to check that the relative import has the leading './'.
|
||||
// Check that the relative import has the leading './'.
|
||||
expect(jsContents).toContain(`import * as i1 from "./target";`);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -5147,6 +5147,7 @@ runInEachFileSystem(os => {
|
|||
const fileoverview = `
|
||||
/**
|
||||
* @fileoverview added by tsickle
|
||||
* Generated from: test.ts
|
||||
* @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
|
||||
*/
|
||||
`;
|
||||
|
@ -5164,6 +5165,7 @@ runInEachFileSystem(os => {
|
|||
const fileoverview = `
|
||||
/**
|
||||
* @fileoverview added by tsickle
|
||||
* Generated from: test.ts
|
||||
* @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
|
||||
*/
|
||||
`;
|
||||
|
@ -5189,6 +5191,7 @@ runInEachFileSystem(os => {
|
|||
const fileoverview = `
|
||||
/**
|
||||
* @fileoverview added by tsickle
|
||||
* Generated from: test.ngfactory.ts
|
||||
* @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
|
||||
*/
|
||||
`;
|
||||
|
@ -5219,6 +5222,7 @@ runInEachFileSystem(os => {
|
|||
/**
|
||||
*
|
||||
* @fileoverview Some Comp overview
|
||||
* Generated from: test.ts
|
||||
* @modName {some_comp}
|
||||
*
|
||||
* @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
|
||||
|
@ -5248,6 +5252,7 @@ runInEachFileSystem(os => {
|
|||
/**
|
||||
*
|
||||
* @fileoverview Some Comp overview
|
||||
* Generated from: test.ts
|
||||
* @modName {some_comp}
|
||||
*
|
||||
* @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
|
||||
|
|
|
@ -1840,12 +1840,7 @@ export declare class AnimationEvent {
|
|||
});
|
||||
|
||||
describe('stability', () => {
|
||||
// This section tests various scenarios which have more complex ts.Program setups and thus
|
||||
// exercise edge cases of the template type-checker.
|
||||
it('should accept a program with a flat index', () => {
|
||||
// This test asserts that flat indices don't have any negative interactions with the
|
||||
// generation of template type-checking code in the program.
|
||||
env.tsconfig({fullTemplateTypeCheck: true, flatModuleOutFile: 'flat.js'});
|
||||
beforeEach(() => {
|
||||
env.write('test.ts', `
|
||||
import {Component} from '@angular/core';
|
||||
|
||||
|
@ -1857,9 +1852,31 @@ export declare class AnimationEvent {
|
|||
expr = 'string';
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
// This section tests various scenarios which have more complex ts.Program setups and thus
|
||||
// exercise edge cases of the template type-checker.
|
||||
it('should accept a program with a flat index', () => {
|
||||
// This test asserts that flat indices don't have any negative interactions with the
|
||||
// generation of template type-checking code in the program.
|
||||
env.tsconfig({fullTemplateTypeCheck: true, flatModuleOutFile: 'flat.js'});
|
||||
|
||||
expect(env.driveDiagnostics()).toEqual([]);
|
||||
});
|
||||
|
||||
it('should not leave references to shims after execution', () => {
|
||||
// This test verifies that proper cleanup is performed for the technique being used to
|
||||
// include shim files in the ts.Program, and that none are left in the referencedFiles of
|
||||
// any ts.SourceFile after compilation.
|
||||
env.enableMultipleCompilations();
|
||||
|
||||
env.driveMain();
|
||||
for (const sf of env.getTsProgram().getSourceFiles()) {
|
||||
for (const ref of sf.referencedFiles) {
|
||||
expect(ref.fileName).not.toContain('.ngtypecheck.ts');
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue