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:
parent
9c8bc4a239
commit
736f6337b2
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -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()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 => {
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function convertDiagnostic(
|
||||||
|
diag: ts.Diagnostic, sourceResolver: TemplateSourceResolver): ts.Diagnostic|null {
|
||||||
if (!shouldReportDiagnostic(diag)) {
|
if (!shouldReportDiagnostic(diag)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return translateDiagnostic(diag, record.sourceResolver);
|
return translateDiagnostic(diag, sourceResolver);
|
||||||
})
|
|
||||||
.filter((diag: ts.Diagnostic|null): diag is ts.Diagnostic => diag !== null)
|
|
||||||
.concat(record.genesisDiagnostics);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,19 +323,29 @@ 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 = {
|
||||||
|
hasInlines: pendingFileData.hasInlines,
|
||||||
|
shimData: new Map(),
|
||||||
|
sourceResolver: pendingFileData.sourceManager,
|
||||||
|
};
|
||||||
|
|
||||||
|
// For each input file, consider generation operations for each of its shims.
|
||||||
|
for (const [shimPath, pendingShimData] of pendingFileData.shimData) {
|
||||||
|
updates.set(pendingShimData.file.fileName, pendingShimData.file.render());
|
||||||
|
fileData.shimData.set(shimPath, {
|
||||||
genesisDiagnostics: [
|
genesisDiagnostics: [
|
||||||
...fileData.domSchemaChecker.diagnostics,
|
...pendingShimData.domSchemaChecker.diagnostics,
|
||||||
...fileData.oobRecorder.diagnostics,
|
...pendingShimData.oobRecorder.diagnostics,
|
||||||
],
|
],
|
||||||
hasInlines: fileData.hasInlines,
|
path: pendingShimData.file.fileName,
|
||||||
sourceResolver: fileData.sourceManager,
|
|
||||||
typeCheckFile: fileData.typeCheckFile.fileName,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
results.perFileData.set(sfPath, fileData);
|
||||||
|
}
|
||||||
|
|
||||||
for (const [sfPath, fileData] of this.adoptedFiles.entries()) {
|
for (const [sfPath, fileData] of this.adoptedFiles.entries()) {
|
||||||
results.perFileData.set(sfPath, fileData);
|
results.perFileData.set(sfPath, fileData);
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
|
||||||
if (!this.fileMap.has(sfPath)) {
|
|
||||||
const sourceManager = new TemplateSourceManager();
|
const sourceManager = new TemplateSourceManager();
|
||||||
|
|
||||||
|
if (!this.fileMap.has(sfPath)) {
|
||||||
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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
|
@ -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');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
Loading…
Reference in New Issue