feat(compiler): reuse the TypeScript typecheck for template typechecking. (#19152)
This speeds up the compilation process significantly. Also introduces a new option `fullTemplateTypeCheck` to do more checks in templates: - check expressions inside of templatized content (e.g. inside of `<div *ngIf>`). - check the arguments of calls to the `transform` function of pipes - check references to directives that were exposed as variables via `exportAs` PR Close #19152
This commit is contained in:

committed by
Matias Niemelä

parent
554fe65690
commit
996c7c2dde
@ -1,231 +0,0 @@
|
||||
/**
|
||||
* @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 {AotCompiler, AotCompilerHost, AotCompilerOptions, EmitterVisitorContext, GeneratedFile, NgAnalyzedModules, ParseSourceSpan, Statement, StaticReflector, TypeScriptEmitter, createAotCompiler} from '@angular/compiler';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {DEFAULT_ERROR_CODE, Diagnostic, SOURCE} from '../transformers/api';
|
||||
|
||||
interface FactoryInfo {
|
||||
source: ts.SourceFile;
|
||||
context: EmitterVisitorContext;
|
||||
}
|
||||
|
||||
type FactoryInfoMap = Map<string, FactoryInfo>;
|
||||
|
||||
const stubCancellationToken: ts.CancellationToken = {
|
||||
isCancellationRequested(): boolean{return false;},
|
||||
throwIfCancellationRequested(): void{}
|
||||
};
|
||||
|
||||
export class TypeChecker {
|
||||
private _aotCompiler: AotCompiler|undefined;
|
||||
private _reflector: StaticReflector|undefined;
|
||||
private _factories: Map<string, FactoryInfo>|undefined;
|
||||
private _factoryNames: string[]|undefined;
|
||||
private _diagnosticProgram: ts.Program|undefined;
|
||||
private _diagnosticsByFile: Map<string, Diagnostic[]>|undefined;
|
||||
private _currentCancellationToken: ts.CancellationToken = stubCancellationToken;
|
||||
private _partial: boolean = false;
|
||||
|
||||
constructor(
|
||||
private program: ts.Program, private tsOptions: ts.CompilerOptions,
|
||||
private compilerHost: ts.CompilerHost, private aotCompilerHost: AotCompilerHost,
|
||||
private aotOptions: AotCompilerOptions, private _analyzedModules?: NgAnalyzedModules,
|
||||
private _generatedFiles?: GeneratedFile[]) {}
|
||||
|
||||
getDiagnostics(fileName?: string, cancellationToken?: ts.CancellationToken): Diagnostic[] {
|
||||
this._currentCancellationToken = cancellationToken || stubCancellationToken;
|
||||
try {
|
||||
return fileName ?
|
||||
this.diagnosticsByFileName.get(fileName) || [] :
|
||||
([] as Diagnostic[]).concat(...Array.from(this.diagnosticsByFileName.values()));
|
||||
} finally {
|
||||
this._currentCancellationToken = stubCancellationToken;
|
||||
}
|
||||
}
|
||||
|
||||
get partialResults(): boolean { return this._partial; }
|
||||
|
||||
private get analyzedModules(): NgAnalyzedModules {
|
||||
return this._analyzedModules || (this._analyzedModules = this.aotCompiler.analyzeModulesSync(
|
||||
this.program.getSourceFiles().map(sf => sf.fileName)));
|
||||
}
|
||||
|
||||
private get diagnosticsByFileName(): Map<string, Diagnostic[]> {
|
||||
return this._diagnosticsByFile || this.createDiagnosticsByFile();
|
||||
}
|
||||
|
||||
private get diagnosticProgram(): ts.Program {
|
||||
return this._diagnosticProgram || this.createDiagnosticProgram();
|
||||
}
|
||||
|
||||
private get generatedFiles(): GeneratedFile[] {
|
||||
let result = this._generatedFiles;
|
||||
if (!result) {
|
||||
this._generatedFiles = result = this.aotCompiler.emitAllImpls(this.analyzedModules);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private get aotCompiler(): AotCompiler {
|
||||
return this._aotCompiler || this.createCompilerAndReflector();
|
||||
}
|
||||
|
||||
private get reflector(): StaticReflector {
|
||||
let result = this._reflector;
|
||||
if (!result) {
|
||||
this.createCompilerAndReflector();
|
||||
result = this._reflector !;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private get factories(): Map<string, FactoryInfo> {
|
||||
return this._factories || this.createFactories();
|
||||
}
|
||||
|
||||
private get factoryNames(): string[] {
|
||||
return this._factoryNames || (this.createFactories() && this._factoryNames !);
|
||||
}
|
||||
|
||||
private createCompilerAndReflector() {
|
||||
const {compiler, reflector} = createAotCompiler(this.aotCompilerHost, this.aotOptions);
|
||||
this._reflector = reflector;
|
||||
return this._aotCompiler = compiler;
|
||||
}
|
||||
|
||||
private createDiagnosticProgram() {
|
||||
// Create a program that is all the files from the original program plus the factories.
|
||||
const existingFiles = this.program.getSourceFiles().map(source => source.fileName);
|
||||
const host = new TypeCheckingHost(this.compilerHost, this.program, this.factories);
|
||||
return this._diagnosticProgram =
|
||||
ts.createProgram([...existingFiles, ...this.factoryNames], this.tsOptions, host);
|
||||
}
|
||||
|
||||
private createFactories() {
|
||||
// Create all the factory files with enough information to map the diagnostics reported for the
|
||||
// created file back to the original source.
|
||||
const emitter = new TypeScriptEmitter();
|
||||
const factorySources =
|
||||
this.generatedFiles.filter(file => file.stmts != null && file.stmts.length)
|
||||
.map<[string, FactoryInfo]>(
|
||||
file => [file.genFileUrl, createFactoryInfo(emitter, file)]);
|
||||
this._factories = new Map(factorySources);
|
||||
this._factoryNames = Array.from(this._factories.keys());
|
||||
return this._factories;
|
||||
}
|
||||
|
||||
private createDiagnosticsByFile() {
|
||||
// Collect all the diagnostics binned by original source file name.
|
||||
const result = new Map<string, Diagnostic[]>();
|
||||
const diagnosticsFor = (fileName: string) => {
|
||||
let r = result.get(fileName);
|
||||
if (!r) {
|
||||
r = [];
|
||||
result.set(fileName, r);
|
||||
}
|
||||
return r;
|
||||
};
|
||||
const program = this.diagnosticProgram;
|
||||
for (const factoryName of this.factoryNames) {
|
||||
if (this._currentCancellationToken.isCancellationRequested()) return result;
|
||||
const sourceFile = program.getSourceFile(factoryName);
|
||||
for (const diagnostic of this.diagnosticProgram.getSemanticDiagnostics(sourceFile)) {
|
||||
if (diagnostic.file && diagnostic.start) {
|
||||
const span = this.sourceSpanOf(diagnostic.file, diagnostic.start);
|
||||
if (span) {
|
||||
const fileName = span.start.file.url;
|
||||
const diagnosticsList = diagnosticsFor(fileName);
|
||||
diagnosticsList.push({
|
||||
messageText: diagnosticMessageToString(diagnostic.messageText),
|
||||
category: diagnostic.category, span,
|
||||
source: SOURCE,
|
||||
code: DEFAULT_ERROR_CODE
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private sourceSpanOf(source: ts.SourceFile, start: number): ParseSourceSpan|null {
|
||||
// Find the corresponding TypeScript node
|
||||
const info = this.factories.get(source.fileName);
|
||||
if (info) {
|
||||
const {line, character} = ts.getLineAndCharacterOfPosition(source, start);
|
||||
return info.context.spanOf(line, character);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function diagnosticMessageToString(message: ts.DiagnosticMessageChain | string): string {
|
||||
return ts.flattenDiagnosticMessageText(message, '\n');
|
||||
}
|
||||
|
||||
const REWRITE_PREFIX = /^\u0275[0-9]+$/;
|
||||
|
||||
function createFactoryInfo(emitter: TypeScriptEmitter, file: GeneratedFile): FactoryInfo {
|
||||
const {sourceText, context} = emitter.emitStatementsAndContext(
|
||||
file.srcFileUrl, file.genFileUrl, file.stmts !,
|
||||
/* preamble */ undefined, /* emitSourceMaps */ undefined,
|
||||
/* referenceFilter */ reference => !!(reference.name && REWRITE_PREFIX.test(reference.name)));
|
||||
const source = ts.createSourceFile(
|
||||
file.genFileUrl, sourceText, ts.ScriptTarget.Latest, /* setParentNodes */ true);
|
||||
return {source, context};
|
||||
}
|
||||
|
||||
class TypeCheckingHost implements ts.CompilerHost {
|
||||
constructor(
|
||||
private host: ts.CompilerHost, private originalProgram: ts.Program,
|
||||
private factories: Map<string, FactoryInfo>) {}
|
||||
|
||||
getSourceFile(
|
||||
fileName: string, languageVersion: ts.ScriptTarget,
|
||||
onError?: ((message: string) => void)): ts.SourceFile {
|
||||
const originalSource = this.originalProgram.getSourceFile(fileName);
|
||||
if (originalSource) {
|
||||
return originalSource;
|
||||
}
|
||||
const factoryInfo = this.factories.get(fileName);
|
||||
if (factoryInfo) {
|
||||
return factoryInfo.source;
|
||||
}
|
||||
return this.host.getSourceFile(fileName, languageVersion, onError);
|
||||
}
|
||||
|
||||
getDefaultLibFileName(options: ts.CompilerOptions): string {
|
||||
return this.host.getDefaultLibFileName(options);
|
||||
}
|
||||
|
||||
writeFile: ts.WriteFileCallback =
|
||||
() => { throw new Error('Unexpected write in diagnostic program'); };
|
||||
|
||||
getCurrentDirectory(): string { return this.host.getCurrentDirectory(); }
|
||||
|
||||
getDirectories(path: string): string[] { return this.host.getDirectories(path); }
|
||||
|
||||
getCanonicalFileName(fileName: string): string {
|
||||
return this.host.getCanonicalFileName(fileName);
|
||||
}
|
||||
|
||||
useCaseSensitiveFileNames(): boolean { return this.host.useCaseSensitiveFileNames(); }
|
||||
|
||||
getNewLine(): string { return this.host.getNewLine(); }
|
||||
|
||||
fileExists(fileName: string): boolean {
|
||||
return this.factories.has(fileName) || this.host.fileExists(fileName);
|
||||
}
|
||||
|
||||
readFile(fileName: string): string {
|
||||
const factoryInfo = this.factories.get(fileName);
|
||||
return (factoryInfo && factoryInfo.source.text) || this.host.readFile(fileName);
|
||||
}
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
/**
|
||||
* @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 {ParseSourceSpan} from '@angular/compiler';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {DEFAULT_ERROR_CODE, Diagnostic, SOURCE} from '../transformers/api';
|
||||
import {GENERATED_FILES} from '../transformers/util';
|
||||
|
||||
export interface TypeCheckHost {
|
||||
ngSpanOf(fileName: string, line: number, character: number): ParseSourceSpan|null;
|
||||
}
|
||||
|
||||
export function translateDiagnostics(host: TypeCheckHost, untranslatedDiagnostics: ts.Diagnostic[]):
|
||||
{ts: ts.Diagnostic[], ng: Diagnostic[]} {
|
||||
const ts: ts.Diagnostic[] = [];
|
||||
const ng: Diagnostic[] = [];
|
||||
|
||||
untranslatedDiagnostics.forEach((diagnostic) => {
|
||||
if (diagnostic.file && diagnostic.start && GENERATED_FILES.test(diagnostic.file.fileName)) {
|
||||
// We need to filter out diagnostics about unused functions as
|
||||
// they are in fact referenced by nobody and only serve to surface
|
||||
// type check errors.
|
||||
if (diagnostic.code === /* ... is declared but never used */ 6133) {
|
||||
return;
|
||||
}
|
||||
const span = sourceSpanOf(host, diagnostic.file, diagnostic.start);
|
||||
if (span) {
|
||||
const fileName = span.start.file.url;
|
||||
ng.push({
|
||||
messageText: diagnosticMessageToString(diagnostic.messageText),
|
||||
category: diagnostic.category, span,
|
||||
source: SOURCE,
|
||||
code: DEFAULT_ERROR_CODE
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
ts.push(diagnostic);
|
||||
});
|
||||
return {ts, ng};
|
||||
}
|
||||
|
||||
function sourceSpanOf(host: TypeCheckHost, source: ts.SourceFile, start: number): ParseSourceSpan|
|
||||
null {
|
||||
const {line, character} = ts.getLineAndCharacterOfPosition(source, start);
|
||||
return host.ngSpanOf(source.fileName, line, character);
|
||||
}
|
||||
|
||||
function diagnosticMessageToString(message: ts.DiagnosticMessageChain | string): string {
|
||||
return ts.flattenDiagnosticMessageText(message, '\n');
|
||||
}
|
@ -15,7 +15,6 @@ to the language service.
|
||||
|
||||
*/
|
||||
export {CompilerHost, CompilerHostContext, MetadataProvider, ModuleResolutionHostAdapter, NodeCompilerHostContext} from './compiler_host';
|
||||
export {TypeChecker} from './diagnostics/check_types';
|
||||
export {DiagnosticTemplateInfo, ExpressionDiagnostic, getExpressionDiagnostics, getExpressionScope, getTemplateExpressionDiagnostics} from './diagnostics/expression_diagnostics';
|
||||
export {AstType, DiagnosticKind, ExpressionDiagnosticsContext, TypeDiagnostic} from './diagnostics/expression_type';
|
||||
export {BuiltinType, DeclarationKind, Definition, Location, PipeInfo, Pipes, Signature, Span, Symbol, SymbolDeclaration, SymbolQuery, SymbolTable} from './diagnostics/symbols';
|
||||
|
@ -18,10 +18,6 @@ const TS_EXT = /\.ts$/;
|
||||
|
||||
export type Diagnostics = Array<ts.Diagnostic|api.Diagnostic>;
|
||||
|
||||
function isTsDiagnostic(diagnostic: any): diagnostic is ts.Diagnostic {
|
||||
return diagnostic && diagnostic.source != 'angular';
|
||||
}
|
||||
|
||||
export function formatDiagnostics(options: api.CompilerOptions, diags: Diagnostics): string {
|
||||
if (diags && diags.length) {
|
||||
const tsFormatHost: ts.FormatDiagnosticsHost = {
|
||||
@ -31,7 +27,7 @@ export function formatDiagnostics(options: api.CompilerOptions, diags: Diagnosti
|
||||
};
|
||||
return diags
|
||||
.map(d => {
|
||||
if (isTsDiagnostic(d)) {
|
||||
if (api.isTsDiagnostic(d)) {
|
||||
return ts.formatDiagnostics([d], tsFormatHost);
|
||||
} else {
|
||||
let res = ts.DiagnosticCategory[d.category];
|
||||
|
@ -21,6 +21,14 @@ export interface Diagnostic {
|
||||
source: 'angular';
|
||||
}
|
||||
|
||||
export function isTsDiagnostic(diagnostic: any): diagnostic is ts.Diagnostic {
|
||||
return diagnostic != null && diagnostic.source !== 'angular';
|
||||
}
|
||||
|
||||
export function isNgDiagnostic(diagnostic: any): diagnostic is Diagnostic {
|
||||
return diagnostic != null && diagnostic.source === 'angular';
|
||||
}
|
||||
|
||||
export interface CompilerOptions extends ts.CompilerOptions {
|
||||
// Absolute path to a directory where generated file structure is written.
|
||||
// If unspecified, generated files will be written alongside sources.
|
||||
@ -73,6 +81,10 @@ export interface CompilerOptions extends ts.CompilerOptions {
|
||||
// Default is true.
|
||||
generateCodeForLibraries?: boolean;
|
||||
|
||||
// Whether to enable all type checks for templates.
|
||||
// This will be true be default in Angular 6.
|
||||
fullTemplateTypeCheck?: boolean;
|
||||
|
||||
// Insert JSDoc type annotations needed by Closure Compiler
|
||||
annotateForClosureCompiler?: boolean;
|
||||
|
||||
|
@ -6,20 +6,19 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {AotCompiler, AotCompilerHost, AotCompilerOptions, GeneratedFile, MessageBundle, NgAnalyzedModules, Serializer, Xliff, Xliff2, Xmb, core, createAotCompiler, getParseErrors, isSyntaxError, toTypeScript} from '@angular/compiler';
|
||||
import {AotCompiler, AotCompilerHost, AotCompilerOptions, EmitterVisitorContext, GeneratedFile, MessageBundle, NgAnalyzedModules, ParseSourceSpan, Serializer, TypeScriptEmitter, Xliff, Xliff2, Xmb, core, createAotCompiler, getParseErrors, isSyntaxError, toTypeScript} from '@angular/compiler';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {BaseAotCompilerHost} from '../compiler_host';
|
||||
import {TypeChecker} from '../diagnostics/check_types';
|
||||
import {TypeCheckHost, translateDiagnostics} from '../diagnostics/translate_diagnostics';
|
||||
import {createBundleIndexHost} from '../metadata/index';
|
||||
|
||||
import {CompilerHost, CompilerOptions, CustomTransformers, DEFAULT_ERROR_CODE, Diagnostic, EmitFlags, Program, SOURCE, TsEmitArguments, TsEmitCallback} from './api';
|
||||
import {LowerMetadataCache, getExpressionLoweringTransformFactory} from './lower_expressions';
|
||||
import {getAngularEmitterTransformFactory} from './node_emitter_transform';
|
||||
|
||||
const GENERATED_FILES = /(.*?)\.(ngfactory|shim\.ngstyle|ngstyle|ngsummary)\.(js|d\.ts|ts)$/;
|
||||
import {GENERATED_FILES} from './util';
|
||||
|
||||
const emptyModules: NgAnalyzedModules = {
|
||||
ngModules: [],
|
||||
@ -45,12 +44,11 @@ class AngularCompilerProgram implements Program {
|
||||
private _structuralDiagnostics: Diagnostic[] = [];
|
||||
private _stubs: GeneratedFile[]|undefined;
|
||||
private _stubFiles: string[]|undefined;
|
||||
private _programWithStubsHost: ts.CompilerHost|undefined;
|
||||
private _programWithStubsHost: ts.CompilerHost&TypeCheckHost|undefined;
|
||||
private _programWithStubs: ts.Program|undefined;
|
||||
private _generatedFiles: GeneratedFile[]|undefined;
|
||||
private _generatedFileDiagnostics: Diagnostic[]|undefined;
|
||||
private _typeChecker: TypeChecker|undefined;
|
||||
private _semanticDiagnostics: Diagnostic[]|undefined;
|
||||
private _semanticDiagnostics: {ts: ts.Diagnostic[], ng: Diagnostic[]}|undefined;
|
||||
private _optionsDiagnostics: Diagnostic[] = [];
|
||||
|
||||
constructor(
|
||||
@ -109,7 +107,7 @@ class AngularCompilerProgram implements Program {
|
||||
|
||||
getTsSemanticDiagnostics(sourceFile?: ts.SourceFile, cancellationToken?: ts.CancellationToken):
|
||||
ts.Diagnostic[] {
|
||||
return this.programWithStubs.getSemanticDiagnostics(sourceFile, cancellationToken);
|
||||
return this.semanticDiagnostics.ts;
|
||||
}
|
||||
|
||||
getNgSemanticDiagnostics(fileName?: string, cancellationToken?: ts.CancellationToken):
|
||||
@ -119,8 +117,7 @@ class AngularCompilerProgram implements Program {
|
||||
// If we have diagnostics during the parser phase the type check phase is not meaningful so skip
|
||||
// it.
|
||||
if (compilerDiagnostics && compilerDiagnostics.length) return compilerDiagnostics;
|
||||
|
||||
return this.typeChecker.getDiagnostics(fileName, cancellationToken);
|
||||
return this.semanticDiagnostics.ng;
|
||||
}
|
||||
|
||||
loadNgStructureAsync(): Promise<void> {
|
||||
@ -187,7 +184,7 @@ class AngularCompilerProgram implements Program {
|
||||
}, []));
|
||||
}
|
||||
|
||||
private get programWithStubsHost(): ts.CompilerHost {
|
||||
private get programWithStubsHost(): ts.CompilerHost&TypeCheckHost {
|
||||
return this._programWithStubsHost || (this._programWithStubsHost = createProgramWithStubsHost(
|
||||
this.stubs, this.tsProgram, this.host));
|
||||
}
|
||||
@ -200,16 +197,15 @@ class AngularCompilerProgram implements Program {
|
||||
return this._generatedFiles || (this._generatedFiles = this.generateFiles());
|
||||
}
|
||||
|
||||
private get typeChecker(): TypeChecker {
|
||||
return (this._typeChecker && !this._typeChecker.partialResults) ?
|
||||
this._typeChecker :
|
||||
(this._typeChecker = this.createTypeChecker());
|
||||
}
|
||||
|
||||
private get generatedFileDiagnostics(): Diagnostic[]|undefined {
|
||||
return this.generatedFiles && this._generatedFileDiagnostics !;
|
||||
}
|
||||
|
||||
private get semanticDiagnostics(): {ts: ts.Diagnostic[], ng: Diagnostic[]} {
|
||||
return this._semanticDiagnostics ||
|
||||
(this._semanticDiagnostics = this.generateSemanticDiagnostics());
|
||||
}
|
||||
|
||||
private calculateTransforms(customTransformers?: CustomTransformers): ts.CustomTransformers {
|
||||
const beforeTs: ts.TransformerFactory<ts.SourceFile>[] = [];
|
||||
if (!this.options.disableExpressionLowering) {
|
||||
@ -283,12 +279,6 @@ class AngularCompilerProgram implements Program {
|
||||
}
|
||||
}
|
||||
|
||||
private createTypeChecker(): TypeChecker {
|
||||
return new TypeChecker(
|
||||
this.tsProgram, this.options, this.host, this.aotCompilerHost, this.options,
|
||||
this.analyzedModules, this.generatedFiles);
|
||||
}
|
||||
|
||||
private createProgramWithStubs(): ts.Program {
|
||||
// If we are skipping code generation just use the original program.
|
||||
// Otherwise, create a new program that includes the stub files.
|
||||
@ -297,6 +287,11 @@ class AngularCompilerProgram implements Program {
|
||||
ts.createProgram(
|
||||
[...this.rootNames, ...this.stubFiles], this.options, this.programWithStubsHost);
|
||||
}
|
||||
|
||||
private generateSemanticDiagnostics(): {ts: ts.Diagnostic[], ng: Diagnostic[]} {
|
||||
return translateDiagnostics(
|
||||
this.programWithStubsHost, this.programWithStubs.getSemanticDiagnostics());
|
||||
}
|
||||
}
|
||||
|
||||
class AotCompilerHostImpl extends BaseAotCompilerHost<CompilerHost> {
|
||||
@ -360,6 +355,7 @@ function getAotCompilerOptions(options: CompilerOptions): AotCompilerOptions {
|
||||
enableLegacyTemplate: options.enableLegacyTemplate,
|
||||
enableSummariesForJit: true,
|
||||
preserveWhitespaces: options.preserveWhitespaces,
|
||||
fullTemplateTypeCheck: options.fullTemplateTypeCheck,
|
||||
};
|
||||
}
|
||||
|
||||
@ -453,13 +449,15 @@ function getNgOptionDiagnostics(options: CompilerOptions): Diagnostic[] {
|
||||
|
||||
function createProgramWithStubsHost(
|
||||
generatedFiles: GeneratedFile[], originalProgram: ts.Program,
|
||||
originalHost: ts.CompilerHost): ts.CompilerHost {
|
||||
originalHost: ts.CompilerHost): ts.CompilerHost&TypeCheckHost {
|
||||
interface FileData {
|
||||
g: GeneratedFile;
|
||||
s?: ts.SourceFile;
|
||||
emitCtx?: EmitterVisitorContext;
|
||||
}
|
||||
return new class implements ts.CompilerHost {
|
||||
return new class implements ts.CompilerHost, TypeCheckHost {
|
||||
private generatedFiles: Map<string, FileData>;
|
||||
private emitter = new TypeScriptEmitter();
|
||||
writeFile: ts.WriteFileCallback;
|
||||
getCancellationToken: () => ts.CancellationToken;
|
||||
getDefaultLibLocation: () => string;
|
||||
@ -487,13 +485,27 @@ function createProgramWithStubsHost(
|
||||
this.trace = s => originalHost.trace !(s);
|
||||
}
|
||||
}
|
||||
ngSpanOf(fileName: string, line: number, character: number): ParseSourceSpan|null {
|
||||
const data = this.generatedFiles.get(fileName);
|
||||
if (data && data.emitCtx) {
|
||||
return data.emitCtx.spanOf(line, character);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
getSourceFile(
|
||||
fileName: string, languageVersion: ts.ScriptTarget,
|
||||
onError?: ((message: string) => void)|undefined): ts.SourceFile {
|
||||
const data = this.generatedFiles.get(fileName);
|
||||
if (data) {
|
||||
return data.s || (data.s = ts.createSourceFile(
|
||||
fileName, data.g.source || toTypeScript(data.g), languageVersion));
|
||||
if (!data.s) {
|
||||
const {sourceText, context} = this.emitter.emitStatementsAndContext(
|
||||
data.g.srcFileUrl, data.g.genFileUrl, data.g.stmts !,
|
||||
/* preamble */ undefined, /* emitSourceMaps */ undefined,
|
||||
/* referenceFilter */ undefined);
|
||||
data.emitCtx = context;
|
||||
data.s = ts.createSourceFile(fileName, sourceText, languageVersion);
|
||||
}
|
||||
return data.s;
|
||||
}
|
||||
return originalProgram.getSourceFile(fileName) ||
|
||||
originalHost.getSourceFile(fileName, languageVersion, onError);
|
||||
|
9
packages/compiler-cli/src/transformers/util.ts
Normal file
9
packages/compiler-cli/src/transformers/util.ts
Normal file
@ -0,0 +1,9 @@
|
||||
/**
|
||||
* @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
|
||||
*/
|
||||
|
||||
export const GENERATED_FILES = /(.*?)\.(ngfactory|shim\.ngstyle|ngstyle|ngsummary)\.(js|d\.ts|ts)$/;
|
Reference in New Issue
Block a user