refactor(compiler-cli): make file/shim split 1:n instead of 1:1 (#38105)

Previously in the template type-checking engine, it was assumed that every
input file would have an associated type-checking shim. The type check block
code for all components in the input file would be generated into this shim.

This is fine for whole-program type checking operations, but to support the
language service's requirements for low latency, it would be ideal to be
able to check a single component in isolation, especially if the component
is declared along with many others in a single file.

This commit removes the assumption that the file/shim mapping is 1:1, and
introduces the concept of component-to-shim mapping. Any
`TypeCheckingProgramStrategy` must provide such a mapping.

To achieve this:

 * type checking record information is now split into file-level data as
   well as per-shim data.
 * components are now assigned a stable `TemplateId` which is unique to the
   file in which they're declared.

PR Close #38105
This commit is contained in:
Alex Rickabaugh 2020-06-22 12:32:24 -07:00 committed by Misko Hevery
parent 9c8bc4a239
commit 736f6337b2
8 changed files with 200 additions and 89 deletions

View File

@ -12,21 +12,21 @@ import {AbsoluteFsPath} from '../file_system';
/** /**
* Interface of the incremental build engine. * Interface of the incremental build engine.
* *
* `W` is a generic type representing a unit of work. This is generic to avoid a cyclic dependency * `AnalysisT` is a generic type representing a unit of work. This is generic to avoid a cyclic
* between the incremental engine API definition and its consumer(s). * dependency between the incremental engine API definition and its consumer(s).
* `T` is a generic type representing template type-checking data for a particular file, which is * `FileTypeCheckDataT` is a generic type representing template type-checking data for a particular
* generic for the same reason. * input file, which is generic for the same reason.
*/ */
export interface IncrementalBuild<W, T> { export interface IncrementalBuild<AnalysisT, FileTypeCheckDataT> {
/** /**
* Retrieve the prior analysis work, if any, done for the given source file. * Retrieve the prior analysis work, if any, done for the given source file.
*/ */
priorWorkFor(sf: ts.SourceFile): W[]|null; priorWorkFor(sf: ts.SourceFile): AnalysisT[]|null;
/** /**
* Retrieve the prior type-checking work, if any, that's been done for the given source file. * Retrieve the prior type-checking work, if any, that's been done for the given source file.
*/ */
priorTypeCheckingResultsFor(sf: ts.SourceFile): T|null; priorTypeCheckingResultsFor(fileSf: ts.SourceFile): FileTypeCheckDataT|null;
} }
/** /**

View File

@ -13,6 +13,7 @@ import {AbsoluteFsPath} from '../../file_system';
import {Reference} from '../../imports'; import {Reference} from '../../imports';
import {TemplateGuardMeta} from '../../metadata'; import {TemplateGuardMeta} from '../../metadata';
import {ClassDeclaration} from '../../reflection'; import {ClassDeclaration} from '../../reflection';
import {ComponentToShimMappingStrategy} from './context';
/** /**
@ -284,7 +285,7 @@ export interface ExternalTemplateSourceMapping {
* This abstraction allows both the Angular compiler itself as well as the language service to * This abstraction allows both the Angular compiler itself as well as the language service to
* implement efficient template type-checking using common infrastructure. * implement efficient template type-checking using common infrastructure.
*/ */
export interface TypeCheckingProgramStrategy { export interface TypeCheckingProgramStrategy extends ComponentToShimMappingStrategy {
/** /**
* Retrieve the latest version of the program, containing all the updates made thus far. * Retrieve the latest version of the program, containing all the updates made thus far.
*/ */

View File

@ -8,11 +8,12 @@
import * as ts from 'typescript'; import * as ts from 'typescript';
import {AbsoluteFsPath} from '../../file_system'; import {absoluteFromSourceFile, AbsoluteFsPath} from '../../file_system';
import {retagAllTsFiles, untagAllTsFiles} from '../../shims'; import {retagAllTsFiles, untagAllTsFiles} from '../../shims';
import {TypeCheckingProgramStrategy, UpdateMode} from './api'; import {TypeCheckingProgramStrategy, UpdateMode} from './api';
import {TypeCheckProgramHost} from './host'; import {TypeCheckProgramHost} from './host';
import {TypeCheckShimGenerator} from './shim';
/** /**
* Implements a template type-checking program using `ts.createProgram` and TypeScript's program * Implements a template type-checking program using `ts.createProgram` and TypeScript's program
@ -78,4 +79,8 @@ export class ReusedProgramStrategy implements TypeCheckingProgramStrategy {
untagAllTsFiles(this.program); untagAllTsFiles(this.program);
untagAllTsFiles(oldProgram); untagAllTsFiles(oldProgram);
} }
shimPathForComponent(node: ts.ClassDeclaration): AbsoluteFsPath {
return TypeCheckShimGenerator.shimFor(absoluteFromSourceFile(node.getSourceFile()));
}
} }

View File

@ -16,7 +16,7 @@ import {isShim} from '../../shims';
import {TypeCheckingConfig, TypeCheckingProgramStrategy, UpdateMode} from './api'; import {TypeCheckingConfig, TypeCheckingProgramStrategy, UpdateMode} from './api';
import {FileTypeCheckingData, TypeCheckContext, TypeCheckRequest} from './context'; import {FileTypeCheckingData, TypeCheckContext, TypeCheckRequest} from './context';
import {shouldReportDiagnostic, translateDiagnostic} from './diagnostics'; import {shouldReportDiagnostic, TemplateSourceResolver, translateDiagnostic} from './diagnostics';
/** /**
* Interface to trigger generation of type-checking code for a program given a new * Interface to trigger generation of type-checking code for a program given a new
@ -49,8 +49,8 @@ export class TemplateTypeChecker {
refresh(): TypeCheckRequest { refresh(): TypeCheckRequest {
this.files.clear(); this.files.clear();
const ctx = const ctx = new TypeCheckContext(
new TypeCheckContext(this.config, this.compilerHost, this.refEmitter, this.reflector); this.config, this.compilerHost, this.typeCheckingStrategy, this.refEmitter, this.reflector);
// Typecheck all the files. // Typecheck all the files.
for (const sf of this.originalProgram.getSourceFiles()) { for (const sf of this.originalProgram.getSourceFiles()) {
@ -86,25 +86,32 @@ export class TemplateTypeChecker {
if (!this.files.has(path)) { if (!this.files.has(path)) {
return []; return [];
} }
const record = this.files.get(path)!; const fileRecord = this.files.get(path)!;
const typeCheckProgram = this.typeCheckingStrategy.getProgram(); const typeCheckProgram = this.typeCheckingStrategy.getProgram();
const typeCheckSf = getSourceFileOrError(typeCheckProgram, record.typeCheckFile);
const rawDiagnostics = []; const diagnostics: (ts.Diagnostic|null)[] = [];
rawDiagnostics.push(...typeCheckProgram.getSemanticDiagnostics(typeCheckSf)); if (fileRecord.hasInlines) {
if (record.hasInlines) {
const inlineSf = getSourceFileOrError(typeCheckProgram, path); const inlineSf = getSourceFileOrError(typeCheckProgram, path);
rawDiagnostics.push(...typeCheckProgram.getSemanticDiagnostics(inlineSf)); diagnostics.push(...typeCheckProgram.getSemanticDiagnostics(inlineSf).map(
diag => convertDiagnostic(diag, fileRecord.sourceResolver)));
}
for (const [shimPath, shimRecord] of fileRecord.shimData) {
const shimSf = getSourceFileOrError(typeCheckProgram, shimPath);
diagnostics.push(...typeCheckProgram.getSemanticDiagnostics(shimSf).map(
diag => convertDiagnostic(diag, fileRecord.sourceResolver)));
diagnostics.push(...shimRecord.genesisDiagnostics);
} }
return rawDiagnostics return diagnostics.filter((diag: ts.Diagnostic|null): diag is ts.Diagnostic => diag !== null);
.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);
} }
} }
function convertDiagnostic(
diag: ts.Diagnostic, sourceResolver: TemplateSourceResolver): ts.Diagnostic|null {
if (!shouldReportDiagnostic(diag)) {
return null;
}
return translateDiagnostic(diag, sourceResolver);
}

View File

@ -14,12 +14,11 @@ import {NoopImportRewriter, Reference, ReferenceEmitter} from '../../imports';
import {ClassDeclaration, ReflectionHost} from '../../reflection'; import {ClassDeclaration, ReflectionHost} from '../../reflection';
import {ImportManager} from '../../translator'; import {ImportManager} from '../../translator';
import {TemplateSourceMapping, TypeCheckableDirectiveMeta, TypeCheckBlockMetadata, TypeCheckingConfig, TypeCtorMetadata} from './api'; import {TemplateId, TemplateSourceMapping, TypeCheckableDirectiveMeta, TypeCheckBlockMetadata, TypeCheckingConfig, TypeCtorMetadata} from './api';
import {TemplateSourceResolver} from './diagnostics'; import {TemplateSourceResolver} from './diagnostics';
import {DomSchemaChecker, RegistryDomSchemaChecker} from './dom'; import {DomSchemaChecker, RegistryDomSchemaChecker} from './dom';
import {Environment} from './environment'; import {Environment} from './environment';
import {OutOfBandDiagnosticRecorder, OutOfBandDiagnosticRecorderImpl} from './oob'; import {OutOfBandDiagnosticRecorder, OutOfBandDiagnosticRecorderImpl} from './oob';
import {TypeCheckShimGenerator} from './shim';
import {TemplateSourceManager} from './source'; import {TemplateSourceManager} from './source';
import {generateTypeCheckBlock, requiresInlineTypeCheckBlock} from './type_check_block'; import {generateTypeCheckBlock, requiresInlineTypeCheckBlock} from './type_check_block';
import {TypeCheckFile} from './type_check_file'; import {TypeCheckFile} from './type_check_file';
@ -46,7 +45,8 @@ export interface TypeCheckRequest {
} }
/** /**
* Data for a type-checking shim which is required to support generation of diagnostics. * Data for template type-checking related to a specific input file in the user's program (which
* contains components to be checked).
*/ */
export interface FileTypeCheckingData { export interface FileTypeCheckingData {
/** /**
@ -56,25 +56,39 @@ export interface FileTypeCheckingData {
hasInlines: boolean; hasInlines: boolean;
/** /**
* Source mapping information for mapping diagnostics back to the original template. * Source mapping information for mapping diagnostics from inlined type check blocks back to the
* original template.
*/ */
sourceResolver: TemplateSourceResolver; sourceResolver: TemplateSourceResolver;
/**
* Data for each shim generated from this input file.
*
* A single input file will generate one or more shim files that actually contain template
* type-checking code.
*/
shimData: Map<AbsoluteFsPath, ShimTypeCheckingData>;
}
/**
* Data specific to a single shim generated from an input file.
*/
export interface ShimTypeCheckingData {
/**
* Path to the shim file.
*/
path: AbsoluteFsPath;
/** /**
* Any `ts.Diagnostic`s which were produced during the generation of this shim. * Any `ts.Diagnostic`s which were produced during the generation of this shim.
* *
* Some diagnostics are produced during creation time and are tracked here. * Some diagnostics are produced during creation time and are tracked here.
*/ */
genesisDiagnostics: ts.Diagnostic[]; genesisDiagnostics: ts.Diagnostic[];
/**
* Path to the shim file.
*/
typeCheckFile: AbsoluteFsPath;
} }
/** /**
* Data for a type-checking shim which is still having its code generated. * Data for an input file which is still in the process of template type-checking code generation.
*/ */
export interface PendingFileTypeCheckingData { export interface PendingFileTypeCheckingData {
/** /**
@ -83,10 +97,18 @@ export interface PendingFileTypeCheckingData {
hasInlines: boolean; hasInlines: boolean;
/** /**
* `TemplateSourceManager` being used to track source mapping information for this shim. * Source mapping information for mapping diagnostics from inlined type check blocks back to the
* original template.
*/ */
sourceManager: TemplateSourceManager; sourceManager: TemplateSourceManager;
/**
* Map of in-progress shim data for shims generated from this input file.
*/
shimData: Map<AbsoluteFsPath, PendingShimData>;
}
export interface PendingShimData {
/** /**
* Recorder for out-of-band diagnostics which are raised during generation. * Recorder for out-of-band diagnostics which are raised during generation.
*/ */
@ -98,9 +120,28 @@ export interface PendingFileTypeCheckingData {
domSchemaChecker: DomSchemaChecker; domSchemaChecker: DomSchemaChecker;
/** /**
* Path to the shim file. * Shim file in the process of being generated.
*/ */
typeCheckFile: TypeCheckFile; file: TypeCheckFile;
}
/**
* Abstracts the operation of determining which shim file will host a particular component's
* template type-checking code.
*
* Different consumers of the type checking infrastructure may choose different approaches to
* optimize for their specific use case (for example, the command-line compiler optimizes for
* efficient `ts.Program` reuse in watch mode).
*/
export interface ComponentToShimMappingStrategy {
/**
* Given a component, determine a path to the shim file into which that component's type checking
* code will be generated.
*
* A major constraint is that components in different input files must not share the same shim
* file. The behavior of the template type-checking system is undefined if this is violated.
*/
shimPathForComponent(node: ts.ClassDeclaration): AbsoluteFsPath;
} }
/** /**
@ -115,6 +156,7 @@ export class TypeCheckContext {
constructor( constructor(
private config: TypeCheckingConfig, private config: TypeCheckingConfig,
private compilerHost: Pick<ts.CompilerHost, 'getCanonicalFileName'>, private compilerHost: Pick<ts.CompilerHost, 'getCanonicalFileName'>,
private componentMappingStrategy: ComponentToShimMappingStrategy,
private refEmitter: ReferenceEmitter, private reflector: ReflectionHost) {} private refEmitter: ReferenceEmitter, private reflector: ReflectionHost) {}
/** /**
@ -160,8 +202,7 @@ export class TypeCheckContext {
schemas: SchemaMetadata[], sourceMapping: TemplateSourceMapping, schemas: SchemaMetadata[], sourceMapping: TemplateSourceMapping,
file: ParseSourceFile): void { file: ParseSourceFile): void {
const fileData = this.dataForFile(ref.node.getSourceFile()); const fileData = this.dataForFile(ref.node.getSourceFile());
const shimData = this.pendingShimForComponent(ref.node);
const id = fileData.sourceManager.captureSource(sourceMapping, file);
// Get all of the directives used in the template and record type constructors for all of them. // Get all of the directives used in the template and record type constructors for all of them.
for (const dir of boundTarget.getUsedDirectives()) { for (const dir of boundTarget.getUsedDirectives()) {
const dirRef = dir.ref as Reference<ClassDeclaration<ts.ClassDeclaration>>; const dirRef = dir.ref as Reference<ClassDeclaration<ts.ClassDeclaration>>;
@ -184,15 +225,19 @@ export class TypeCheckContext {
} }
} }
const tcbMetadata: TypeCheckBlockMetadata = {id, boundTarget, pipes, schemas}; const meta = {
id: fileData.sourceManager.captureSource(ref.node, sourceMapping, file),
boundTarget,
pipes,
schemas,
};
if (requiresInlineTypeCheckBlock(ref.node)) { if (requiresInlineTypeCheckBlock(ref.node)) {
// This class didn't meet the requirements for external type checking, so generate an inline // This class didn't meet the requirements for external type checking, so generate an inline
// TCB for the class. // TCB for the class.
this.addInlineTypeCheckBlock(fileData, ref, tcbMetadata); this.addInlineTypeCheckBlock(fileData, shimData, ref, meta);
} else { } else {
// The class can be type-checked externally as normal. // The class can be type-checked externally as normal.
fileData.typeCheckFile.addTypeCheckBlock( shimData.file.addTypeCheckBlock(ref, meta, shimData.domSchemaChecker, shimData.oobRecorder);
ref, tcbMetadata, fileData.domSchemaChecker, fileData.oobRecorder);
} }
} }
@ -278,17 +323,27 @@ export class TypeCheckContext {
perFileData: new Map<AbsoluteFsPath, FileTypeCheckingData>(), perFileData: new Map<AbsoluteFsPath, FileTypeCheckingData>(),
}; };
for (const [sfPath, fileData] of this.fileMap.entries()) { // Then go through each input file that has pending code generation operations.
updates.set(fileData.typeCheckFile.fileName, fileData.typeCheckFile.render()); for (const [sfPath, pendingFileData] of this.fileMap) {
results.perFileData.set(sfPath, { const fileData: FileTypeCheckingData = {
genesisDiagnostics: [ hasInlines: pendingFileData.hasInlines,
...fileData.domSchemaChecker.diagnostics, shimData: new Map(),
...fileData.oobRecorder.diagnostics, sourceResolver: pendingFileData.sourceManager,
], };
hasInlines: fileData.hasInlines,
sourceResolver: fileData.sourceManager, // For each input file, consider generation operations for each of its shims.
typeCheckFile: fileData.typeCheckFile.fileName, for (const [shimPath, pendingShimData] of pendingFileData.shimData) {
}); updates.set(pendingShimData.file.fileName, pendingShimData.file.render());
fileData.shimData.set(shimPath, {
genesisDiagnostics: [
...pendingShimData.domSchemaChecker.diagnostics,
...pendingShimData.oobRecorder.diagnostics,
],
path: pendingShimData.file.fileName,
});
}
results.perFileData.set(sfPath, fileData);
} }
for (const [sfPath, fileData] of this.adoptedFiles.entries()) { for (const [sfPath, fileData] of this.adoptedFiles.entries()) {
@ -299,7 +354,8 @@ export class TypeCheckContext {
} }
private addInlineTypeCheckBlock( private addInlineTypeCheckBlock(
fileData: PendingFileTypeCheckingData, ref: Reference<ClassDeclaration<ts.ClassDeclaration>>, fileData: PendingFileTypeCheckingData, shimData: PendingShimData,
ref: Reference<ClassDeclaration<ts.ClassDeclaration>>,
tcbMeta: TypeCheckBlockMetadata): void { tcbMeta: TypeCheckBlockMetadata): void {
const sf = ref.node.getSourceFile(); const sf = ref.node.getSourceFile();
if (!this.opMap.has(sf)) { if (!this.opMap.has(sf)) {
@ -307,24 +363,35 @@ export class TypeCheckContext {
} }
const ops = this.opMap.get(sf)!; const ops = this.opMap.get(sf)!;
ops.push(new TcbOp( ops.push(new TcbOp(
ref, tcbMeta, this.config, this.reflector, fileData.domSchemaChecker, ref, tcbMeta, this.config, this.reflector, shimData.domSchemaChecker,
fileData.oobRecorder)); shimData.oobRecorder));
fileData.hasInlines = true; fileData.hasInlines = true;
} }
private pendingShimForComponent(node: ts.ClassDeclaration): PendingShimData {
const fileData = this.dataForFile(node.getSourceFile());
const shimPath = this.componentMappingStrategy.shimPathForComponent(node);
if (!fileData.shimData.has(shimPath)) {
fileData.shimData.set(shimPath, {
domSchemaChecker: new RegistryDomSchemaChecker(fileData.sourceManager),
oobRecorder: new OutOfBandDiagnosticRecorderImpl(fileData.sourceManager),
file: new TypeCheckFile(
shimPath, this.config, this.refEmitter, this.reflector, this.compilerHost),
});
}
return fileData.shimData.get(shimPath)!;
}
private dataForFile(sf: ts.SourceFile): PendingFileTypeCheckingData { private dataForFile(sf: ts.SourceFile): PendingFileTypeCheckingData {
const sfPath = absoluteFromSourceFile(sf); const sfPath = absoluteFromSourceFile(sf);
const sourceManager = new TemplateSourceManager();
if (!this.fileMap.has(sfPath)) { if (!this.fileMap.has(sfPath)) {
const sourceManager = new TemplateSourceManager();
const data: PendingFileTypeCheckingData = { const data: PendingFileTypeCheckingData = {
domSchemaChecker: new RegistryDomSchemaChecker(sourceManager),
oobRecorder: new OutOfBandDiagnosticRecorderImpl(sourceManager),
typeCheckFile: new TypeCheckFile(
TypeCheckShimGenerator.shimFor(sfPath), this.config, this.refEmitter, this.reflector,
this.compilerHost),
hasInlines: false, hasInlines: false,
sourceManager, sourceManager,
shimData: new Map(),
}; };
this.fileMap.set(sfPath, data); this.fileMap.set(sfPath, data);
} }

View File

@ -7,6 +7,7 @@
*/ */
import {AbsoluteSourceSpan, ParseLocation, ParseSourceFile, ParseSourceSpan} from '@angular/compiler'; import {AbsoluteSourceSpan, ParseLocation, ParseSourceFile, ParseSourceSpan} from '@angular/compiler';
import * as ts from 'typescript';
import {TemplateId, TemplateSourceMapping} from './api'; import {TemplateId, TemplateSourceMapping} from './api';
import {TemplateSourceResolver} from './diagnostics'; import {TemplateSourceResolver} from './diagnostics';
@ -47,7 +48,6 @@ export class TemplateSource {
* Implements `TemplateSourceResolver` to resolve the source of a template based on these IDs. * Implements `TemplateSourceResolver` to resolve the source of a template based on these IDs.
*/ */
export class TemplateSourceManager implements TemplateSourceResolver { export class TemplateSourceManager implements TemplateSourceResolver {
private nextTemplateId: number = 1;
/** /**
* This map keeps track of all template sources that have been type-checked by the id that is * This map keeps track of all template sources that have been type-checked by the id that is
* attached to a TCB's function declaration as leading trivia. This enables translation of * attached to a TCB's function declaration as leading trivia. This enables translation of
@ -55,8 +55,9 @@ export class TemplateSourceManager implements TemplateSourceResolver {
*/ */
private templateSources = new Map<TemplateId, TemplateSource>(); private templateSources = new Map<TemplateId, TemplateSource>();
captureSource(mapping: TemplateSourceMapping, file: ParseSourceFile): TemplateId { captureSource(node: ts.ClassDeclaration, mapping: TemplateSourceMapping, file: ParseSourceFile):
const id = `tcb${this.nextTemplateId++}` as TemplateId; TemplateId {
const id = getTemplateId(node);
this.templateSources.set(id, new TemplateSource(mapping, file)); this.templateSources.set(id, new TemplateSource(mapping, file));
return id; return id;
} }
@ -76,3 +77,28 @@ export class TemplateSourceManager implements TemplateSourceResolver {
return templateSource.toParseSourceSpan(span.start, span.end); return templateSource.toParseSourceSpan(span.start, span.end);
} }
} }
const TEMPLATE_ID = Symbol('ngTemplateId');
const NEXT_TEMPLATE_ID = Symbol('ngNextTemplateId');
interface HasTemplateId {
[TEMPLATE_ID]: TemplateId;
}
interface HasNextTemplateId {
[NEXT_TEMPLATE_ID]: number;
}
function getTemplateId(node: ts.ClassDeclaration&Partial<HasTemplateId>): TemplateId {
if (node[TEMPLATE_ID] === undefined) {
node[TEMPLATE_ID] = allocateTemplateId(node.getSourceFile());
}
return node[TEMPLATE_ID]!;
}
function allocateTemplateId(sf: ts.SourceFile&Partial<HasNextTemplateId>): TemplateId {
if (sf[NEXT_TEMPLATE_ID] === undefined) {
sf[NEXT_TEMPLATE_ID] = 1;
}
return (`tcb${sf[NEXT_TEMPLATE_ID]!++}`) as TemplateId;
}

View File

@ -7,7 +7,7 @@
*/ */
import * as ts from 'typescript'; import * as ts from 'typescript';
import {absoluteFrom, getFileSystem, getSourceFileOrError, LogicalFileSystem, NgtscCompilerHost} from '../../file_system'; import {absoluteFrom, AbsoluteFsPath, getFileSystem, getSourceFileOrError, LogicalFileSystem, NgtscCompilerHost} from '../../file_system';
import {runInEachFileSystem, TestFile} from '../../file_system/testing'; import {runInEachFileSystem, TestFile} from '../../file_system/testing';
import {AbsoluteModuleStrategy, LocalIdentifierStrategy, LogicalProjectStrategy, ModuleResolver, Reference, ReferenceEmitter} from '../../imports'; import {AbsoluteModuleStrategy, LocalIdentifierStrategy, LogicalProjectStrategy, ModuleResolver, Reference, ReferenceEmitter} from '../../imports';
import {isNamedClassDeclaration, ReflectionHost, TypeScriptReflectionHost} from '../../reflection'; import {isNamedClassDeclaration, ReflectionHost, TypeScriptReflectionHost} from '../../reflection';
@ -15,8 +15,7 @@ import {getDeclaration, makeProgram} from '../../testing';
import {getRootDirs} from '../../util/src/typescript'; import {getRootDirs} from '../../util/src/typescript';
import {UpdateMode} from '../src/api'; import {UpdateMode} from '../src/api';
import {ReusedProgramStrategy} from '../src/augmented_program'; import {ReusedProgramStrategy} from '../src/augmented_program';
import {PendingFileTypeCheckingData, TypeCheckContext} from '../src/context'; import {ComponentToShimMappingStrategy, PendingFileTypeCheckingData, TypeCheckContext} from '../src/context';
import {RegistryDomSchemaChecker} from '../src/dom';
import {TemplateSourceManager} from '../src/source'; import {TemplateSourceManager} from '../src/source';
import {TypeCheckFile} from '../src/type_check_file'; import {TypeCheckFile} from '../src/type_check_file';
@ -73,10 +72,11 @@ TestClass.ngTypeCtor({value: 'test'});
new AbsoluteModuleStrategy(program, checker, moduleResolver, reflectionHost), new AbsoluteModuleStrategy(program, checker, moduleResolver, reflectionHost),
new LogicalProjectStrategy(reflectionHost, logicalFs), new LogicalProjectStrategy(reflectionHost, logicalFs),
]); ]);
const ctx = new TypeCheckContext(ALL_ENABLED_CONFIG, host, emitter, reflectionHost); const ctx = new TypeCheckContext(
ALL_ENABLED_CONFIG, host, new TestMappingStrategy(), emitter, reflectionHost);
const TestClass = const TestClass =
getDeclaration(program, _('/main.ts'), 'TestClass', isNamedClassDeclaration); getDeclaration(program, _('/main.ts'), 'TestClass', isNamedClassDeclaration);
const pendingFile = makePendingFile(reflectionHost, host); const pendingFile = makePendingFile();
ctx.addInlineTypeCtor( ctx.addInlineTypeCtor(
pendingFile, getSourceFileOrError(program, _('/main.ts')), new Reference(TestClass), { pendingFile, getSourceFileOrError(program, _('/main.ts')), new Reference(TestClass), {
fnName: 'ngTypeCtor', fnName: 'ngTypeCtor',
@ -109,8 +109,9 @@ TestClass.ngTypeCtor({value: 'test'});
new AbsoluteModuleStrategy(program, checker, moduleResolver, reflectionHost), new AbsoluteModuleStrategy(program, checker, moduleResolver, reflectionHost),
new LogicalProjectStrategy(reflectionHost, logicalFs), new LogicalProjectStrategy(reflectionHost, logicalFs),
]); ]);
const pendingFile = makePendingFile(reflectionHost, host); const pendingFile = makePendingFile();
const ctx = new TypeCheckContext(ALL_ENABLED_CONFIG, host, emitter, reflectionHost); const ctx = new TypeCheckContext(
ALL_ENABLED_CONFIG, host, new TestMappingStrategy(), emitter, reflectionHost);
const TestClass = const TestClass =
getDeclaration(program, _('/main.ts'), 'TestClass', isNamedClassDeclaration); getDeclaration(program, _('/main.ts'), 'TestClass', isNamedClassDeclaration);
ctx.addInlineTypeCtor( ctx.addInlineTypeCtor(
@ -152,8 +153,9 @@ TestClass.ngTypeCtor({value: 'test'});
new AbsoluteModuleStrategy(program, checker, moduleResolver, reflectionHost), new AbsoluteModuleStrategy(program, checker, moduleResolver, reflectionHost),
new LogicalProjectStrategy(reflectionHost, logicalFs), new LogicalProjectStrategy(reflectionHost, logicalFs),
]); ]);
const pendingFile = makePendingFile(reflectionHost, host); const pendingFile = makePendingFile();
const ctx = new TypeCheckContext(ALL_ENABLED_CONFIG, host, emitter, reflectionHost); const ctx = new TypeCheckContext(
ALL_ENABLED_CONFIG, host, new TestMappingStrategy(), emitter, reflectionHost);
const TestClass = const TestClass =
getDeclaration(program, _('/main.ts'), 'TestClass', isNamedClassDeclaration); getDeclaration(program, _('/main.ts'), 'TestClass', isNamedClassDeclaration);
ctx.addInlineTypeCtor( ctx.addInlineTypeCtor(
@ -184,16 +186,16 @@ TestClass.ngTypeCtor({value: 'test'});
} }
}); });
function makePendingFile( function makePendingFile(): PendingFileTypeCheckingData {
reflector: ReflectionHost, compilerHost: ts.CompilerHost): PendingFileTypeCheckingData {
const manager = new TemplateSourceManager();
return { return {
domSchemaChecker: new RegistryDomSchemaChecker(manager),
hasInlines: false, hasInlines: false,
oobRecorder: new NoopOobRecorder(), sourceManager: new TemplateSourceManager(),
sourceManager: manager, shimData: new Map(),
typeCheckFile: new TypeCheckFile(
absoluteFrom('/typecheck.ts'), ALL_ENABLED_CONFIG, new ReferenceEmitter([]), reflector,
compilerHost)
}; };
} }
class TestMappingStrategy implements ComponentToShimMappingStrategy {
shimPathForComponent(): AbsoluteFsPath {
return absoluteFrom('/typecheck.ts');
}
}

