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:
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);
|
||||
|
|
|
@ -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)$/;
|
|
@ -6,95 +6,200 @@
|
|||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {AotCompilerOptions, createAotCompiler} from '@angular/compiler';
|
||||
import {EmittingCompilerHost, MockAotCompilerHost, MockCompilerHost, MockData, MockDirectory, MockMetadataBundlerHost, arrayToMockDir, arrayToMockMap, isSource, settings, setup, toMockFileArray} from '@angular/compiler/test/aot/test_util';
|
||||
import * as ng from '@angular/compiler-cli';
|
||||
import {makeTempDir} from '@angular/tsc-wrapped/test/test_support';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {TypeChecker} from '../../src/diagnostics/check_types';
|
||||
import {Diagnostic} from '../../src/transformers/api';
|
||||
import {LowerMetadataCache} from '../../src/transformers/lower_expressions';
|
||||
|
||||
function compile(
|
||||
rootDirs: MockData, options: AotCompilerOptions = {},
|
||||
tsOptions: ts.CompilerOptions = {}): Diagnostic[] {
|
||||
const rootDirArr = toMockFileArray(rootDirs);
|
||||
const scriptNames = rootDirArr.map(entry => entry.fileName).filter(isSource);
|
||||
const host = new MockCompilerHost(scriptNames, arrayToMockDir(rootDirArr));
|
||||
const aotHost = new MockAotCompilerHost(host, new LowerMetadataCache({}));
|
||||
const tsSettings = {...settings, ...tsOptions};
|
||||
const program = ts.createProgram(host.scriptNames.slice(0), tsSettings, host);
|
||||
const ngChecker = new TypeChecker(program, tsSettings, host, aotHost, options);
|
||||
return ngChecker.getDiagnostics();
|
||||
function getNgRootDir() {
|
||||
const moduleFilename = module.filename.replace(/\\/g, '/');
|
||||
const distIndex = moduleFilename.indexOf('/dist/all');
|
||||
return moduleFilename.substr(0, distIndex);
|
||||
}
|
||||
|
||||
describe('ng type checker', () => {
|
||||
let angularFiles = setup();
|
||||
let basePath: string;
|
||||
let write: (fileName: string, content: string) => void;
|
||||
let errorSpy: jasmine.Spy&((s: string) => void);
|
||||
|
||||
function accept(...files: MockDirectory[]) {
|
||||
expectNoDiagnostics(compile([angularFiles, QUICKSTART, ...files]));
|
||||
function compileAndCheck(
|
||||
mockDirs: {[fileName: string]: string}[],
|
||||
overrideOptions: ng.CompilerOptions = {}): ng.Diagnostics {
|
||||
const fileNames: string[] = [];
|
||||
mockDirs.forEach((dir) => {
|
||||
Object.keys(dir).forEach((fileName) => {
|
||||
if (fileName.endsWith('.ts')) {
|
||||
fileNames.push(path.resolve(basePath, fileName));
|
||||
}
|
||||
write(fileName, dir[fileName]);
|
||||
});
|
||||
});
|
||||
const options: ng.CompilerOptions = {
|
||||
basePath,
|
||||
'experimentalDecorators': true,
|
||||
'skipLibCheck': true,
|
||||
'strict': true,
|
||||
'types': [],
|
||||
'outDir': path.resolve(basePath, 'built'),
|
||||
'rootDir': basePath,
|
||||
'baseUrl': basePath,
|
||||
'declaration': true,
|
||||
'target': ts.ScriptTarget.ES5,
|
||||
'module': ts.ModuleKind.ES2015,
|
||||
'moduleResolution': ts.ModuleResolutionKind.NodeJs,
|
||||
'lib': [
|
||||
path.resolve(basePath, 'node_modules/typescript/lib/lib.es6.d.ts'),
|
||||
path.resolve(basePath, 'node_modules/typescript/lib/lib.dom.d.ts')
|
||||
],
|
||||
'typeRoots': [path.resolve(basePath, 'node_modules/@types')], ...overrideOptions
|
||||
};
|
||||
const {diagnostics} = ng.performCompilation({rootNames: fileNames, options});
|
||||
return diagnostics;
|
||||
}
|
||||
|
||||
function reject(message: string | RegExp, ...files: MockDirectory[]) {
|
||||
const diagnostics = compile([angularFiles, QUICKSTART, ...files]);
|
||||
beforeEach(() => {
|
||||
errorSpy = jasmine.createSpy('consoleError').and.callFake(console.error);
|
||||
basePath = makeTempDir();
|
||||
write = (fileName: string, content: string) => {
|
||||
const dir = path.dirname(fileName);
|
||||
if (dir != '.') {
|
||||
const newDir = path.join(basePath, dir);
|
||||
if (!fs.existsSync(newDir)) fs.mkdirSync(newDir);
|
||||
}
|
||||
fs.writeFileSync(path.join(basePath, fileName), content, {encoding: 'utf-8'});
|
||||
};
|
||||
const ngRootDir = getNgRootDir();
|
||||
const nodeModulesPath = path.resolve(basePath, 'node_modules');
|
||||
fs.mkdirSync(nodeModulesPath);
|
||||
fs.symlinkSync(
|
||||
path.resolve(ngRootDir, 'dist', 'all', '@angular'),
|
||||
path.resolve(nodeModulesPath, '@angular'));
|
||||
fs.symlinkSync(
|
||||
path.resolve(ngRootDir, 'node_modules', 'rxjs'), path.resolve(nodeModulesPath, 'rxjs'));
|
||||
fs.symlinkSync(
|
||||
path.resolve(ngRootDir, 'node_modules', 'typescript'),
|
||||
path.resolve(nodeModulesPath, 'typescript'));
|
||||
});
|
||||
|
||||
function accept(
|
||||
files: {[fileName: string]: string} = {}, overrideOptions: ng.CompilerOptions = {}) {
|
||||
expectNoDiagnostics(compileAndCheck([QUICKSTART, files], overrideOptions));
|
||||
}
|
||||
|
||||
function reject(
|
||||
message: string | RegExp, location: RegExp, files: {[fileName: string]: string},
|
||||
overrideOptions: ng.CompilerOptions = {}) {
|
||||
const diagnostics = compileAndCheck([QUICKSTART, files], overrideOptions);
|
||||
if (!diagnostics || !diagnostics.length) {
|
||||
throw new Error('Expected a diagnostic erorr message');
|
||||
} else {
|
||||
const matches: (d: Diagnostic) => boolean = typeof message === 'string' ?
|
||||
d => d.messageText == message :
|
||||
d => message.test(d.messageText);
|
||||
const matchingDiagnostics = diagnostics.filter(matches);
|
||||
const matches: (d: ng.Diagnostic) => boolean = typeof message === 'string' ?
|
||||
d => ng.isNgDiagnostic(d)&& d.messageText == message :
|
||||
d => ng.isNgDiagnostic(d) && message.test(d.messageText);
|
||||
const matchingDiagnostics = diagnostics.filter(matches) as ng.Diagnostic[];
|
||||
if (!matchingDiagnostics || !matchingDiagnostics.length) {
|
||||
throw new Error(
|
||||
`Expected a diagnostics matching ${message}, received\n ${diagnostics.map(d => d.messageText).join('\n ')}`);
|
||||
}
|
||||
|
||||
const span = matchingDiagnostics[0].span;
|
||||
if (!span) {
|
||||
throw new Error('Expected a sourceSpan');
|
||||
}
|
||||
expect(`${span.start.file.url}@${span.start.line}:${span.start.offset}`).toMatch(location);
|
||||
}
|
||||
}
|
||||
|
||||
it('should accept unmodified QuickStart', () => { accept(); });
|
||||
|
||||
describe('with modified quickstart', () => {
|
||||
function a(template: string) {
|
||||
accept({quickstart: {app: {'app.component.ts': appComponentSource(template)}}});
|
||||
it('should accept unmodified QuickStart with tests for unused variables', () => {
|
||||
accept({}, {
|
||||
strict: true,
|
||||
noUnusedLocals: true,
|
||||
noUnusedParameters: true,
|
||||
});
|
||||
});
|
||||
|
||||
describe('with modified quickstart (fullTemplateTypeCheck: false)', () => {
|
||||
addTests({fullTemplateTypeCheck: false});
|
||||
});
|
||||
|
||||
describe('with modified quickstart (fullTemplateTypeCheck: true)', () => {
|
||||
addTests({fullTemplateTypeCheck: true});
|
||||
});
|
||||
|
||||
function addTests(config: {fullTemplateTypeCheck: boolean}) {
|
||||
function a(template: string) { accept({'src/app.component.html': template}, config); }
|
||||
|
||||
function r(template: string, message: string | RegExp, location: string) {
|
||||
reject(
|
||||
message, new RegExp(`app\.component\.html\@${location}$`),
|
||||
{'src/app.component.html': template}, config);
|
||||
}
|
||||
|
||||
function r(template: string, message: string | RegExp) {
|
||||
reject(message, {quickstart: {app: {'app.component.ts': appComponentSource(template)}}});
|
||||
function rejectOnlyWithFullTemplateTypeCheck(
|
||||
template: string, message: string | RegExp, location: string) {
|
||||
if (config.fullTemplateTypeCheck) {
|
||||
r(template, message, location);
|
||||
} else {
|
||||
a(template);
|
||||
}
|
||||
}
|
||||
|
||||
it('should report an invalid field access',
|
||||
() => { r('{{fame}}', `Property 'fame' does not exist on type 'AppComponent'.`); });
|
||||
it('should report an invalid field access', () => {
|
||||
r('<div>{{fame}}<div>', `Property 'fame' does not exist on type 'AppComponent'.`, '0:5');
|
||||
});
|
||||
it('should reject a reference to a field of a nullable',
|
||||
() => { r('{{maybePerson.name}}', `Object is possibly 'undefined'.`); });
|
||||
() => { r('<div>{{maybePerson.name}}</div>', `Object is possibly 'undefined'.`, '0:5'); });
|
||||
it('should accept a reference to a field of a nullable using using non-null-assert',
|
||||
() => { a('{{maybePerson!.name}}'); });
|
||||
it('should accept a safe property access of a nullable person',
|
||||
() => { a('{{maybePerson?.name}}'); });
|
||||
it('should accept a function call', () => { a('{{getName()}}'); });
|
||||
it('should reject an invalid method', () => {
|
||||
r('{{getFame()}}',
|
||||
`Property 'getFame' does not exist on type 'AppComponent'. Did you mean 'getName'?`);
|
||||
r('<div>{{getFame()}}</div>',
|
||||
`Property 'getFame' does not exist on type 'AppComponent'. Did you mean 'getName'?`, '0:5');
|
||||
});
|
||||
it('should accept a field access of a method result', () => { a('{{getPerson().name}}'); });
|
||||
it('should reject an invalid field reference of a method result',
|
||||
() => { r('{{getPerson().fame}}', `Property 'fame' does not exist on type 'Person'.`); });
|
||||
it('should reject an access to a nullable field of a method result',
|
||||
() => { r('{{getMaybePerson().name}}', `Object is possibly 'undefined'.`); });
|
||||
it('should reject an invalid field reference of a method result', () => {
|
||||
r('<div>{{getPerson().fame}}</div>', `Property 'fame' does not exist on type 'Person'.`,
|
||||
'0:5');
|
||||
});
|
||||
it('should reject an access to a nullable field of a method result', () => {
|
||||
r('<div>{{getMaybePerson().name}}</div>', `Object is possibly 'undefined'.`, '0:5');
|
||||
});
|
||||
it('should accept a nullable assert of a nullable field refernces of a method result',
|
||||
() => { a('{{getMaybePerson()!.name}}'); });
|
||||
it('should accept a safe property access of a nullable field reference of a method result',
|
||||
() => { a('{{getMaybePerson()?.name}}'); });
|
||||
|
||||
it('should report an invalid field access inside of an ng-template', () => {
|
||||
rejectOnlyWithFullTemplateTypeCheck(
|
||||
'<ng-template>{{fame}}</ng-template>',
|
||||
`Property 'fame' does not exist on type 'AppComponent'.`, '0:13');
|
||||
});
|
||||
it('should report an invalid call to a pipe', () => {
|
||||
rejectOnlyWithFullTemplateTypeCheck(
|
||||
'<div>{{"hello" | aPipe}}</div>',
|
||||
`Argument of type '"hello"' is not assignable to parameter of type 'number'.`, '0:5');
|
||||
});
|
||||
it('should report an invalid property on an exportAs directive', () => {
|
||||
rejectOnlyWithFullTemplateTypeCheck(
|
||||
'<div aDir #aDir="aDir">{{aDir.fname}}</div>',
|
||||
`Property 'fname' does not exist on type 'ADirective'. Did you mean 'name'?`, '0:23');
|
||||
});
|
||||
}
|
||||
|
||||
describe('with lowered expressions', () => {
|
||||
it('should not report lowered expressions as errors', () => {
|
||||
expectNoDiagnostics(compile([angularFiles, LOWERING_QUICKSTART]));
|
||||
});
|
||||
it('should not report lowered expressions as errors',
|
||||
() => { expectNoDiagnostics(compileAndCheck([LOWERING_QUICKSTART])); });
|
||||
});
|
||||
});
|
||||
|
||||
function appComponentSource(template: string): string {
|
||||
function appComponentSource(): string {
|
||||
return `
|
||||
import {Component} from '@angular/core';
|
||||
import {Component, Pipe, Directive} from '@angular/core';
|
||||
|
||||
export interface Person {
|
||||
name: string;
|
||||
|
@ -109,7 +214,7 @@ function appComponentSource(template: string): string {
|
|||
}
|
||||
|
||||
@Component({
|
||||
template: '${template}'
|
||||
templateUrl: './app.component.html'
|
||||
})
|
||||
export class AppComponent {
|
||||
name = 'Angular';
|
||||
|
@ -119,40 +224,48 @@ function appComponentSource(template: string): string {
|
|||
|
||||
getName(): string { return this.name; }
|
||||
getPerson(): Person { return this.person; }
|
||||
getMaybePerson(): Person | undefined { this.maybePerson; }
|
||||
getMaybePerson(): Person | undefined { return this.maybePerson; }
|
||||
}
|
||||
|
||||
@Pipe({
|
||||
name: 'aPipe',
|
||||
})
|
||||
export class APipe {
|
||||
transform(n: number): number { return n + 1; }
|
||||
}
|
||||
|
||||
@Directive({
|
||||
selector: '[aDir]',
|
||||
exportAs: 'aDir'
|
||||
})
|
||||
export class ADirective {
|
||||
name = 'ADirective';
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
const QUICKSTART: MockDirectory = {
|
||||
quickstart: {
|
||||
app: {
|
||||
'app.component.ts': appComponentSource('<h1>Hello {{name}}</h1>'),
|
||||
'app.module.ts': `
|
||||
const QUICKSTART = {
|
||||
'src/app.component.ts': appComponentSource(),
|
||||
'src/app.component.html': '<h1>Hello {{name}}</h1>',
|
||||
'src/app.module.ts': `
|
||||
import { NgModule } from '@angular/core';
|
||||
import { toString } from './utils';
|
||||
|
||||
import { AppComponent } from './app.component';
|
||||
import { AppComponent, APipe, ADirective } from './app.component';
|
||||
|
||||
@NgModule({
|
||||
declarations: [ AppComponent ],
|
||||
declarations: [ AppComponent, APipe, ADirective ],
|
||||
bootstrap: [ AppComponent ]
|
||||
})
|
||||
export class AppModule { }
|
||||
`
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const LOWERING_QUICKSTART: MockDirectory = {
|
||||
quickstart: {
|
||||
app: {
|
||||
'app.component.ts': appComponentSource('<h1>Hello {{name}}</h1>'),
|
||||
'app.module.ts': `
|
||||
const LOWERING_QUICKSTART = {
|
||||
'src/app.component.ts': appComponentSource(),
|
||||
'src/app.component.html': '<h1>Hello {{name}}</h1>',
|
||||
'src/app.module.ts': `
|
||||
import { NgModule, Component } from '@angular/core';
|
||||
import { toString } from './utils';
|
||||
|
||||
import { AppComponent } from './app.component';
|
||||
import { AppComponent, APipe, ADirective } from './app.component';
|
||||
|
||||
class Foo {}
|
||||
|
||||
|
@ -165,17 +278,15 @@ const LOWERING_QUICKSTART: MockDirectory = {
|
|||
export class Bar {}
|
||||
|
||||
@NgModule({
|
||||
declarations: [ AppComponent, Bar ],
|
||||
declarations: [ AppComponent, APipe, ADirective, Bar ],
|
||||
bootstrap: [ AppComponent ]
|
||||
})
|
||||
export class AppModule { }
|
||||
`
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function expectNoDiagnostics(diagnostics: Diagnostic[]) {
|
||||
function expectNoDiagnostics(diagnostics: ng.Diagnostics) {
|
||||
if (diagnostics && diagnostics.length) {
|
||||
throw new Error(diagnostics.map(d => `${d.span}: ${d.messageText}`).join('\n'));
|
||||
throw new Error(ng.formatDiagnostics({}, diagnostics));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {CompileDirectiveMetadata, CompileDirectiveSummary, CompileIdentifierMetadata, CompileNgModuleMetadata, CompileNgModuleSummary, CompilePipeMetadata, CompileProviderMetadata, CompileStylesheetMetadata, CompileSummaryKind, CompileTypeMetadata, CompileTypeSummary, componentFactoryName, createHostComponentMeta, flatten, identifierName, sourceUrl, templateSourceUrl} from '../compile_metadata';
|
||||
import {CompileDirectiveMetadata, CompileDirectiveSummary, CompileIdentifierMetadata, CompileNgModuleMetadata, CompileNgModuleSummary, CompilePipeMetadata, CompilePipeSummary, CompileProviderMetadata, CompileStylesheetMetadata, CompileSummaryKind, CompileTypeMetadata, CompileTypeSummary, componentFactoryName, createHostComponentMeta, flatten, identifierName, sourceUrl, templateSourceUrl} from '../compile_metadata';
|
||||
import {CompilerConfig} from '../config';
|
||||
import {MessageBundle} from '../i18n/message_bundle';
|
||||
import {Identifiers, createTokenForExternalReference} from '../identifiers';
|
||||
|
@ -19,8 +19,10 @@ import * as o from '../output/output_ast';
|
|||
import {ParseError} from '../parse_util';
|
||||
import {CompiledStylesheet, StyleCompiler} from '../style_compiler';
|
||||
import {SummaryResolver} from '../summary_resolver';
|
||||
import {TemplateAst} from '../template_parser/template_ast';
|
||||
import {TemplateParser} from '../template_parser/template_parser';
|
||||
import {OutputContext, syntaxError} from '../util';
|
||||
import {TypeCheckCompiler} from '../view_compiler/type_check_compiler';
|
||||
import {ViewCompileResult, ViewCompiler} from '../view_compiler/view_compiler';
|
||||
|
||||
import {AotCompilerHost} from './compiler_host';
|
||||
|
@ -32,11 +34,15 @@ import {createForJitStub, serializeSummaries} from './summary_serializer';
|
|||
import {ngfactoryFilePath, splitTypescriptSuffix, summaryFileName, summaryForJitFileName, summaryForJitName} from './util';
|
||||
|
||||
export class AotCompiler {
|
||||
private _templateAstCache =
|
||||
new Map<StaticSymbol, {template: TemplateAst[], pipes: CompilePipeSummary[]}>();
|
||||
|
||||
constructor(
|
||||
private _config: CompilerConfig, private _host: AotCompilerHost,
|
||||
private _reflector: StaticReflector, private _metadataResolver: CompileMetadataResolver,
|
||||
private _templateParser: TemplateParser, private _styleCompiler: StyleCompiler,
|
||||
private _viewCompiler: ViewCompiler, private _ngModuleCompiler: NgModuleCompiler,
|
||||
private _htmlParser: HtmlParser, private _templateParser: TemplateParser,
|
||||
private _styleCompiler: StyleCompiler, private _viewCompiler: ViewCompiler,
|
||||
private _typeCheckCompiler: TypeCheckCompiler, private _ngModuleCompiler: NgModuleCompiler,
|
||||
private _outputEmitter: OutputEmitter,
|
||||
private _summaryResolver: SummaryResolver<StaticSymbol>, private _localeId: string|null,
|
||||
private _translationFormat: string|null, private _enableSummariesForJit: boolean|null,
|
||||
|
@ -66,9 +72,10 @@ export class AotCompiler {
|
|||
}
|
||||
|
||||
emitAllStubs(analyzeResult: NgAnalyzedModules): GeneratedFile[] {
|
||||
const {files} = analyzeResult;
|
||||
const {files, ngModuleByPipeOrDirective} = analyzeResult;
|
||||
const sourceModules = files.map(
|
||||
file => this._compileStubFile(file.srcUrl, file.directives, file.pipes, file.ngModules));
|
||||
file => this._compileStubFile(
|
||||
file.srcUrl, ngModuleByPipeOrDirective, file.directives, file.pipes, file.ngModules));
|
||||
return flatten(sourceModules);
|
||||
}
|
||||
|
||||
|
@ -112,7 +119,8 @@ export class AotCompiler {
|
|||
}
|
||||
|
||||
private _compileStubFile(
|
||||
srcFileUrl: string, directives: StaticSymbol[], pipes: StaticSymbol[],
|
||||
srcFileUrl: string, ngModuleByPipeOrDirective: Map<StaticSymbol, CompileNgModuleMetadata>,
|
||||
directives: StaticSymbol[], pipes: StaticSymbol[],
|
||||
ngModules: StaticSymbol[]): GeneratedFile[] {
|
||||
const fileSuffix = splitTypescriptSuffix(srcFileUrl, true)[1];
|
||||
const generatedFiles: GeneratedFile[] = [];
|
||||
|
@ -130,10 +138,17 @@ export class AotCompiler {
|
|||
// the generated code)
|
||||
directives.forEach((dirType) => {
|
||||
const compMeta = this._metadataResolver.getDirectiveMetadata(<any>dirType);
|
||||
|
||||
if (!compMeta.isComponent) {
|
||||
return;
|
||||
}
|
||||
const ngModule = ngModuleByPipeOrDirective.get(dirType);
|
||||
if (!ngModule) {
|
||||
throw new Error(
|
||||
`Internal Error: cannot determine the module for component ${identifierName(compMeta.type)}!`);
|
||||
}
|
||||
this._compileComponentTypeCheckBlock(
|
||||
ngFactoryOutputCtx, compMeta, ngModule, ngModule.transitiveModule.directives);
|
||||
|
||||
// Note: compMeta is a component and therefore template is non null.
|
||||
compMeta.template !.externalStylesheets.forEach((stylesheetMeta) => {
|
||||
const styleContext = this._createOutputContext(_stylesModuleUrl(
|
||||
|
@ -285,7 +300,8 @@ export class AotCompiler {
|
|||
ngModule: CompileNgModuleMetadata, fileSuffix: string): void {
|
||||
const hostType = this._metadataResolver.getHostComponentType(compMeta.type.reference);
|
||||
const hostMeta = createHostComponentMeta(
|
||||
hostType, compMeta, this._metadataResolver.getHostComponentViewClass(hostType));
|
||||
hostType, compMeta, this._metadataResolver.getHostComponentViewClass(hostType),
|
||||
this._htmlParser);
|
||||
const hostViewFactoryVar =
|
||||
this._compileComponent(outputCtx, hostMeta, ngModule, [compMeta.type], null, fileSuffix)
|
||||
.viewClassVar;
|
||||
|
@ -320,19 +336,32 @@ export class AotCompiler {
|
|||
[o.StmtModifier.Final, o.StmtModifier.Exported]));
|
||||
}
|
||||
|
||||
private _compileComponent(
|
||||
outputCtx: OutputContext, compMeta: CompileDirectiveMetadata,
|
||||
ngModule: CompileNgModuleMetadata, directiveIdentifiers: CompileIdentifierMetadata[],
|
||||
componentStyles: CompiledStylesheet|null, fileSuffix: string): ViewCompileResult {
|
||||
private _parseTemplate(
|
||||
compMeta: CompileDirectiveMetadata, ngModule: CompileNgModuleMetadata,
|
||||
directiveIdentifiers: CompileIdentifierMetadata[]):
|
||||
{template: TemplateAst[], pipes: CompilePipeSummary[]} {
|
||||
let result = this._templateAstCache.get(compMeta.type.reference);
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
const preserveWhitespaces = compMeta !.template !.preserveWhitespaces;
|
||||
const directives =
|
||||
directiveIdentifiers.map(dir => this._metadataResolver.getDirectiveSummary(dir.reference));
|
||||
const pipes = ngModule.transitiveModule.pipes.map(
|
||||
pipe => this._metadataResolver.getPipeSummary(pipe.reference));
|
||||
|
||||
const preserveWhitespaces = compMeta !.template !.preserveWhitespaces;
|
||||
const {template: parsedTemplate, pipes: usedPipes} = this._templateParser.parse(
|
||||
compMeta, compMeta.template !.template !, directives, pipes, ngModule.schemas,
|
||||
result = this._templateParser.parse(
|
||||
compMeta, compMeta.template !.htmlAst !, directives, pipes, ngModule.schemas,
|
||||
templateSourceUrl(ngModule.type, compMeta, compMeta.template !), preserveWhitespaces);
|
||||
this._templateAstCache.set(compMeta.type.reference, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
private _compileComponent(
|
||||
outputCtx: OutputContext, compMeta: CompileDirectiveMetadata,
|
||||
ngModule: CompileNgModuleMetadata, directiveIdentifiers: CompileIdentifierMetadata[],
|
||||
componentStyles: CompiledStylesheet|null, fileSuffix: string): ViewCompileResult {
|
||||
const {template: parsedTemplate, pipes: usedPipes} =
|
||||
this._parseTemplate(compMeta, ngModule, directiveIdentifiers);
|
||||
const stylesExpr = componentStyles ? o.variable(componentStyles.stylesVar) : o.literalArr([]);
|
||||
const viewResult = this._viewCompiler.compileComponent(
|
||||
outputCtx, compMeta, parsedTemplate, stylesExpr, usedPipes);
|
||||
|
@ -344,6 +373,14 @@ export class AotCompiler {
|
|||
return viewResult;
|
||||
}
|
||||
|
||||
private _compileComponentTypeCheckBlock(
|
||||
outputCtx: OutputContext, compMeta: CompileDirectiveMetadata,
|
||||
ngModule: CompileNgModuleMetadata, directiveIdentifiers: CompileIdentifierMetadata[]) {
|
||||
const {template: parsedTemplate, pipes: usedPipes} =
|
||||
this._parseTemplate(compMeta, ngModule, directiveIdentifiers);
|
||||
this._typeCheckCompiler.compileComponent(outputCtx, compMeta, parsedTemplate, usedPipes);
|
||||
}
|
||||
|
||||
private _createOutputContext(genFilePath: string): OutputContext {
|
||||
const importExpr = (symbol: StaticSymbol, typeParams: o.Type[] | null = null) => {
|
||||
if (!(symbol instanceof StaticSymbol)) {
|
||||
|
|
|
@ -24,6 +24,7 @@ import {StyleCompiler} from '../style_compiler';
|
|||
import {TemplateParser} from '../template_parser/template_parser';
|
||||
import {UrlResolver} from '../url_resolver';
|
||||
import {syntaxError} from '../util';
|
||||
import {TypeCheckCompiler} from '../view_compiler/type_check_compiler';
|
||||
import {ViewCompiler} from '../view_compiler/view_compiler';
|
||||
|
||||
import {AotCompiler} from './compiler';
|
||||
|
@ -81,9 +82,11 @@ export function createAotCompiler(compilerHost: AotCompilerHost, options: AotCom
|
|||
console, symbolCache, staticReflector);
|
||||
// TODO(vicb): do not pass options.i18nFormat here
|
||||
const viewCompiler = new ViewCompiler(config, staticReflector, elementSchemaRegistry);
|
||||
const typeCheckCompiler = new TypeCheckCompiler(options, staticReflector);
|
||||
const compiler = new AotCompiler(
|
||||
config, compilerHost, staticReflector, resolver, tmplParser, new StyleCompiler(urlResolver),
|
||||
viewCompiler, new NgModuleCompiler(staticReflector), new TypeScriptEmitter(), summaryResolver,
|
||||
config, compilerHost, staticReflector, resolver, htmlParser, tmplParser,
|
||||
new StyleCompiler(urlResolver), viewCompiler, typeCheckCompiler,
|
||||
new NgModuleCompiler(staticReflector), new TypeScriptEmitter(), summaryResolver,
|
||||
options.locale || null, options.i18nFormat || null, options.enableSummariesForJit || null,
|
||||
symbolResolver);
|
||||
return {compiler, reflector: staticReflector};
|
||||
|
|
|
@ -16,4 +16,5 @@ export interface AotCompilerOptions {
|
|||
enableLegacyTemplate?: boolean;
|
||||
enableSummariesForJit?: boolean;
|
||||
preserveWhitespaces?: boolean;
|
||||
fullTemplateTypeCheck?: boolean;
|
||||
}
|
||||
|
|
|
@ -9,6 +9,9 @@
|
|||
import {StaticSymbol} from './aot/static_symbol';
|
||||
import {ChangeDetectionStrategy, SchemaMetadata, Type, ViewEncapsulation} from './core';
|
||||
import {LifecycleHooks} from './lifecycle_reflector';
|
||||
import * as html from './ml_parser/ast';
|
||||
import {HtmlParser} from './ml_parser/html_parser';
|
||||
import {ParseTreeResult as HtmlParseTreeResult} from './ml_parser/parser';
|
||||
import {CssSelector} from './selector';
|
||||
import {splitAtColon, stringify} from './util';
|
||||
|
||||
|
@ -244,6 +247,7 @@ export class CompileTemplateMetadata {
|
|||
encapsulation: ViewEncapsulation|null;
|
||||
template: string|null;
|
||||
templateUrl: string|null;
|
||||
htmlAst: HtmlParseTreeResult|null;
|
||||
isInline: boolean;
|
||||
styles: string[];
|
||||
styleUrls: string[];
|
||||
|
@ -252,11 +256,13 @@ export class CompileTemplateMetadata {
|
|||
ngContentSelectors: string[];
|
||||
interpolation: [string, string]|null;
|
||||
preserveWhitespaces: boolean;
|
||||
constructor({encapsulation, template, templateUrl, styles, styleUrls, externalStylesheets,
|
||||
animations, ngContentSelectors, interpolation, isInline, preserveWhitespaces}: {
|
||||
constructor({encapsulation, template, templateUrl, htmlAst, styles, styleUrls,
|
||||
externalStylesheets, animations, ngContentSelectors, interpolation, isInline,
|
||||
preserveWhitespaces}: {
|
||||
encapsulation: ViewEncapsulation | null,
|
||||
template: string|null,
|
||||
templateUrl: string|null,
|
||||
htmlAst: HtmlParseTreeResult|null,
|
||||
styles: string[],
|
||||
styleUrls: string[],
|
||||
externalStylesheets: CompileStylesheetMetadata[],
|
||||
|
@ -269,6 +275,7 @@ export class CompileTemplateMetadata {
|
|||
this.encapsulation = encapsulation;
|
||||
this.template = template;
|
||||
this.templateUrl = templateUrl;
|
||||
this.htmlAst = htmlAst;
|
||||
this.styles = _normalizeArray(styles);
|
||||
this.styleUrls = _normalizeArray(styleUrls);
|
||||
this.externalStylesheets = _normalizeArray(externalStylesheets);
|
||||
|
@ -503,15 +510,18 @@ export class CompileDirectiveMetadata {
|
|||
*/
|
||||
export function createHostComponentMeta(
|
||||
hostTypeReference: any, compMeta: CompileDirectiveMetadata,
|
||||
hostViewType: StaticSymbol | ProxyClass): CompileDirectiveMetadata {
|
||||
hostViewType: StaticSymbol | ProxyClass, htmlParser: HtmlParser): CompileDirectiveMetadata {
|
||||
const template = CssSelector.parse(compMeta.selector !)[0].getMatchingElementTemplate();
|
||||
const templateUrl = '';
|
||||
const htmlAst = htmlParser.parse(template, templateUrl);
|
||||
return CompileDirectiveMetadata.create({
|
||||
isHost: true,
|
||||
type: {reference: hostTypeReference, diDeps: [], lifecycleHooks: []},
|
||||
template: new CompileTemplateMetadata({
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
template: template,
|
||||
templateUrl: '',
|
||||
template,
|
||||
templateUrl,
|
||||
htmlAst,
|
||||
styles: [],
|
||||
styleUrls: [],
|
||||
ngContentSelectors: [],
|
||||
|
|
|
@ -30,7 +30,8 @@ export class CompilerConfig {
|
|||
jitDevMode?: boolean,
|
||||
missingTranslation?: MissingTranslationStrategy,
|
||||
enableLegacyTemplate?: boolean,
|
||||
preserveWhitespaces?: boolean
|
||||
preserveWhitespaces?: boolean,
|
||||
fullTemplateTypeCheck?: boolean
|
||||
} = {}) {
|
||||
this.defaultEncapsulation = defaultEncapsulation;
|
||||
this.useJit = !!useJit;
|
||||
|
|
|
@ -150,7 +150,8 @@ export class DirectiveNormalizer {
|
|||
return new CompileTemplateMetadata({
|
||||
encapsulation,
|
||||
template,
|
||||
templateUrl: templateAbsUrl, styles, styleUrls,
|
||||
templateUrl: templateAbsUrl,
|
||||
htmlAst: rootNodesAndErrors, styles, styleUrls,
|
||||
ngContentSelectors: visitor.ngContentSelectors,
|
||||
animations: prenormData.animations,
|
||||
interpolation: prenormData.interpolation, isInline,
|
||||
|
@ -168,6 +169,7 @@ export class DirectiveNormalizer {
|
|||
encapsulation: templateMeta.encapsulation,
|
||||
template: templateMeta.template,
|
||||
templateUrl: templateMeta.templateUrl,
|
||||
htmlAst: templateMeta.htmlAst,
|
||||
styles: templateMeta.styles,
|
||||
styleUrls: templateMeta.styleUrls,
|
||||
externalStylesheets: externalStylesheets,
|
||||
|
|
|
@ -11,6 +11,7 @@ import {CompileReflector} from '../compile_reflector';
|
|||
import {CompilerConfig} from '../config';
|
||||
import {Type} from '../core';
|
||||
import {CompileMetadataResolver} from '../metadata_resolver';
|
||||
import {HtmlParser} from '../ml_parser/html_parser';
|
||||
import {NgModuleCompiler} from '../ng_module_compiler';
|
||||
import * as ir from '../output/output_ast';
|
||||
import {interpretStatements} from '../output/output_interpreter';
|
||||
|
@ -43,11 +44,11 @@ export class JitCompiler {
|
|||
private _sharedStylesheetCount = 0;
|
||||
|
||||
constructor(
|
||||
private _metadataResolver: CompileMetadataResolver, private _templateParser: TemplateParser,
|
||||
private _styleCompiler: StyleCompiler, private _viewCompiler: ViewCompiler,
|
||||
private _ngModuleCompiler: NgModuleCompiler, private _summaryResolver: SummaryResolver<Type>,
|
||||
private _reflector: CompileReflector, private _compilerConfig: CompilerConfig,
|
||||
private _console: Console,
|
||||
private _metadataResolver: CompileMetadataResolver, private _htmlParser: HtmlParser,
|
||||
private _templateParser: TemplateParser, private _styleCompiler: StyleCompiler,
|
||||
private _viewCompiler: ViewCompiler, private _ngModuleCompiler: NgModuleCompiler,
|
||||
private _summaryResolver: SummaryResolver<Type>, private _reflector: CompileReflector,
|
||||
private _compilerConfig: CompilerConfig, private _console: Console,
|
||||
private getExtraNgModuleProviders: (ngModule: any) => CompileProviderMetadata[]) {}
|
||||
|
||||
compileModuleSync(moduleType: Type): object {
|
||||
|
@ -228,7 +229,7 @@ export class JitCompiler {
|
|||
|
||||
const hostClass = this._metadataResolver.getHostComponentType(compType);
|
||||
const hostMeta = createHostComponentMeta(
|
||||
hostClass, compMeta, (compMeta.componentFactory as any).viewDefFactory);
|
||||
hostClass, compMeta, (compMeta.componentFactory as any).viewDefFactory, this._htmlParser);
|
||||
compiledTemplate =
|
||||
new CompiledTemplate(true, compMeta.type, hostMeta, ngModule, [compMeta.type]);
|
||||
this._compiledHostTemplateCache.set(compType, compiledTemplate);
|
||||
|
|
|
@ -261,6 +261,7 @@ export class CompileMetadataResolver {
|
|||
encapsulation: noUndefined(compMeta.encapsulation),
|
||||
template: noUndefined(compMeta.template),
|
||||
templateUrl: noUndefined(compMeta.templateUrl),
|
||||
htmlAst: null,
|
||||
styles: compMeta.styles || [],
|
||||
styleUrls: compMeta.styleUrls || [],
|
||||
animations: animations || [],
|
||||
|
|
|
@ -1141,8 +1141,8 @@ export function importType(
|
|||
}
|
||||
|
||||
export function expressionType(
|
||||
expr: Expression, typeModifiers: TypeModifier[] | null = null): ExpressionType|null {
|
||||
return expr != null ? new ExpressionType(expr, typeModifiers) ! : null;
|
||||
expr: Expression, typeModifiers: TypeModifier[] | null = null): ExpressionType {
|
||||
return new ExpressionType(expr, typeModifiers);
|
||||
}
|
||||
|
||||
export function literalArr(
|
||||
|
|
|
@ -101,8 +101,9 @@ export class TemplateParser {
|
|||
public transforms: TemplateAstVisitor[]) {}
|
||||
|
||||
parse(
|
||||
component: CompileDirectiveMetadata, template: string, directives: CompileDirectiveSummary[],
|
||||
pipes: CompilePipeSummary[], schemas: SchemaMetadata[], templateUrl: string,
|
||||
component: CompileDirectiveMetadata, template: string|ParseTreeResult,
|
||||
directives: CompileDirectiveSummary[], pipes: CompilePipeSummary[], schemas: SchemaMetadata[],
|
||||
templateUrl: string,
|
||||
preserveWhitespaces: boolean): {template: TemplateAst[], pipes: CompilePipeSummary[]} {
|
||||
const result = this.tryParse(
|
||||
component, template, directives, pipes, schemas, templateUrl, preserveWhitespaces);
|
||||
|
@ -126,11 +127,13 @@ export class TemplateParser {
|
|||
}
|
||||
|
||||
tryParse(
|
||||
component: CompileDirectiveMetadata, template: string, directives: CompileDirectiveSummary[],
|
||||
pipes: CompilePipeSummary[], schemas: SchemaMetadata[], templateUrl: string,
|
||||
preserveWhitespaces: boolean): TemplateParseResult {
|
||||
let htmlParseResult = this._htmlParser !.parse(
|
||||
template, templateUrl, true, this.getInterpolationConfig(component));
|
||||
component: CompileDirectiveMetadata, template: string|ParseTreeResult,
|
||||
directives: CompileDirectiveSummary[], pipes: CompilePipeSummary[], schemas: SchemaMetadata[],
|
||||
templateUrl: string, preserveWhitespaces: boolean): TemplateParseResult {
|
||||
let htmlParseResult = typeof template === 'string' ?
|
||||
this._htmlParser !.parse(
|
||||
template, templateUrl, true, this.getInterpolationConfig(component)) :
|
||||
template;
|
||||
|
||||
if (!preserveWhitespaces) {
|
||||
htmlParseResult = removeWhitespaces(htmlParseResult);
|
||||
|
|
|
@ -0,0 +1,283 @@
|
|||
/**
|
||||
* @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 {AotCompilerOptions} from '../aot/compiler_options';
|
||||
import {StaticReflector} from '../aot/static_reflector';
|
||||
import {StaticSymbol} from '../aot/static_symbol';
|
||||
import {CompileDiDependencyMetadata, CompileDirectiveMetadata, CompilePipeSummary, viewClassName} from '../compile_metadata';
|
||||
import {BuiltinConverter, EventHandlerVars, LocalResolver, convertActionBinding, convertPropertyBinding, convertPropertyBindingBuiltins} from '../compiler_util/expression_converter';
|
||||
import {AST, ASTWithSource, Interpolation} from '../expression_parser/ast';
|
||||
import {Identifiers} from '../identifiers';
|
||||
import * as o from '../output/output_ast';
|
||||
import {convertValueToOutputAst} from '../output/value_util';
|
||||
import {ParseSourceSpan} from '../parse_util';
|
||||
import {AttrAst, BoundDirectivePropertyAst, BoundElementPropertyAst, BoundEventAst, BoundTextAst, DirectiveAst, ElementAst, EmbeddedTemplateAst, NgContentAst, PropertyBindingType, ProviderAst, ProviderAstType, QueryMatch, ReferenceAst, TemplateAst, TemplateAstVisitor, TextAst, VariableAst, templateVisitAll} from '../template_parser/template_ast';
|
||||
import {OutputContext} from '../util';
|
||||
|
||||
|
||||
/**
|
||||
* Generates code that is used to type check templates.
|
||||
*/
|
||||
export class TypeCheckCompiler {
|
||||
constructor(private options: AotCompilerOptions, private reflector: StaticReflector) {}
|
||||
|
||||
compileComponent(
|
||||
outputCtx: OutputContext, component: CompileDirectiveMetadata, template: TemplateAst[],
|
||||
usedPipes: CompilePipeSummary[]): void {
|
||||
const pipes = new Map<string, StaticSymbol>();
|
||||
usedPipes.forEach(p => pipes.set(p.name, p.type.reference));
|
||||
let embeddedViewCount = 0;
|
||||
const viewBuilderFactory = (parent: ViewBuilder | null): ViewBuilder => {
|
||||
const embeddedViewIndex = embeddedViewCount++;
|
||||
return new ViewBuilder(
|
||||
this.options, this.reflector, outputCtx, parent, component.type.reference,
|
||||
embeddedViewIndex, pipes, viewBuilderFactory);
|
||||
};
|
||||
|
||||
const visitor = viewBuilderFactory(null);
|
||||
visitor.visitAll([], template);
|
||||
|
||||
outputCtx.statements.push(...visitor.build());
|
||||
}
|
||||
}
|
||||
|
||||
interface ViewBuilderFactory {
|
||||
(parent: ViewBuilder): ViewBuilder;
|
||||
}
|
||||
|
||||
// Note: This is used as key in Map and should therefore be
|
||||
// unique per value.
|
||||
type OutputVarType = o.BuiltinTypeName | StaticSymbol;
|
||||
|
||||
interface Expression {
|
||||
context: OutputVarType;
|
||||
sourceSpan: ParseSourceSpan;
|
||||
value: AST;
|
||||
}
|
||||
|
||||
class ViewBuilder implements TemplateAstVisitor, LocalResolver {
|
||||
private outputVarTypes = new Map<string, OutputVarType>();
|
||||
private outputVarNames = new Map<OutputVarType, string>();
|
||||
private refOutputVars = new Map<string, OutputVarType>();
|
||||
private variables: VariableAst[] = [];
|
||||
private children: ViewBuilder[] = [];
|
||||
private updates: Expression[] = [];
|
||||
private actions: Expression[] = [];
|
||||
|
||||
constructor(
|
||||
private options: AotCompilerOptions, private reflector: StaticReflector,
|
||||
private outputCtx: OutputContext, private parent: ViewBuilder|null,
|
||||
private component: StaticSymbol, private embeddedViewIndex: number,
|
||||
private pipes: Map<string, StaticSymbol>, private viewBuilderFactory: ViewBuilderFactory) {}
|
||||
|
||||
private getOrAddOutputVar(type: o.BuiltinTypeName|StaticSymbol): string {
|
||||
let varName = this.outputVarNames.get(type);
|
||||
if (!varName) {
|
||||
varName = `_v${this.outputVarNames.size}`;
|
||||
this.outputVarNames.set(type, varName);
|
||||
this.outputVarTypes.set(varName, type);
|
||||
}
|
||||
return varName;
|
||||
}
|
||||
|
||||
visitAll(variables: VariableAst[], astNodes: TemplateAst[]) {
|
||||
this.variables = variables;
|
||||
templateVisitAll(this, astNodes);
|
||||
}
|
||||
|
||||
build(targetStatements: o.Statement[] = []): o.Statement[] {
|
||||
this.children.forEach((child) => child.build(targetStatements));
|
||||
|
||||
const viewStmts: o.Statement[] = [];
|
||||
let bindingCount = 0;
|
||||
this.updates.forEach((expression) => {
|
||||
const {sourceSpan, context, value} = this.preprocessUpdateExpression(expression);
|
||||
const bindingId = `${bindingCount++}`;
|
||||
const nameResolver = context === this.component ? this : null;
|
||||
const {stmts, currValExpr} = convertPropertyBinding(
|
||||
nameResolver, o.variable(this.getOrAddOutputVar(context)), value, bindingId);
|
||||
stmts.push(new o.ExpressionStatement(currValExpr));
|
||||
viewStmts.push(...stmts.map(
|
||||
(stmt: o.Statement) => o.applySourceSpanToStatementIfNeeded(stmt, sourceSpan)));
|
||||
});
|
||||
|
||||
this.actions.forEach(({sourceSpan, context, value}) => {
|
||||
const bindingId = `${bindingCount++}`;
|
||||
const nameResolver = context === this.component ? this : null;
|
||||
const {stmts} = convertActionBinding(
|
||||
nameResolver, o.variable(this.getOrAddOutputVar(context)), value, bindingId);
|
||||
viewStmts.push(...stmts.map(
|
||||
(stmt: o.Statement) => o.applySourceSpanToStatementIfNeeded(stmt, sourceSpan)));
|
||||
});
|
||||
|
||||
const viewName = `_View_${this.component.name}_${this.embeddedViewIndex}`;
|
||||
const params: o.FnParam[] = [];
|
||||
this.outputVarNames.forEach((varName, varType) => {
|
||||
const outputType = varType instanceof StaticSymbol ?
|
||||
o.expressionType(this.outputCtx.importExpr(varType)) :
|
||||
new o.BuiltinType(varType);
|
||||
params.push(new o.FnParam(varName, outputType));
|
||||
});
|
||||
|
||||
const viewFactory = new o.DeclareFunctionStmt(viewName, params, viewStmts);
|
||||
targetStatements.push(viewFactory);
|
||||
return targetStatements;
|
||||
}
|
||||
|
||||
visitBoundText(ast: BoundTextAst, context: any): any {
|
||||
const astWithSource = <ASTWithSource>ast.value;
|
||||
const inter = <Interpolation>astWithSource.ast;
|
||||
|
||||
inter.expressions.forEach(
|
||||
(expr) =>
|
||||
this.updates.push({context: this.component, value: expr, sourceSpan: ast.sourceSpan}));
|
||||
}
|
||||
|
||||
visitEmbeddedTemplate(ast: EmbeddedTemplateAst, context: any): any {
|
||||
this.visitElementOrTemplate(ast);
|
||||
// Note: The old view compiler used to use an `any` type
|
||||
// for the context in any embedded view.
|
||||
// We keep this behaivor behind a flag for now.
|
||||
if (this.options.fullTemplateTypeCheck) {
|
||||
const childVisitor = this.viewBuilderFactory(this);
|
||||
this.children.push(childVisitor);
|
||||
childVisitor.visitAll(ast.variables, ast.children);
|
||||
}
|
||||
}
|
||||
|
||||
visitElement(ast: ElementAst, context: any): any {
|
||||
this.visitElementOrTemplate(ast);
|
||||
|
||||
let inputDefs: o.Expression[] = [];
|
||||
let updateRendererExpressions: Expression[] = [];
|
||||
let outputDefs: o.Expression[] = [];
|
||||
ast.inputs.forEach((inputAst) => {
|
||||
this.updates.push(
|
||||
{context: this.component, value: inputAst.value, sourceSpan: inputAst.sourceSpan});
|
||||
});
|
||||
|
||||
templateVisitAll(this, ast.children);
|
||||
}
|
||||
|
||||
private visitElementOrTemplate(ast: {
|
||||
outputs: BoundEventAst[],
|
||||
directives: DirectiveAst[],
|
||||
references: ReferenceAst[],
|
||||
}) {
|
||||
ast.directives.forEach((dirAst) => { this.visitDirective(dirAst); });
|
||||
|
||||
ast.references.forEach((ref) => {
|
||||
let outputVarType: OutputVarType = null !;
|
||||
// Note: The old view compiler used to use an `any` type
|
||||
// for directives exposed via `exportAs`.
|
||||
// We keep this behaivor behind a flag for now.
|
||||
if (ref.value && ref.value.identifier && this.options.fullTemplateTypeCheck) {
|
||||
outputVarType = ref.value.identifier.reference;
|
||||
} else {
|
||||
outputVarType = o.BuiltinTypeName.Dynamic;
|
||||
}
|
||||
this.refOutputVars.set(ref.name, outputVarType);
|
||||
});
|
||||
ast.outputs.forEach((outputAst) => {
|
||||
this.actions.push(
|
||||
{context: this.component, value: outputAst.handler, sourceSpan: outputAst.sourceSpan});
|
||||
});
|
||||
}
|
||||
|
||||
visitDirective(dirAst: DirectiveAst) {
|
||||
const dirType = dirAst.directive.type.reference;
|
||||
dirAst.inputs.forEach(
|
||||
(input) => this.updates.push(
|
||||
{context: this.component, value: input.value, sourceSpan: input.sourceSpan}));
|
||||
// Note: The old view compiler used to use an `any` type
|
||||
// for expressions in host properties / events.
|
||||
// We keep this behaivor behind a flag for now.
|
||||
if (this.options.fullTemplateTypeCheck) {
|
||||
dirAst.hostProperties.forEach(
|
||||
(inputAst) => this.updates.push(
|
||||
{context: dirType, value: inputAst.value, sourceSpan: inputAst.sourceSpan}));
|
||||
dirAst.hostEvents.forEach((hostEventAst) => this.actions.push({
|
||||
context: dirType,
|
||||
value: hostEventAst.handler,
|
||||
sourceSpan: hostEventAst.sourceSpan
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
getLocal(name: string): o.Expression|null {
|
||||
if (name == EventHandlerVars.event.name) {
|
||||
return o.variable(this.getOrAddOutputVar(o.BuiltinTypeName.Dynamic));
|
||||
}
|
||||
for (let currBuilder: ViewBuilder|null = this; currBuilder; currBuilder = currBuilder.parent) {
|
||||
let outputVarType: OutputVarType|undefined;
|
||||
// check references
|
||||
outputVarType = currBuilder.refOutputVars.get(name);
|
||||
if (outputVarType == null) {
|
||||
// check variables
|
||||
const varAst = currBuilder.variables.find((varAst) => varAst.name === name);
|
||||
if (varAst) {
|
||||
outputVarType = o.BuiltinTypeName.Dynamic;
|
||||
}
|
||||
}
|
||||
if (outputVarType != null) {
|
||||
return o.variable(this.getOrAddOutputVar(outputVarType));
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private pipeOutputVar(name: string): string {
|
||||
const pipe = this.pipes.get(name);
|
||||
if (!pipe) {
|
||||
throw new Error(
|
||||
`Illegal State: Could not find pipe ${name} in template of ${this.component}`);
|
||||
}
|
||||
return this.getOrAddOutputVar(pipe);
|
||||
}
|
||||
|
||||
private preprocessUpdateExpression(expression: Expression): Expression {
|
||||
return {
|
||||
sourceSpan: expression.sourceSpan,
|
||||
context: expression.context,
|
||||
value: convertPropertyBindingBuiltins(
|
||||
{
|
||||
createLiteralArrayConverter: (argCount: number) => (args: o.Expression[]) =>
|
||||
o.literalArr(args),
|
||||
createLiteralMapConverter: (keys: {key: string, quoted: boolean}[]) =>
|
||||
(values: o.Expression[]) => {
|
||||
const entries = keys.map((k, i) => ({
|
||||
key: k.key,
|
||||
value: values[i],
|
||||
quoted: k.quoted,
|
||||
}));
|
||||
return o.literalMap(entries);
|
||||
},
|
||||
createPipeConverter: (name: string, argCount: number) => (args: o.Expression[]) => {
|
||||
// Note: The old view compiler used to use an `any` type
|
||||
// for pipe calls.
|
||||
// We keep this behaivor behind a flag for now.
|
||||
if (this.options.fullTemplateTypeCheck) {
|
||||
return o.variable(this.pipeOutputVar(name)).callMethod('transform', args);
|
||||
} else {
|
||||
return o.variable(this.getOrAddOutputVar(o.BuiltinTypeName.Dynamic));
|
||||
}
|
||||
},
|
||||
},
|
||||
expression.value)
|
||||
};
|
||||
}
|
||||
|
||||
visitNgContent(ast: NgContentAst, context: any): any {}
|
||||
visitText(ast: TextAst, context: any): any {}
|
||||
visitDirectiveProperty(ast: BoundDirectivePropertyAst, context: any): any {}
|
||||
visitReference(ast: ReferenceAst, context: any): any {}
|
||||
visitVariable(ast: VariableAst, context: any): any {}
|
||||
visitEvent(ast: BoundEventAst, context: any): any {}
|
||||
visitElementProperty(ast: BoundElementPropertyAst, context: any): any {}
|
||||
visitAttr(ast: AttrAst, context: any): any {}
|
||||
}
|
|
@ -92,6 +92,7 @@ function compileTemplateMetadata({encapsulation, template, templateUrl, styles,
|
|||
encapsulation: encapsulation || null,
|
||||
template: template || null,
|
||||
templateUrl: templateUrl || null,
|
||||
htmlAst: null,
|
||||
styles: styles || [],
|
||||
styleUrls: styleUrls || [],
|
||||
externalStylesheets: externalStylesheets || [],
|
||||
|
|
|
@ -101,6 +101,7 @@ function compileTemplateMetadata({encapsulation, template, templateUrl, styles,
|
|||
encapsulation: noUndefined(encapsulation),
|
||||
template: noUndefined(template),
|
||||
templateUrl: noUndefined(templateUrl),
|
||||
htmlAst: null,
|
||||
styles: styles || [],
|
||||
styleUrls: styleUrls || [],
|
||||
externalStylesheets: externalStylesheets || [],
|
||||
|
@ -372,6 +373,7 @@ export function main() {
|
|||
animations: [],
|
||||
template: null,
|
||||
templateUrl: null,
|
||||
htmlAst: null,
|
||||
ngContentSelectors: [],
|
||||
externalStylesheets: [],
|
||||
styleUrls: [],
|
||||
|
|
|
@ -34,12 +34,13 @@ export class CompilerImpl implements Compiler {
|
|||
private _delegate: JitCompiler;
|
||||
constructor(
|
||||
private _injector: Injector, private _metadataResolver: CompileMetadataResolver,
|
||||
templateParser: TemplateParser, styleCompiler: StyleCompiler, viewCompiler: ViewCompiler,
|
||||
ngModuleCompiler: NgModuleCompiler, summaryResolver: SummaryResolver<Type<any>>,
|
||||
compileReflector: CompileReflector, compilerConfig: CompilerConfig, console: Console) {
|
||||
htmlParser: HtmlParser, templateParser: TemplateParser, styleCompiler: StyleCompiler,
|
||||
viewCompiler: ViewCompiler, ngModuleCompiler: NgModuleCompiler,
|
||||
summaryResolver: SummaryResolver<Type<any>>, compileReflector: CompileReflector,
|
||||
compilerConfig: CompilerConfig, console: Console) {
|
||||
this._delegate = new JitCompiler(
|
||||
_metadataResolver, templateParser, styleCompiler, viewCompiler, ngModuleCompiler,
|
||||
summaryResolver, compileReflector, compilerConfig, console,
|
||||
_metadataResolver, htmlParser, templateParser, styleCompiler, viewCompiler,
|
||||
ngModuleCompiler, summaryResolver, compileReflector, compilerConfig, console,
|
||||
this.getExtraNgModuleProviders.bind(this));
|
||||
}
|
||||
|
||||
|
@ -141,7 +142,7 @@ export const COMPILER_PROVIDERS = <StaticProvider[]>[
|
|||
{ provide: NgModuleCompiler, deps: [CompileReflector] },
|
||||
{ provide: CompilerConfig, useValue: new CompilerConfig()},
|
||||
{ provide: Compiler, useClass: CompilerImpl, deps: [Injector, CompileMetadataResolver,
|
||||
TemplateParser, StyleCompiler,
|
||||
HtmlParser, TemplateParser, StyleCompiler,
|
||||
ViewCompiler, NgModuleCompiler,
|
||||
SummaryResolver, CompileReflector, CompilerConfig,
|
||||
Console]},
|
||||
|
|
Loading…
Reference in New Issue