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 {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 {DiagnosticTemplateInfo, ExpressionDiagnostic, getExpressionDiagnostics, getExpressionScope, getTemplateExpressionDiagnostics} from './diagnostics/expression_diagnostics';
|
||||||
export {AstType, DiagnosticKind, ExpressionDiagnosticsContext, TypeDiagnostic} from './diagnostics/expression_type';
|
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';
|
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>;
|
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 {
|
export function formatDiagnostics(options: api.CompilerOptions, diags: Diagnostics): string {
|
||||||
if (diags && diags.length) {
|
if (diags && diags.length) {
|
||||||
const tsFormatHost: ts.FormatDiagnosticsHost = {
|
const tsFormatHost: ts.FormatDiagnosticsHost = {
|
||||||
|
@ -31,7 +27,7 @@ export function formatDiagnostics(options: api.CompilerOptions, diags: Diagnosti
|
||||||
};
|
};
|
||||||
return diags
|
return diags
|
||||||
.map(d => {
|
.map(d => {
|
||||||
if (isTsDiagnostic(d)) {
|
if (api.isTsDiagnostic(d)) {
|
||||||
return ts.formatDiagnostics([d], tsFormatHost);
|
return ts.formatDiagnostics([d], tsFormatHost);
|
||||||
} else {
|
} else {
|
||||||
let res = ts.DiagnosticCategory[d.category];
|
let res = ts.DiagnosticCategory[d.category];
|
||||||
|
|
|
@ -21,6 +21,14 @@ export interface Diagnostic {
|
||||||
source: 'angular';
|
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 {
|
export interface CompilerOptions extends ts.CompilerOptions {
|
||||||
// Absolute path to a directory where generated file structure is written.
|
// Absolute path to a directory where generated file structure is written.
|
||||||
// If unspecified, generated files will be written alongside sources.
|
// If unspecified, generated files will be written alongside sources.
|
||||||
|
@ -73,6 +81,10 @@ export interface CompilerOptions extends ts.CompilerOptions {
|
||||||
// Default is true.
|
// Default is true.
|
||||||
generateCodeForLibraries?: boolean;
|
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
|
// Insert JSDoc type annotations needed by Closure Compiler
|
||||||
annotateForClosureCompiler?: boolean;
|
annotateForClosureCompiler?: boolean;
|
||||||
|
|
||||||
|
|
|
@ -6,20 +6,19 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* 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 fs from 'fs';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
import {BaseAotCompilerHost} from '../compiler_host';
|
import {BaseAotCompilerHost} from '../compiler_host';
|
||||||
import {TypeChecker} from '../diagnostics/check_types';
|
import {TypeCheckHost, translateDiagnostics} from '../diagnostics/translate_diagnostics';
|
||||||
import {createBundleIndexHost} from '../metadata/index';
|
import {createBundleIndexHost} from '../metadata/index';
|
||||||
|
|
||||||
import {CompilerHost, CompilerOptions, CustomTransformers, DEFAULT_ERROR_CODE, Diagnostic, EmitFlags, Program, SOURCE, TsEmitArguments, TsEmitCallback} from './api';
|
import {CompilerHost, CompilerOptions, CustomTransformers, DEFAULT_ERROR_CODE, Diagnostic, EmitFlags, Program, SOURCE, TsEmitArguments, TsEmitCallback} from './api';
|
||||||
import {LowerMetadataCache, getExpressionLoweringTransformFactory} from './lower_expressions';
|
import {LowerMetadataCache, getExpressionLoweringTransformFactory} from './lower_expressions';
|
||||||
import {getAngularEmitterTransformFactory} from './node_emitter_transform';
|
import {getAngularEmitterTransformFactory} from './node_emitter_transform';
|
||||||
|
import {GENERATED_FILES} from './util';
|
||||||
const GENERATED_FILES = /(.*?)\.(ngfactory|shim\.ngstyle|ngstyle|ngsummary)\.(js|d\.ts|ts)$/;
|
|
||||||
|
|
||||||
const emptyModules: NgAnalyzedModules = {
|
const emptyModules: NgAnalyzedModules = {
|
||||||
ngModules: [],
|
ngModules: [],
|
||||||
|
@ -45,12 +44,11 @@ class AngularCompilerProgram implements Program {
|
||||||
private _structuralDiagnostics: Diagnostic[] = [];
|
private _structuralDiagnostics: Diagnostic[] = [];
|
||||||
private _stubs: GeneratedFile[]|undefined;
|
private _stubs: GeneratedFile[]|undefined;
|
||||||
private _stubFiles: string[]|undefined;
|
private _stubFiles: string[]|undefined;
|
||||||
private _programWithStubsHost: ts.CompilerHost|undefined;
|
private _programWithStubsHost: ts.CompilerHost&TypeCheckHost|undefined;
|
||||||
private _programWithStubs: ts.Program|undefined;
|
private _programWithStubs: ts.Program|undefined;
|
||||||
private _generatedFiles: GeneratedFile[]|undefined;
|
private _generatedFiles: GeneratedFile[]|undefined;
|
||||||
private _generatedFileDiagnostics: Diagnostic[]|undefined;
|
private _generatedFileDiagnostics: Diagnostic[]|undefined;
|
||||||
private _typeChecker: TypeChecker|undefined;
|
private _semanticDiagnostics: {ts: ts.Diagnostic[], ng: Diagnostic[]}|undefined;
|
||||||
private _semanticDiagnostics: Diagnostic[]|undefined;
|
|
||||||
private _optionsDiagnostics: Diagnostic[] = [];
|
private _optionsDiagnostics: Diagnostic[] = [];
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
@ -109,7 +107,7 @@ class AngularCompilerProgram implements Program {
|
||||||
|
|
||||||
getTsSemanticDiagnostics(sourceFile?: ts.SourceFile, cancellationToken?: ts.CancellationToken):
|
getTsSemanticDiagnostics(sourceFile?: ts.SourceFile, cancellationToken?: ts.CancellationToken):
|
||||||
ts.Diagnostic[] {
|
ts.Diagnostic[] {
|
||||||
return this.programWithStubs.getSemanticDiagnostics(sourceFile, cancellationToken);
|
return this.semanticDiagnostics.ts;
|
||||||
}
|
}
|
||||||
|
|
||||||
getNgSemanticDiagnostics(fileName?: string, cancellationToken?: ts.CancellationToken):
|
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
|
// If we have diagnostics during the parser phase the type check phase is not meaningful so skip
|
||||||
// it.
|
// it.
|
||||||
if (compilerDiagnostics && compilerDiagnostics.length) return compilerDiagnostics;
|
if (compilerDiagnostics && compilerDiagnostics.length) return compilerDiagnostics;
|
||||||
|
return this.semanticDiagnostics.ng;
|
||||||
return this.typeChecker.getDiagnostics(fileName, cancellationToken);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
loadNgStructureAsync(): Promise<void> {
|
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(
|
return this._programWithStubsHost || (this._programWithStubsHost = createProgramWithStubsHost(
|
||||||
this.stubs, this.tsProgram, this.host));
|
this.stubs, this.tsProgram, this.host));
|
||||||
}
|
}
|
||||||
|
@ -200,16 +197,15 @@ class AngularCompilerProgram implements Program {
|
||||||
return this._generatedFiles || (this._generatedFiles = this.generateFiles());
|
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 {
|
private get generatedFileDiagnostics(): Diagnostic[]|undefined {
|
||||||
return this.generatedFiles && this._generatedFileDiagnostics !;
|
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 {
|
private calculateTransforms(customTransformers?: CustomTransformers): ts.CustomTransformers {
|
||||||
const beforeTs: ts.TransformerFactory<ts.SourceFile>[] = [];
|
const beforeTs: ts.TransformerFactory<ts.SourceFile>[] = [];
|
||||||
if (!this.options.disableExpressionLowering) {
|
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 {
|
private createProgramWithStubs(): ts.Program {
|
||||||
// If we are skipping code generation just use the original program.
|
// If we are skipping code generation just use the original program.
|
||||||
// Otherwise, create a new program that includes the stub files.
|
// Otherwise, create a new program that includes the stub files.
|
||||||
|
@ -297,6 +287,11 @@ class AngularCompilerProgram implements Program {
|
||||||
ts.createProgram(
|
ts.createProgram(
|
||||||
[...this.rootNames, ...this.stubFiles], this.options, this.programWithStubsHost);
|
[...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> {
|
class AotCompilerHostImpl extends BaseAotCompilerHost<CompilerHost> {
|
||||||
|
@ -360,6 +355,7 @@ function getAotCompilerOptions(options: CompilerOptions): AotCompilerOptions {
|
||||||
enableLegacyTemplate: options.enableLegacyTemplate,
|
enableLegacyTemplate: options.enableLegacyTemplate,
|
||||||
enableSummariesForJit: true,
|
enableSummariesForJit: true,
|
||||||
preserveWhitespaces: options.preserveWhitespaces,
|
preserveWhitespaces: options.preserveWhitespaces,
|
||||||
|
fullTemplateTypeCheck: options.fullTemplateTypeCheck,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -453,13 +449,15 @@ function getNgOptionDiagnostics(options: CompilerOptions): Diagnostic[] {
|
||||||
|
|
||||||
function createProgramWithStubsHost(
|
function createProgramWithStubsHost(
|
||||||
generatedFiles: GeneratedFile[], originalProgram: ts.Program,
|
generatedFiles: GeneratedFile[], originalProgram: ts.Program,
|
||||||
originalHost: ts.CompilerHost): ts.CompilerHost {
|
originalHost: ts.CompilerHost): ts.CompilerHost&TypeCheckHost {
|
||||||
interface FileData {
|
interface FileData {
|
||||||
g: GeneratedFile;
|
g: GeneratedFile;
|
||||||
s?: ts.SourceFile;
|
s?: ts.SourceFile;
|
||||||
|
emitCtx?: EmitterVisitorContext;
|
||||||
}
|
}
|
||||||
return new class implements ts.CompilerHost {
|
return new class implements ts.CompilerHost, TypeCheckHost {
|
||||||
private generatedFiles: Map<string, FileData>;
|
private generatedFiles: Map<string, FileData>;
|
||||||
|
private emitter = new TypeScriptEmitter();
|
||||||
writeFile: ts.WriteFileCallback;
|
writeFile: ts.WriteFileCallback;
|
||||||
getCancellationToken: () => ts.CancellationToken;
|
getCancellationToken: () => ts.CancellationToken;
|
||||||
getDefaultLibLocation: () => string;
|
getDefaultLibLocation: () => string;
|
||||||
|
@ -487,13 +485,27 @@ function createProgramWithStubsHost(
|
||||||
this.trace = s => originalHost.trace !(s);
|
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(
|
getSourceFile(
|
||||||
fileName: string, languageVersion: ts.ScriptTarget,
|
fileName: string, languageVersion: ts.ScriptTarget,
|
||||||
onError?: ((message: string) => void)|undefined): ts.SourceFile {
|
onError?: ((message: string) => void)|undefined): ts.SourceFile {
|
||||||
const data = this.generatedFiles.get(fileName);
|
const data = this.generatedFiles.get(fileName);
|
||||||
if (data) {
|
if (data) {
|
||||||
return data.s || (data.s = ts.createSourceFile(
|
if (!data.s) {
|
||||||
fileName, data.g.source || toTypeScript(data.g), languageVersion));
|
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) ||
|
return originalProgram.getSourceFile(fileName) ||
|
||||||
originalHost.getSourceFile(fileName, languageVersion, onError);
|
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
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {AotCompilerOptions, createAotCompiler} from '@angular/compiler';
|
import * as ng from '@angular/compiler-cli';
|
||||||
import {EmittingCompilerHost, MockAotCompilerHost, MockCompilerHost, MockData, MockDirectory, MockMetadataBundlerHost, arrayToMockDir, arrayToMockMap, isSource, settings, setup, toMockFileArray} from '@angular/compiler/test/aot/test_util';
|
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 * as ts from 'typescript';
|
||||||
|
|
||||||
import {TypeChecker} from '../../src/diagnostics/check_types';
|
function getNgRootDir() {
|
||||||
import {Diagnostic} from '../../src/transformers/api';
|
const moduleFilename = module.filename.replace(/\\/g, '/');
|
||||||
import {LowerMetadataCache} from '../../src/transformers/lower_expressions';
|
const distIndex = moduleFilename.indexOf('/dist/all');
|
||||||
|
return moduleFilename.substr(0, distIndex);
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('ng type checker', () => {
|
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[]) {
|
function compileAndCheck(
|
||||||
expectNoDiagnostics(compile([angularFiles, QUICKSTART, ...files]));
|
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[]) {
|
beforeEach(() => {
|
||||||
const diagnostics = compile([angularFiles, QUICKSTART, ...files]);
|
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) {
|
if (!diagnostics || !diagnostics.length) {
|
||||||
throw new Error('Expected a diagnostic erorr message');
|
throw new Error('Expected a diagnostic erorr message');
|
||||||
} else {
|
} else {
|
||||||
const matches: (d: Diagnostic) => boolean = typeof message === 'string' ?
|
const matches: (d: ng.Diagnostic) => boolean = typeof message === 'string' ?
|
||||||
d => d.messageText == message :
|
d => ng.isNgDiagnostic(d)&& d.messageText == message :
|
||||||
d => message.test(d.messageText);
|
d => ng.isNgDiagnostic(d) && message.test(d.messageText);
|
||||||
const matchingDiagnostics = diagnostics.filter(matches);
|
const matchingDiagnostics = diagnostics.filter(matches) as ng.Diagnostic[];
|
||||||
if (!matchingDiagnostics || !matchingDiagnostics.length) {
|
if (!matchingDiagnostics || !matchingDiagnostics.length) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Expected a diagnostics matching ${message}, received\n ${diagnostics.map(d => d.messageText).join('\n ')}`);
|
`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(); });
|
it('should accept unmodified QuickStart', () => { accept(); });
|
||||||
|
|
||||||
describe('with modified quickstart', () => {
|
it('should accept unmodified QuickStart with tests for unused variables', () => {
|
||||||
function a(template: string) {
|
accept({}, {
|
||||||
accept({quickstart: {app: {'app.component.ts': appComponentSource(template)}}});
|
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) {
|
function rejectOnlyWithFullTemplateTypeCheck(
|
||||||
reject(message, {quickstart: {app: {'app.component.ts': appComponentSource(template)}}});
|
template: string, message: string | RegExp, location: string) {
|
||||||
|
if (config.fullTemplateTypeCheck) {
|
||||||
|
r(template, message, location);
|
||||||
|
} else {
|
||||||
|
a(template);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
it('should report an invalid field access',
|
it('should report an invalid field access', () => {
|
||||||
() => { r('{{fame}}', `Property 'fame' does not exist on type 'AppComponent'.`); });
|
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',
|
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',
|
it('should accept a reference to a field of a nullable using using non-null-assert',
|
||||||
() => { a('{{maybePerson!.name}}'); });
|
() => { a('{{maybePerson!.name}}'); });
|
||||||
it('should accept a safe property access of a nullable person',
|
it('should accept a safe property access of a nullable person',
|
||||||
() => { a('{{maybePerson?.name}}'); });
|
() => { a('{{maybePerson?.name}}'); });
|
||||||
it('should accept a function call', () => { a('{{getName()}}'); });
|
it('should accept a function call', () => { a('{{getName()}}'); });
|
||||||
it('should reject an invalid method', () => {
|
it('should reject an invalid method', () => {
|
||||||
r('{{getFame()}}',
|
r('<div>{{getFame()}}</div>',
|
||||||
`Property 'getFame' does not exist on type 'AppComponent'. Did you mean 'getName'?`);
|
`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 accept a field access of a method result', () => { a('{{getPerson().name}}'); });
|
||||||
it('should reject an invalid field reference of a method result',
|
it('should reject an invalid field reference of a method result', () => {
|
||||||
() => { r('{{getPerson().fame}}', `Property 'fame' does not exist on type 'Person'.`); });
|
r('<div>{{getPerson().fame}}</div>', `Property 'fame' does not exist on type 'Person'.`,
|
||||||
it('should reject an access to a nullable field of a method result',
|
'0:5');
|
||||||
() => { r('{{getMaybePerson().name}}', `Object is possibly 'undefined'.`); });
|
});
|
||||||
|
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',
|
it('should accept a nullable assert of a nullable field refernces of a method result',
|
||||||
() => { a('{{getMaybePerson()!.name}}'); });
|
() => { a('{{getMaybePerson()!.name}}'); });
|
||||||
it('should accept a safe property access of a nullable field reference of a method result',
|
it('should accept a safe property access of a nullable field reference of a method result',
|
||||||
() => { a('{{getMaybePerson()?.name}}'); });
|
() => { 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', () => {
|
describe('with lowered expressions', () => {
|
||||||
it('should not report lowered expressions as errors', () => {
|
it('should not report lowered expressions as errors',
|
||||||
expectNoDiagnostics(compile([angularFiles, LOWERING_QUICKSTART]));
|
() => { expectNoDiagnostics(compileAndCheck([LOWERING_QUICKSTART])); });
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function appComponentSource(template: string): string {
|
function appComponentSource(): string {
|
||||||
return `
|
return `
|
||||||
import {Component} from '@angular/core';
|
import {Component, Pipe, Directive} from '@angular/core';
|
||||||
|
|
||||||
export interface Person {
|
export interface Person {
|
||||||
name: string;
|
name: string;
|
||||||
|
@ -109,7 +214,7 @@ function appComponentSource(template: string): string {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
template: '${template}'
|
templateUrl: './app.component.html'
|
||||||
})
|
})
|
||||||
export class AppComponent {
|
export class AppComponent {
|
||||||
name = 'Angular';
|
name = 'Angular';
|
||||||
|
@ -119,40 +224,48 @@ function appComponentSource(template: string): string {
|
||||||
|
|
||||||
getName(): string { return this.name; }
|
getName(): string { return this.name; }
|
||||||
getPerson(): Person { return this.person; }
|
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 = {
|
const QUICKSTART = {
|
||||||
quickstart: {
|
'src/app.component.ts': appComponentSource(),
|
||||||
app: {
|
'src/app.component.html': '<h1>Hello {{name}}</h1>',
|
||||||
'app.component.ts': appComponentSource('<h1>Hello {{name}}</h1>'),
|
'src/app.module.ts': `
|
||||||
'app.module.ts': `
|
|
||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { toString } from './utils';
|
import { AppComponent, APipe, ADirective } from './app.component';
|
||||||
|
|
||||||
import { AppComponent } from './app.component';
|
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [ AppComponent ],
|
declarations: [ AppComponent, APipe, ADirective ],
|
||||||
bootstrap: [ AppComponent ]
|
bootstrap: [ AppComponent ]
|
||||||
})
|
})
|
||||||
export class AppModule { }
|
export class AppModule { }
|
||||||
`
|
`
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const LOWERING_QUICKSTART: MockDirectory = {
|
const LOWERING_QUICKSTART = {
|
||||||
quickstart: {
|
'src/app.component.ts': appComponentSource(),
|
||||||
app: {
|
'src/app.component.html': '<h1>Hello {{name}}</h1>',
|
||||||
'app.component.ts': appComponentSource('<h1>Hello {{name}}</h1>'),
|
'src/app.module.ts': `
|
||||||
'app.module.ts': `
|
|
||||||
import { NgModule, Component } from '@angular/core';
|
import { NgModule, Component } from '@angular/core';
|
||||||
import { toString } from './utils';
|
|
||||||
|
|
||||||
import { AppComponent } from './app.component';
|
import { AppComponent, APipe, ADirective } from './app.component';
|
||||||
|
|
||||||
class Foo {}
|
class Foo {}
|
||||||
|
|
||||||
|
@ -165,17 +278,15 @@ const LOWERING_QUICKSTART: MockDirectory = {
|
||||||
export class Bar {}
|
export class Bar {}
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [ AppComponent, Bar ],
|
declarations: [ AppComponent, APipe, ADirective, Bar ],
|
||||||
bootstrap: [ AppComponent ]
|
bootstrap: [ AppComponent ]
|
||||||
})
|
})
|
||||||
export class AppModule { }
|
export class AppModule { }
|
||||||
`
|
`
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
function expectNoDiagnostics(diagnostics: Diagnostic[]) {
|
function expectNoDiagnostics(diagnostics: ng.Diagnostics) {
|
||||||
if (diagnostics && diagnostics.length) {
|
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
|
* 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 {CompilerConfig} from '../config';
|
||||||
import {MessageBundle} from '../i18n/message_bundle';
|
import {MessageBundle} from '../i18n/message_bundle';
|
||||||
import {Identifiers, createTokenForExternalReference} from '../identifiers';
|
import {Identifiers, createTokenForExternalReference} from '../identifiers';
|
||||||
|
@ -19,8 +19,10 @@ import * as o from '../output/output_ast';
|
||||||
import {ParseError} from '../parse_util';
|
import {ParseError} from '../parse_util';
|
||||||
import {CompiledStylesheet, StyleCompiler} from '../style_compiler';
|
import {CompiledStylesheet, StyleCompiler} from '../style_compiler';
|
||||||
import {SummaryResolver} from '../summary_resolver';
|
import {SummaryResolver} from '../summary_resolver';
|
||||||
|
import {TemplateAst} from '../template_parser/template_ast';
|
||||||
import {TemplateParser} from '../template_parser/template_parser';
|
import {TemplateParser} from '../template_parser/template_parser';
|
||||||
import {OutputContext, syntaxError} from '../util';
|
import {OutputContext, syntaxError} from '../util';
|
||||||
|
import {TypeCheckCompiler} from '../view_compiler/type_check_compiler';
|
||||||
import {ViewCompileResult, ViewCompiler} from '../view_compiler/view_compiler';
|
import {ViewCompileResult, ViewCompiler} from '../view_compiler/view_compiler';
|
||||||
|
|
||||||
import {AotCompilerHost} from './compiler_host';
|
import {AotCompilerHost} from './compiler_host';
|
||||||
|
@ -32,11 +34,15 @@ import {createForJitStub, serializeSummaries} from './summary_serializer';
|
||||||
import {ngfactoryFilePath, splitTypescriptSuffix, summaryFileName, summaryForJitFileName, summaryForJitName} from './util';
|
import {ngfactoryFilePath, splitTypescriptSuffix, summaryFileName, summaryForJitFileName, summaryForJitName} from './util';
|
||||||
|
|
||||||
export class AotCompiler {
|
export class AotCompiler {
|
||||||
|
private _templateAstCache =
|
||||||
|
new Map<StaticSymbol, {template: TemplateAst[], pipes: CompilePipeSummary[]}>();
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private _config: CompilerConfig, private _host: AotCompilerHost,
|
private _config: CompilerConfig, private _host: AotCompilerHost,
|
||||||
private _reflector: StaticReflector, private _metadataResolver: CompileMetadataResolver,
|
private _reflector: StaticReflector, private _metadataResolver: CompileMetadataResolver,
|
||||||
private _templateParser: TemplateParser, private _styleCompiler: StyleCompiler,
|
private _htmlParser: HtmlParser, private _templateParser: TemplateParser,
|
||||||
private _viewCompiler: ViewCompiler, private _ngModuleCompiler: NgModuleCompiler,
|
private _styleCompiler: StyleCompiler, private _viewCompiler: ViewCompiler,
|
||||||
|
private _typeCheckCompiler: TypeCheckCompiler, private _ngModuleCompiler: NgModuleCompiler,
|
||||||
private _outputEmitter: OutputEmitter,
|
private _outputEmitter: OutputEmitter,
|
||||||
private _summaryResolver: SummaryResolver<StaticSymbol>, private _localeId: string|null,
|
private _summaryResolver: SummaryResolver<StaticSymbol>, private _localeId: string|null,
|
||||||
private _translationFormat: string|null, private _enableSummariesForJit: boolean|null,
|
private _translationFormat: string|null, private _enableSummariesForJit: boolean|null,
|
||||||
|
@ -66,9 +72,10 @@ export class AotCompiler {
|
||||||
}
|
}
|
||||||
|
|
||||||
emitAllStubs(analyzeResult: NgAnalyzedModules): GeneratedFile[] {
|
emitAllStubs(analyzeResult: NgAnalyzedModules): GeneratedFile[] {
|
||||||
const {files} = analyzeResult;
|
const {files, ngModuleByPipeOrDirective} = analyzeResult;
|
||||||
const sourceModules = files.map(
|
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);
|
return flatten(sourceModules);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -112,7 +119,8 @@ export class AotCompiler {
|
||||||
}
|
}
|
||||||
|
|
||||||
private _compileStubFile(
|
private _compileStubFile(
|
||||||
srcFileUrl: string, directives: StaticSymbol[], pipes: StaticSymbol[],
|
srcFileUrl: string, ngModuleByPipeOrDirective: Map<StaticSymbol, CompileNgModuleMetadata>,
|
||||||
|
directives: StaticSymbol[], pipes: StaticSymbol[],
|
||||||
ngModules: StaticSymbol[]): GeneratedFile[] {
|
ngModules: StaticSymbol[]): GeneratedFile[] {
|
||||||
const fileSuffix = splitTypescriptSuffix(srcFileUrl, true)[1];
|
const fileSuffix = splitTypescriptSuffix(srcFileUrl, true)[1];
|
||||||
const generatedFiles: GeneratedFile[] = [];
|
const generatedFiles: GeneratedFile[] = [];
|
||||||
|
@ -130,10 +138,17 @@ export class AotCompiler {
|
||||||
// the generated code)
|
// the generated code)
|
||||||
directives.forEach((dirType) => {
|
directives.forEach((dirType) => {
|
||||||
const compMeta = this._metadataResolver.getDirectiveMetadata(<any>dirType);
|
const compMeta = this._metadataResolver.getDirectiveMetadata(<any>dirType);
|
||||||
|
|
||||||
if (!compMeta.isComponent) {
|
if (!compMeta.isComponent) {
|
||||||
return;
|
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.
|
// Note: compMeta is a component and therefore template is non null.
|
||||||
compMeta.template !.externalStylesheets.forEach((stylesheetMeta) => {
|
compMeta.template !.externalStylesheets.forEach((stylesheetMeta) => {
|
||||||
const styleContext = this._createOutputContext(_stylesModuleUrl(
|
const styleContext = this._createOutputContext(_stylesModuleUrl(
|
||||||
|
@ -285,7 +300,8 @@ export class AotCompiler {
|
||||||
ngModule: CompileNgModuleMetadata, fileSuffix: string): void {
|
ngModule: CompileNgModuleMetadata, fileSuffix: string): void {
|
||||||
const hostType = this._metadataResolver.getHostComponentType(compMeta.type.reference);
|
const hostType = this._metadataResolver.getHostComponentType(compMeta.type.reference);
|
||||||
const hostMeta = createHostComponentMeta(
|
const hostMeta = createHostComponentMeta(
|
||||||
hostType, compMeta, this._metadataResolver.getHostComponentViewClass(hostType));
|
hostType, compMeta, this._metadataResolver.getHostComponentViewClass(hostType),
|
||||||
|
this._htmlParser);
|
||||||
const hostViewFactoryVar =
|
const hostViewFactoryVar =
|
||||||
this._compileComponent(outputCtx, hostMeta, ngModule, [compMeta.type], null, fileSuffix)
|
this._compileComponent(outputCtx, hostMeta, ngModule, [compMeta.type], null, fileSuffix)
|
||||||
.viewClassVar;
|
.viewClassVar;
|
||||||
|
@ -320,19 +336,32 @@ export class AotCompiler {
|
||||||
[o.StmtModifier.Final, o.StmtModifier.Exported]));
|
[o.StmtModifier.Final, o.StmtModifier.Exported]));
|
||||||
}
|
}
|
||||||
|
|
||||||
private _compileComponent(
|
private _parseTemplate(
|
||||||
outputCtx: OutputContext, compMeta: CompileDirectiveMetadata,
|
compMeta: CompileDirectiveMetadata, ngModule: CompileNgModuleMetadata,
|
||||||
ngModule: CompileNgModuleMetadata, directiveIdentifiers: CompileIdentifierMetadata[],
|
directiveIdentifiers: CompileIdentifierMetadata[]):
|
||||||
componentStyles: CompiledStylesheet|null, fileSuffix: string): ViewCompileResult {
|
{template: TemplateAst[], pipes: CompilePipeSummary[]} {
|
||||||
|
let result = this._templateAstCache.get(compMeta.type.reference);
|
||||||
|
if (result) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
const preserveWhitespaces = compMeta !.template !.preserveWhitespaces;
|
||||||
const directives =
|
const directives =
|
||||||
directiveIdentifiers.map(dir => this._metadataResolver.getDirectiveSummary(dir.reference));
|
directiveIdentifiers.map(dir => this._metadataResolver.getDirectiveSummary(dir.reference));
|
||||||
const pipes = ngModule.transitiveModule.pipes.map(
|
const pipes = ngModule.transitiveModule.pipes.map(
|
||||||
pipe => this._metadataResolver.getPipeSummary(pipe.reference));
|
pipe => this._metadataResolver.getPipeSummary(pipe.reference));
|
||||||
|
result = this._templateParser.parse(
|
||||||
const preserveWhitespaces = compMeta !.template !.preserveWhitespaces;
|
compMeta, compMeta.template !.htmlAst !, directives, pipes, ngModule.schemas,
|
||||||
const {template: parsedTemplate, pipes: usedPipes} = this._templateParser.parse(
|
|
||||||
compMeta, compMeta.template !.template !, directives, pipes, ngModule.schemas,
|
|
||||||
templateSourceUrl(ngModule.type, compMeta, compMeta.template !), preserveWhitespaces);
|
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 stylesExpr = componentStyles ? o.variable(componentStyles.stylesVar) : o.literalArr([]);
|
||||||
const viewResult = this._viewCompiler.compileComponent(
|
const viewResult = this._viewCompiler.compileComponent(
|
||||||
outputCtx, compMeta, parsedTemplate, stylesExpr, usedPipes);
|
outputCtx, compMeta, parsedTemplate, stylesExpr, usedPipes);
|
||||||
|
@ -344,6 +373,14 @@ export class AotCompiler {
|
||||||
return viewResult;
|
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 {
|
private _createOutputContext(genFilePath: string): OutputContext {
|
||||||
const importExpr = (symbol: StaticSymbol, typeParams: o.Type[] | null = null) => {
|
const importExpr = (symbol: StaticSymbol, typeParams: o.Type[] | null = null) => {
|
||||||
if (!(symbol instanceof StaticSymbol)) {
|
if (!(symbol instanceof StaticSymbol)) {
|
||||||
|
|
|
@ -24,6 +24,7 @@ import {StyleCompiler} from '../style_compiler';
|
||||||
import {TemplateParser} from '../template_parser/template_parser';
|
import {TemplateParser} from '../template_parser/template_parser';
|
||||||
import {UrlResolver} from '../url_resolver';
|
import {UrlResolver} from '../url_resolver';
|
||||||
import {syntaxError} from '../util';
|
import {syntaxError} from '../util';
|
||||||
|
import {TypeCheckCompiler} from '../view_compiler/type_check_compiler';
|
||||||
import {ViewCompiler} from '../view_compiler/view_compiler';
|
import {ViewCompiler} from '../view_compiler/view_compiler';
|
||||||
|
|
||||||
import {AotCompiler} from './compiler';
|
import {AotCompiler} from './compiler';
|
||||||
|
@ -81,9 +82,11 @@ export function createAotCompiler(compilerHost: AotCompilerHost, options: AotCom
|
||||||
console, symbolCache, staticReflector);
|
console, symbolCache, staticReflector);
|
||||||
// TODO(vicb): do not pass options.i18nFormat here
|
// TODO(vicb): do not pass options.i18nFormat here
|
||||||
const viewCompiler = new ViewCompiler(config, staticReflector, elementSchemaRegistry);
|
const viewCompiler = new ViewCompiler(config, staticReflector, elementSchemaRegistry);
|
||||||
|
const typeCheckCompiler = new TypeCheckCompiler(options, staticReflector);
|
||||||
const compiler = new AotCompiler(
|
const compiler = new AotCompiler(
|
||||||
config, compilerHost, staticReflector, resolver, tmplParser, new StyleCompiler(urlResolver),
|
config, compilerHost, staticReflector, resolver, htmlParser, tmplParser,
|
||||||
viewCompiler, new NgModuleCompiler(staticReflector), new TypeScriptEmitter(), summaryResolver,
|
new StyleCompiler(urlResolver), viewCompiler, typeCheckCompiler,
|
||||||
|
new NgModuleCompiler(staticReflector), new TypeScriptEmitter(), summaryResolver,
|
||||||
options.locale || null, options.i18nFormat || null, options.enableSummariesForJit || null,
|
options.locale || null, options.i18nFormat || null, options.enableSummariesForJit || null,
|
||||||
symbolResolver);
|
symbolResolver);
|
||||||
return {compiler, reflector: staticReflector};
|
return {compiler, reflector: staticReflector};
|
||||||
|
|
|
@ -16,4 +16,5 @@ export interface AotCompilerOptions {
|
||||||
enableLegacyTemplate?: boolean;
|
enableLegacyTemplate?: boolean;
|
||||||
enableSummariesForJit?: boolean;
|
enableSummariesForJit?: boolean;
|
||||||
preserveWhitespaces?: boolean;
|
preserveWhitespaces?: boolean;
|
||||||
|
fullTemplateTypeCheck?: boolean;
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,9 @@
|
||||||
import {StaticSymbol} from './aot/static_symbol';
|
import {StaticSymbol} from './aot/static_symbol';
|
||||||
import {ChangeDetectionStrategy, SchemaMetadata, Type, ViewEncapsulation} from './core';
|
import {ChangeDetectionStrategy, SchemaMetadata, Type, ViewEncapsulation} from './core';
|
||||||
import {LifecycleHooks} from './lifecycle_reflector';
|
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 {CssSelector} from './selector';
|
||||||
import {splitAtColon, stringify} from './util';
|
import {splitAtColon, stringify} from './util';
|
||||||
|
|
||||||
|
@ -244,6 +247,7 @@ export class CompileTemplateMetadata {
|
||||||
encapsulation: ViewEncapsulation|null;
|
encapsulation: ViewEncapsulation|null;
|
||||||
template: string|null;
|
template: string|null;
|
||||||
templateUrl: string|null;
|
templateUrl: string|null;
|
||||||
|
htmlAst: HtmlParseTreeResult|null;
|
||||||
isInline: boolean;
|
isInline: boolean;
|
||||||
styles: string[];
|
styles: string[];
|
||||||
styleUrls: string[];
|
styleUrls: string[];
|
||||||
|
@ -252,11 +256,13 @@ export class CompileTemplateMetadata {
|
||||||
ngContentSelectors: string[];
|
ngContentSelectors: string[];
|
||||||
interpolation: [string, string]|null;
|
interpolation: [string, string]|null;
|
||||||
preserveWhitespaces: boolean;
|
preserveWhitespaces: boolean;
|
||||||
constructor({encapsulation, template, templateUrl, styles, styleUrls, externalStylesheets,
|
constructor({encapsulation, template, templateUrl, htmlAst, styles, styleUrls,
|
||||||
animations, ngContentSelectors, interpolation, isInline, preserveWhitespaces}: {
|
externalStylesheets, animations, ngContentSelectors, interpolation, isInline,
|
||||||
|
preserveWhitespaces}: {
|
||||||
encapsulation: ViewEncapsulation | null,
|
encapsulation: ViewEncapsulation | null,
|
||||||
template: string|null,
|
template: string|null,
|
||||||
templateUrl: string|null,
|
templateUrl: string|null,
|
||||||
|
htmlAst: HtmlParseTreeResult|null,
|
||||||
styles: string[],
|
styles: string[],
|
||||||
styleUrls: string[],
|
styleUrls: string[],
|
||||||
externalStylesheets: CompileStylesheetMetadata[],
|
externalStylesheets: CompileStylesheetMetadata[],
|
||||||
|
@ -269,6 +275,7 @@ export class CompileTemplateMetadata {
|
||||||
this.encapsulation = encapsulation;
|
this.encapsulation = encapsulation;
|
||||||
this.template = template;
|
this.template = template;
|
||||||
this.templateUrl = templateUrl;
|
this.templateUrl = templateUrl;
|
||||||
|
this.htmlAst = htmlAst;
|
||||||
this.styles = _normalizeArray(styles);
|
this.styles = _normalizeArray(styles);
|
||||||
this.styleUrls = _normalizeArray(styleUrls);
|
this.styleUrls = _normalizeArray(styleUrls);
|
||||||
this.externalStylesheets = _normalizeArray(externalStylesheets);
|
this.externalStylesheets = _normalizeArray(externalStylesheets);
|
||||||
|
@ -503,15 +510,18 @@ export class CompileDirectiveMetadata {
|
||||||
*/
|
*/
|
||||||
export function createHostComponentMeta(
|
export function createHostComponentMeta(
|
||||||
hostTypeReference: any, compMeta: CompileDirectiveMetadata,
|
hostTypeReference: any, compMeta: CompileDirectiveMetadata,
|
||||||
hostViewType: StaticSymbol | ProxyClass): CompileDirectiveMetadata {
|
hostViewType: StaticSymbol | ProxyClass, htmlParser: HtmlParser): CompileDirectiveMetadata {
|
||||||
const template = CssSelector.parse(compMeta.selector !)[0].getMatchingElementTemplate();
|
const template = CssSelector.parse(compMeta.selector !)[0].getMatchingElementTemplate();
|
||||||
|
const templateUrl = '';
|
||||||
|
const htmlAst = htmlParser.parse(template, templateUrl);
|
||||||
return CompileDirectiveMetadata.create({
|
return CompileDirectiveMetadata.create({
|
||||||
isHost: true,
|
isHost: true,
|
||||||
type: {reference: hostTypeReference, diDeps: [], lifecycleHooks: []},
|
type: {reference: hostTypeReference, diDeps: [], lifecycleHooks: []},
|
||||||
template: new CompileTemplateMetadata({
|
template: new CompileTemplateMetadata({
|
||||||
encapsulation: ViewEncapsulation.None,
|
encapsulation: ViewEncapsulation.None,
|
||||||
template: template,
|
template,
|
||||||
templateUrl: '',
|
templateUrl,
|
||||||
|
htmlAst,
|
||||||
styles: [],
|
styles: [],
|
||||||
styleUrls: [],
|
styleUrls: [],
|
||||||
ngContentSelectors: [],
|
ngContentSelectors: [],
|
||||||
|
|
|
@ -30,7 +30,8 @@ export class CompilerConfig {
|
||||||
jitDevMode?: boolean,
|
jitDevMode?: boolean,
|
||||||
missingTranslation?: MissingTranslationStrategy,
|
missingTranslation?: MissingTranslationStrategy,
|
||||||
enableLegacyTemplate?: boolean,
|
enableLegacyTemplate?: boolean,
|
||||||
preserveWhitespaces?: boolean
|
preserveWhitespaces?: boolean,
|
||||||
|
fullTemplateTypeCheck?: boolean
|
||||||
} = {}) {
|
} = {}) {
|
||||||
this.defaultEncapsulation = defaultEncapsulation;
|
this.defaultEncapsulation = defaultEncapsulation;
|
||||||
this.useJit = !!useJit;
|
this.useJit = !!useJit;
|
||||||
|
|
|
@ -150,7 +150,8 @@ export class DirectiveNormalizer {
|
||||||
return new CompileTemplateMetadata({
|
return new CompileTemplateMetadata({
|
||||||
encapsulation,
|
encapsulation,
|
||||||
template,
|
template,
|
||||||
templateUrl: templateAbsUrl, styles, styleUrls,
|
templateUrl: templateAbsUrl,
|
||||||
|
htmlAst: rootNodesAndErrors, styles, styleUrls,
|
||||||
ngContentSelectors: visitor.ngContentSelectors,
|
ngContentSelectors: visitor.ngContentSelectors,
|
||||||
animations: prenormData.animations,
|
animations: prenormData.animations,
|
||||||
interpolation: prenormData.interpolation, isInline,
|
interpolation: prenormData.interpolation, isInline,
|
||||||
|
@ -168,6 +169,7 @@ export class DirectiveNormalizer {
|
||||||
encapsulation: templateMeta.encapsulation,
|
encapsulation: templateMeta.encapsulation,
|
||||||
template: templateMeta.template,
|
template: templateMeta.template,
|
||||||
templateUrl: templateMeta.templateUrl,
|
templateUrl: templateMeta.templateUrl,
|
||||||
|
htmlAst: templateMeta.htmlAst,
|
||||||
styles: templateMeta.styles,
|
styles: templateMeta.styles,
|
||||||
styleUrls: templateMeta.styleUrls,
|
styleUrls: templateMeta.styleUrls,
|
||||||
externalStylesheets: externalStylesheets,
|
externalStylesheets: externalStylesheets,
|
||||||
|
|
|
@ -11,6 +11,7 @@ import {CompileReflector} from '../compile_reflector';
|
||||||
import {CompilerConfig} from '../config';
|
import {CompilerConfig} from '../config';
|
||||||
import {Type} from '../core';
|
import {Type} from '../core';
|
||||||
import {CompileMetadataResolver} from '../metadata_resolver';
|
import {CompileMetadataResolver} from '../metadata_resolver';
|
||||||
|
import {HtmlParser} from '../ml_parser/html_parser';
|
||||||
import {NgModuleCompiler} from '../ng_module_compiler';
|
import {NgModuleCompiler} from '../ng_module_compiler';
|
||||||
import * as ir from '../output/output_ast';
|
import * as ir from '../output/output_ast';
|
||||||
import {interpretStatements} from '../output/output_interpreter';
|
import {interpretStatements} from '../output/output_interpreter';
|
||||||
|
@ -43,11 +44,11 @@ export class JitCompiler {
|
||||||
private _sharedStylesheetCount = 0;
|
private _sharedStylesheetCount = 0;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private _metadataResolver: CompileMetadataResolver, private _templateParser: TemplateParser,
|
private _metadataResolver: CompileMetadataResolver, private _htmlParser: HtmlParser,
|
||||||
private _styleCompiler: StyleCompiler, private _viewCompiler: ViewCompiler,
|
private _templateParser: TemplateParser, private _styleCompiler: StyleCompiler,
|
||||||
private _ngModuleCompiler: NgModuleCompiler, private _summaryResolver: SummaryResolver<Type>,
|
private _viewCompiler: ViewCompiler, private _ngModuleCompiler: NgModuleCompiler,
|
||||||
private _reflector: CompileReflector, private _compilerConfig: CompilerConfig,
|
private _summaryResolver: SummaryResolver<Type>, private _reflector: CompileReflector,
|
||||||
private _console: Console,
|
private _compilerConfig: CompilerConfig, private _console: Console,
|
||||||
private getExtraNgModuleProviders: (ngModule: any) => CompileProviderMetadata[]) {}
|
private getExtraNgModuleProviders: (ngModule: any) => CompileProviderMetadata[]) {}
|
||||||
|
|
||||||
compileModuleSync(moduleType: Type): object {
|
compileModuleSync(moduleType: Type): object {
|
||||||
|
@ -228,7 +229,7 @@ export class JitCompiler {
|
||||||
|
|
||||||
const hostClass = this._metadataResolver.getHostComponentType(compType);
|
const hostClass = this._metadataResolver.getHostComponentType(compType);
|
||||||
const hostMeta = createHostComponentMeta(
|
const hostMeta = createHostComponentMeta(
|
||||||
hostClass, compMeta, (compMeta.componentFactory as any).viewDefFactory);
|
hostClass, compMeta, (compMeta.componentFactory as any).viewDefFactory, this._htmlParser);
|
||||||
compiledTemplate =
|
compiledTemplate =
|
||||||
new CompiledTemplate(true, compMeta.type, hostMeta, ngModule, [compMeta.type]);
|
new CompiledTemplate(true, compMeta.type, hostMeta, ngModule, [compMeta.type]);
|
||||||
this._compiledHostTemplateCache.set(compType, compiledTemplate);
|
this._compiledHostTemplateCache.set(compType, compiledTemplate);
|
||||||
|
|
|
@ -261,6 +261,7 @@ export class CompileMetadataResolver {
|
||||||
encapsulation: noUndefined(compMeta.encapsulation),
|
encapsulation: noUndefined(compMeta.encapsulation),
|
||||||
template: noUndefined(compMeta.template),
|
template: noUndefined(compMeta.template),
|
||||||
templateUrl: noUndefined(compMeta.templateUrl),
|
templateUrl: noUndefined(compMeta.templateUrl),
|
||||||
|
htmlAst: null,
|
||||||
styles: compMeta.styles || [],
|
styles: compMeta.styles || [],
|
||||||
styleUrls: compMeta.styleUrls || [],
|
styleUrls: compMeta.styleUrls || [],
|
||||||
animations: animations || [],
|
animations: animations || [],
|
||||||
|
|
|
@ -1141,8 +1141,8 @@ export function importType(
|
||||||
}
|
}
|
||||||
|
|
||||||
export function expressionType(
|
export function expressionType(
|
||||||
expr: Expression, typeModifiers: TypeModifier[] | null = null): ExpressionType|null {
|
expr: Expression, typeModifiers: TypeModifier[] | null = null): ExpressionType {
|
||||||
return expr != null ? new ExpressionType(expr, typeModifiers) ! : null;
|
return new ExpressionType(expr, typeModifiers);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function literalArr(
|
export function literalArr(
|
||||||
|
|
|
@ -101,8 +101,9 @@ export class TemplateParser {
|
||||||
public transforms: TemplateAstVisitor[]) {}
|
public transforms: TemplateAstVisitor[]) {}
|
||||||
|
|
||||||
parse(
|
parse(
|
||||||
component: CompileDirectiveMetadata, template: string, directives: CompileDirectiveSummary[],
|
component: CompileDirectiveMetadata, template: string|ParseTreeResult,
|
||||||
pipes: CompilePipeSummary[], schemas: SchemaMetadata[], templateUrl: string,
|
directives: CompileDirectiveSummary[], pipes: CompilePipeSummary[], schemas: SchemaMetadata[],
|
||||||
|
templateUrl: string,
|
||||||
preserveWhitespaces: boolean): {template: TemplateAst[], pipes: CompilePipeSummary[]} {
|
preserveWhitespaces: boolean): {template: TemplateAst[], pipes: CompilePipeSummary[]} {
|
||||||
const result = this.tryParse(
|
const result = this.tryParse(
|
||||||
component, template, directives, pipes, schemas, templateUrl, preserveWhitespaces);
|
component, template, directives, pipes, schemas, templateUrl, preserveWhitespaces);
|
||||||
|
@ -126,11 +127,13 @@ export class TemplateParser {
|
||||||
}
|
}
|
||||||
|
|
||||||
tryParse(
|
tryParse(
|
||||||
component: CompileDirectiveMetadata, template: string, directives: CompileDirectiveSummary[],
|
component: CompileDirectiveMetadata, template: string|ParseTreeResult,
|
||||||
pipes: CompilePipeSummary[], schemas: SchemaMetadata[], templateUrl: string,
|
directives: CompileDirectiveSummary[], pipes: CompilePipeSummary[], schemas: SchemaMetadata[],
|
||||||
preserveWhitespaces: boolean): TemplateParseResult {
|
templateUrl: string, preserveWhitespaces: boolean): TemplateParseResult {
|
||||||
let htmlParseResult = this._htmlParser !.parse(
|
let htmlParseResult = typeof template === 'string' ?
|
||||||
template, templateUrl, true, this.getInterpolationConfig(component));
|
this._htmlParser !.parse(
|
||||||
|
template, templateUrl, true, this.getInterpolationConfig(component)) :
|
||||||
|
template;
|
||||||
|
|
||||||
if (!preserveWhitespaces) {
|
if (!preserveWhitespaces) {
|
||||||
htmlParseResult = removeWhitespaces(htmlParseResult);
|
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,
|
encapsulation: encapsulation || null,
|
||||||
template: template || null,
|
template: template || null,
|
||||||
templateUrl: templateUrl || null,
|
templateUrl: templateUrl || null,
|
||||||
|
htmlAst: null,
|
||||||
styles: styles || [],
|
styles: styles || [],
|
||||||
styleUrls: styleUrls || [],
|
styleUrls: styleUrls || [],
|
||||||
externalStylesheets: externalStylesheets || [],
|
externalStylesheets: externalStylesheets || [],
|
||||||
|
|
|
@ -101,6 +101,7 @@ function compileTemplateMetadata({encapsulation, template, templateUrl, styles,
|
||||||
encapsulation: noUndefined(encapsulation),
|
encapsulation: noUndefined(encapsulation),
|
||||||
template: noUndefined(template),
|
template: noUndefined(template),
|
||||||
templateUrl: noUndefined(templateUrl),
|
templateUrl: noUndefined(templateUrl),
|
||||||
|
htmlAst: null,
|
||||||
styles: styles || [],
|
styles: styles || [],
|
||||||
styleUrls: styleUrls || [],
|
styleUrls: styleUrls || [],
|
||||||
externalStylesheets: externalStylesheets || [],
|
externalStylesheets: externalStylesheets || [],
|
||||||
|
@ -372,6 +373,7 @@ export function main() {
|
||||||
animations: [],
|
animations: [],
|
||||||
template: null,
|
template: null,
|
||||||
templateUrl: null,
|
templateUrl: null,
|
||||||
|
htmlAst: null,
|
||||||
ngContentSelectors: [],
|
ngContentSelectors: [],
|
||||||
externalStylesheets: [],
|
externalStylesheets: [],
|
||||||
styleUrls: [],
|
styleUrls: [],
|
||||||
|
|
|
@ -34,12 +34,13 @@ export class CompilerImpl implements Compiler {
|
||||||
private _delegate: JitCompiler;
|
private _delegate: JitCompiler;
|
||||||
constructor(
|
constructor(
|
||||||
private _injector: Injector, private _metadataResolver: CompileMetadataResolver,
|
private _injector: Injector, private _metadataResolver: CompileMetadataResolver,
|
||||||
templateParser: TemplateParser, styleCompiler: StyleCompiler, viewCompiler: ViewCompiler,
|
htmlParser: HtmlParser, templateParser: TemplateParser, styleCompiler: StyleCompiler,
|
||||||
ngModuleCompiler: NgModuleCompiler, summaryResolver: SummaryResolver<Type<any>>,
|
viewCompiler: ViewCompiler, ngModuleCompiler: NgModuleCompiler,
|
||||||
compileReflector: CompileReflector, compilerConfig: CompilerConfig, console: Console) {
|
summaryResolver: SummaryResolver<Type<any>>, compileReflector: CompileReflector,
|
||||||
|
compilerConfig: CompilerConfig, console: Console) {
|
||||||
this._delegate = new JitCompiler(
|
this._delegate = new JitCompiler(
|
||||||
_metadataResolver, templateParser, styleCompiler, viewCompiler, ngModuleCompiler,
|
_metadataResolver, htmlParser, templateParser, styleCompiler, viewCompiler,
|
||||||
summaryResolver, compileReflector, compilerConfig, console,
|
ngModuleCompiler, summaryResolver, compileReflector, compilerConfig, console,
|
||||||
this.getExtraNgModuleProviders.bind(this));
|
this.getExtraNgModuleProviders.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -141,7 +142,7 @@ export const COMPILER_PROVIDERS = <StaticProvider[]>[
|
||||||
{ provide: NgModuleCompiler, deps: [CompileReflector] },
|
{ provide: NgModuleCompiler, deps: [CompileReflector] },
|
||||||
{ provide: CompilerConfig, useValue: new CompilerConfig()},
|
{ provide: CompilerConfig, useValue: new CompilerConfig()},
|
||||||
{ provide: Compiler, useClass: CompilerImpl, deps: [Injector, CompileMetadataResolver,
|
{ provide: Compiler, useClass: CompilerImpl, deps: [Injector, CompileMetadataResolver,
|
||||||
TemplateParser, StyleCompiler,
|
HtmlParser, TemplateParser, StyleCompiler,
|
||||||
ViewCompiler, NgModuleCompiler,
|
ViewCompiler, NgModuleCompiler,
|
||||||
SummaryResolver, CompileReflector, CompilerConfig,
|
SummaryResolver, CompileReflector, CompilerConfig,
|
||||||
Console]},
|
Console]},
|
||||||
|
|
Loading…
Reference in New Issue