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:
Tobias Bosch
2017-09-11 15:18:19 -07:00
committed by Matias Niemelä
parent 554fe65690
commit 996c7c2dde
22 changed files with 712 additions and 401 deletions

View File

@ -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);