View File

@ -9,9 +9,9 @@
import {CompilerOptions} from '@angular/compiler-cli'; import {CompilerOptions} from '@angular/compiler-cli';
import {NgCompiler, NgCompilerHost} from '@angular/compiler-cli/src/ngtsc/core'; import {NgCompiler, NgCompilerHost} from '@angular/compiler-cli/src/ngtsc/core';
import {AbsoluteFsPath} from '@angular/compiler-cli/src/ngtsc/file_system'; import {absoluteFromSourceFile, AbsoluteFsPath} from '@angular/compiler-cli/src/ngtsc/file_system';
import {PatchedProgramIncrementalBuildStrategy} from '@angular/compiler-cli/src/ngtsc/incremental'; import {PatchedProgramIncrementalBuildStrategy} from '@angular/compiler-cli/src/ngtsc/incremental';
import {TypeCheckingProgramStrategy, UpdateMode} from '@angular/compiler-cli/src/ngtsc/typecheck'; import {TypeCheckingProgramStrategy, TypeCheckShimGenerator, UpdateMode} from '@angular/compiler-cli/src/ngtsc/typecheck';
import * as ts from 'typescript/lib/tsserverlibrary'; import * as ts from 'typescript/lib/tsserverlibrary';
import {makeCompilerHostFromProject} from './compiler_host'; import {makeCompilerHostFromProject} from './compiler_host';
@ -75,6 +75,9 @@ export class Compiler {
function createTypeCheckingProgramStrategy(project: ts.server.Project): function createTypeCheckingProgramStrategy(project: ts.server.Project):
TypeCheckingProgramStrategy { TypeCheckingProgramStrategy {
return { return {
shimPathForComponent(component: ts.ClassDeclaration): AbsoluteFsPath {
return TypeCheckShimGenerator.shimFor(absoluteFromSourceFile(component.getSourceFile()));
},
getProgram(): ts.Program { getProgram(): ts.Program {
const program = project.getLanguageService().getProgram(); const program = project.getLanguageService().getProgram();
if (!program) { if (!program) {