feat(compiler): implement "enableIvy" compiler option (#21427)
The "enableIvy" compiler option is the initial implementation of the Render3 (or Ivy) code generation. This commit enables generation generating "Hello, World" (example in the test) but not much else. It is currenly only useful for internal Ivy testing as Ivy is in development. PR Close #21427
This commit is contained in:

committed by
Miško Hevery

parent
ce8b5877e2
commit
64d16dee02
@ -8,6 +8,7 @@
|
||||
|
||||
import {CompileDirectiveMetadata, CompileDirectiveSummary, CompileIdentifierMetadata, CompileNgModuleMetadata, CompileNgModuleSummary, CompilePipeMetadata, CompilePipeSummary, CompileProviderMetadata, CompileStylesheetMetadata, CompileSummaryKind, CompileTypeMetadata, CompileTypeSummary, componentFactoryName, flatten, identifierName, templateSourceUrl, tokenReference} from '../compile_metadata';
|
||||
import {CompilerConfig} from '../config';
|
||||
import {ConstantPool} from '../constant_pool';
|
||||
import {ViewEncapsulation} from '../core';
|
||||
import {MessageBundle} from '../i18n/message_bundle';
|
||||
import {Identifiers, createTokenForExternalReference} from '../identifiers';
|
||||
@ -18,11 +19,12 @@ import {NgModuleCompiler} from '../ng_module_compiler';
|
||||
import {OutputEmitter} from '../output/abstract_emitter';
|
||||
import * as o from '../output/output_ast';
|
||||
import {ParseError} from '../parse_util';
|
||||
import {compileComponent as compileIvyComponent} from '../render3/r3_view_compiler';
|
||||
import {CompiledStylesheet, StyleCompiler} from '../style_compiler';
|
||||
import {SummaryResolver} from '../summary_resolver';
|
||||
import {TemplateAst} from '../template_parser/template_ast';
|
||||
import {TemplateParser} from '../template_parser/template_parser';
|
||||
import {OutputContext, ValueVisitor, syntaxError, visitValue} from '../util';
|
||||
import {OutputContext, ValueVisitor, error, syntaxError, visitValue} from '../util';
|
||||
import {TypeCheckCompiler} from '../view_compiler/type_check_compiler';
|
||||
import {ViewCompileResult, ViewCompiler} from '../view_compiler/view_compiler';
|
||||
|
||||
@ -30,6 +32,7 @@ import {AotCompilerHost} from './compiler_host';
|
||||
import {AotCompilerOptions} from './compiler_options';
|
||||
import {GeneratedFile} from './generated_file';
|
||||
import {LazyRoute, listLazyRoutes, parseLazyRoute} from './lazy_routes';
|
||||
import {PartialModule} from './partial_module';
|
||||
import {StaticReflector} from './static_reflector';
|
||||
import {StaticSymbol} from './static_symbol';
|
||||
import {ResolvedStaticSymbol, StaticSymbolResolver} from './static_symbol_resolver';
|
||||
@ -304,6 +307,45 @@ export class AotCompiler {
|
||||
return messageBundle;
|
||||
}
|
||||
|
||||
emitAllPartialModules({ngModuleByPipeOrDirective, files}: NgAnalyzedModules): PartialModule[] {
|
||||
// Using reduce like this is a select many pattern (where map is a select pattern)
|
||||
return files.reduce<PartialModule[]>((r, file) => {
|
||||
r.push(...this._emitPartialModule(
|
||||
file.fileName, ngModuleByPipeOrDirective, file.directives, file.pipes, file.ngModules,
|
||||
file.injectables));
|
||||
return r;
|
||||
}, []);
|
||||
}
|
||||
|
||||
private _emitPartialModule(
|
||||
fileName: string, ngModuleByPipeOrDirective: Map<StaticSymbol, CompileNgModuleMetadata>,
|
||||
directives: StaticSymbol[], pipes: StaticSymbol[], ngModules: CompileNgModuleMetadata[],
|
||||
injectables: StaticSymbol[]): PartialModule[] {
|
||||
const classes: o.ClassStmt[] = [];
|
||||
|
||||
const context = this._createOutputContext(fileName);
|
||||
|
||||
// Process all components
|
||||
directives.forEach(directiveType => {
|
||||
const directiveMetadata = this._metadataResolver.getDirectiveMetadata(directiveType);
|
||||
if (directiveMetadata.isComponent) {
|
||||
const module = ngModuleByPipeOrDirective.get(directiveType) !;
|
||||
module ||
|
||||
error(
|
||||
`Cannot determine the module for component '${identifierName(directiveMetadata.type)}'`);
|
||||
|
||||
const {template: parsedTemplate} =
|
||||
this._parseTemplate(directiveMetadata, module, module.transitiveModule.directives);
|
||||
compileIvyComponent(context, directiveMetadata, parsedTemplate, this._reflector);
|
||||
}
|
||||
});
|
||||
|
||||
if (context.statements) {
|
||||
return [{fileName, statements: [...context.constantPool.statements, ...context.statements]}];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
emitAllImpls(analyzeResult: NgAnalyzedModules): GeneratedFile[] {
|
||||
const {ngModuleByPipeOrDirective, files} = analyzeResult;
|
||||
const sourceModules = files.map(
|
||||
@ -547,7 +589,7 @@ export class AotCompiler {
|
||||
new o.ExternalReference(moduleName, name, null), allTypeParams));
|
||||
};
|
||||
|
||||
return {statements: [], genFilePath, importExpr};
|
||||
return {statements: [], genFilePath, importExpr, constantPool: new ConstantPool()};
|
||||
}
|
||||
|
||||
private _fileNameToModuleName(importedFilePath: string, containingFilePath: string): string {
|
||||
|
14
packages/compiler/src/aot/partial_module.ts
Normal file
14
packages/compiler/src/aot/partial_module.ts
Normal file
@ -0,0 +1,14 @@
|
||||
/**
|
||||
* @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 * as o from '../output/output_ast';
|
||||
|
||||
export interface PartialModule {
|
||||
fileName: string;
|
||||
statements: o.Statement[];
|
||||
}
|
@ -36,6 +36,7 @@ export * from './aot/generated_file';
|
||||
export * from './aot/compiler_options';
|
||||
export * from './aot/compiler_host';
|
||||
export * from './aot/formatted_error';
|
||||
export * from './aot/partial_module';
|
||||
export * from './aot/static_reflector';
|
||||
export * from './aot/static_symbol';
|
||||
export * from './aot/static_symbol_resolver';
|
||||
@ -66,7 +67,7 @@ export * from './ml_parser/html_tags';
|
||||
export * from './ml_parser/interpolation_config';
|
||||
export * from './ml_parser/tags';
|
||||
export {NgModuleCompiler} from './ng_module_compiler';
|
||||
export {AssertNotNull, BinaryOperator, BinaryOperatorExpr, BuiltinMethod, BuiltinVar, CastExpr, ClassStmt, CommaExpr, CommentStmt, ConditionalExpr, DeclareFunctionStmt, DeclareVarStmt, ExpressionStatement, ExpressionVisitor, ExternalExpr, ExternalReference, FunctionExpr, IfStmt, InstantiateExpr, InvokeFunctionExpr, InvokeMethodExpr, LiteralArrayExpr, LiteralExpr, LiteralMapExpr, NotExpr, ReadKeyExpr, ReadPropExpr, ReadVarExpr, ReturnStatement, StatementVisitor, ThrowStmt, TryCatchStmt, WriteKeyExpr, WritePropExpr, WriteVarExpr, StmtModifier, Statement, collectExternalReferences} from './output/output_ast';
|
||||
export {AssertNotNull, BinaryOperator, BinaryOperatorExpr, BuiltinMethod, BuiltinVar, CastExpr, ClassMethod, ClassStmt, CommaExpr, CommentStmt, ConditionalExpr, DeclareFunctionStmt, DeclareVarStmt, ExpressionStatement, ExpressionVisitor, ExternalExpr, ExternalReference, FunctionExpr, IfStmt, InstantiateExpr, InvokeFunctionExpr, InvokeMethodExpr, LiteralArrayExpr, LiteralExpr, LiteralMapExpr, NotExpr, ReadKeyExpr, ReadPropExpr, ReadVarExpr, ReturnStatement, StatementVisitor, ThrowStmt, TryCatchStmt, WriteKeyExpr, WritePropExpr, WriteVarExpr, StmtModifier, Statement, collectExternalReferences} from './output/output_ast';
|
||||
export {EmitterVisitorContext} from './output/abstract_emitter';
|
||||
export * from './output/ts_emitter';
|
||||
export * from './parse_util';
|
||||
|
@ -6,7 +6,6 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
|
||||
import * as cdAst from '../expression_parser/ast';
|
||||
import {Identifiers} from '../identifiers';
|
||||
import * as o from '../output/output_ast';
|
||||
@ -19,13 +18,15 @@ export class ConvertActionBindingResult {
|
||||
constructor(public stmts: o.Statement[], public allowDefault: o.ReadVarExpr) {}
|
||||
}
|
||||
|
||||
export type InterpolationFunction = (args: o.Expression[]) => o.Expression;
|
||||
|
||||
/**
|
||||
* Converts the given expression AST into an executable output AST, assuming the expression is
|
||||
* used in an action binding (e.g. an event handler).
|
||||
*/
|
||||
export function convertActionBinding(
|
||||
localResolver: LocalResolver | null, implicitReceiver: o.Expression, action: cdAst.AST,
|
||||
bindingId: string): ConvertActionBindingResult {
|
||||
bindingId: string, interpolationFunction?: InterpolationFunction): ConvertActionBindingResult {
|
||||
if (!localResolver) {
|
||||
localResolver = new DefaultLocalResolver();
|
||||
}
|
||||
@ -52,7 +53,8 @@ export function convertActionBinding(
|
||||
},
|
||||
action);
|
||||
|
||||
const visitor = new _AstToIrVisitor(localResolver, implicitReceiver, bindingId);
|
||||
const visitor =
|
||||
new _AstToIrVisitor(localResolver, implicitReceiver, bindingId, interpolationFunction);
|
||||
const actionStmts: o.Statement[] = [];
|
||||
flattenStatements(actionWithoutBuiltins.visit(visitor, _Mode.Statement), actionStmts);
|
||||
prependTemporaryDecls(visitor.temporaryCount, bindingId, actionStmts);
|
||||
@ -98,6 +100,7 @@ export enum BindingForm {
|
||||
// otherise generate a general binding
|
||||
TrySimple,
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the given expression AST into an executable output AST, assuming the expression
|
||||
* is used in property binding. The expression has to be preprocessed via
|
||||
@ -105,14 +108,15 @@ export enum BindingForm {
|
||||
*/
|
||||
export function convertPropertyBinding(
|
||||
localResolver: LocalResolver | null, implicitReceiver: o.Expression,
|
||||
expressionWithoutBuiltins: cdAst.AST, bindingId: string,
|
||||
form: BindingForm): ConvertPropertyBindingResult {
|
||||
expressionWithoutBuiltins: cdAst.AST, bindingId: string, form: BindingForm,
|
||||
interpolationFunction?: InterpolationFunction): ConvertPropertyBindingResult {
|
||||
if (!localResolver) {
|
||||
localResolver = new DefaultLocalResolver();
|
||||
}
|
||||
const currValExpr = createCurrValueExpr(bindingId);
|
||||
const stmts: o.Statement[] = [];
|
||||
const visitor = new _AstToIrVisitor(localResolver, implicitReceiver, bindingId);
|
||||
const visitor =
|
||||
new _AstToIrVisitor(localResolver, implicitReceiver, bindingId, interpolationFunction);
|
||||
const outputExpr: o.Expression = expressionWithoutBuiltins.visit(visitor, _Mode.Expression);
|
||||
|
||||
if (visitor.temporaryCount) {
|
||||
@ -200,7 +204,7 @@ class _AstToIrVisitor implements cdAst.AstVisitor {
|
||||
|
||||
constructor(
|
||||
private _localResolver: LocalResolver, private _implicitReceiver: o.Expression,
|
||||
private bindingId: string) {}
|
||||
private bindingId: string, private interpolationFunction: InterpolationFunction|undefined) {}
|
||||
|
||||
visitBinary(ast: cdAst.Binary, mode: _Mode): any {
|
||||
let op: o.BinaryOperator;
|
||||
@ -303,6 +307,9 @@ class _AstToIrVisitor implements cdAst.AstVisitor {
|
||||
}
|
||||
args.push(o.literal(ast.strings[ast.strings.length - 1]));
|
||||
|
||||
if (this.interpolationFunction) {
|
||||
return this.interpolationFunction(args);
|
||||
}
|
||||
return ast.expressions.length <= 9 ?
|
||||
o.importExpr(Identifiers.inlineInterpolate).callFn(args) :
|
||||
o.importExpr(Identifiers.interpolate).callFn([args[0], o.literalArr(args.slice(1))]);
|
||||
|
141
packages/compiler/src/constant_pool.ts
Normal file
141
packages/compiler/src/constant_pool.ts
Normal file
@ -0,0 +1,141 @@
|
||||
/**
|
||||
* @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 * as o from './output/output_ast';
|
||||
import {OutputContext, error} from './util';
|
||||
|
||||
export const enum DefinitionKind {Injector, Directive, Component}
|
||||
|
||||
/**
|
||||
* A node that is a place-holder that allows the node to be replaced when the actual
|
||||
* node is known.
|
||||
*
|
||||
* This allows the constant pool to change an expression from a direct reference to
|
||||
* a constant to a shared constant. It returns a fix-up node that is later allowed to
|
||||
* change the referenced expression.
|
||||
*/
|
||||
class FixupExpression extends o.Expression {
|
||||
constructor(public resolved: o.Expression) { super(resolved.type); }
|
||||
|
||||
shared: boolean;
|
||||
|
||||
visitExpression(visitor: o.ExpressionVisitor, context: any): any {
|
||||
this.resolved.visitExpression(visitor, context);
|
||||
}
|
||||
|
||||
isEquivalent(e: o.Expression): boolean {
|
||||
return e instanceof FixupExpression && this.resolved.isEquivalent(e.resolved);
|
||||
}
|
||||
|
||||
fixup(expression: o.Expression) {
|
||||
this.resolved = expression;
|
||||
this.shared = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A constant pool allows a code emitter to share constant in an output context.
|
||||
*
|
||||
* The constant pool also supports sharing access to ivy definitions references.
|
||||
*/
|
||||
export class ConstantPool {
|
||||
statements: o.Statement[] = [];
|
||||
private literals = new Map<string, FixupExpression>();
|
||||
private injectorDefinitions = new Map<any, FixupExpression>();
|
||||
private directiveDefinitions = new Map<any, FixupExpression>();
|
||||
private componentDefintions = new Map<any, FixupExpression>();
|
||||
|
||||
private nextNameIndex = 0;
|
||||
|
||||
getConstLiteral(literal: o.Expression): o.Expression {
|
||||
const key = this.keyOf(literal);
|
||||
let fixup = this.literals.get(key);
|
||||
if (!fixup) {
|
||||
fixup = new FixupExpression(literal);
|
||||
this.literals.set(key, fixup);
|
||||
} else if (!fixup.shared) {
|
||||
// Replace the expression with a variable
|
||||
const name = this.freshName();
|
||||
this.statements.push(
|
||||
o.variable(name).set(literal).toDeclStmt(o.INFERRED_TYPE, [o.StmtModifier.Final]));
|
||||
fixup.fixup(o.variable(name));
|
||||
}
|
||||
return fixup;
|
||||
}
|
||||
|
||||
getDefinition(type: any, kind: DefinitionKind, ctx: OutputContext): o.Expression {
|
||||
const declarations = kind == DefinitionKind.Component ?
|
||||
this.componentDefintions :
|
||||
kind == DefinitionKind.Directive ? this.directiveDefinitions : this.injectorDefinitions;
|
||||
let fixup = declarations.get(type);
|
||||
if (!fixup) {
|
||||
const property = kind == DefinitionKind.Component ?
|
||||
'ngComponentDef' :
|
||||
kind == DefinitionKind.Directive ? 'ngDirectiveDef' : 'ngInjectorDef';
|
||||
fixup = new FixupExpression(ctx.importExpr(type).prop(property));
|
||||
declarations.set(type, fixup);
|
||||
} else if (!fixup.shared) {
|
||||
const name = this.freshName();
|
||||
this.statements.push(
|
||||
o.variable(name).set(fixup.resolved).toDeclStmt(o.INFERRED_TYPE, [o.StmtModifier.Final]));
|
||||
fixup.fixup(o.variable(name));
|
||||
}
|
||||
return fixup;
|
||||
}
|
||||
|
||||
/**
|
||||
* Produce a unique name.
|
||||
*
|
||||
* The name might be unique among different prefixes if any of the prefixes end in
|
||||
* a digit so the prefix should be a constant string (not based on user input) and
|
||||
* must not end in a digit.
|
||||
*/
|
||||
uniqueName(prefix: string): string { return `${prefix}${this.nextNameIndex++}`; }
|
||||
|
||||
private freshName(): string { return this.uniqueName(`_$`); }
|
||||
|
||||
private keyOf(expression: o.Expression) {
|
||||
return expression.visitExpression(new KeyVisitor(), null);
|
||||
}
|
||||
}
|
||||
|
||||
class KeyVisitor implements o.ExpressionVisitor {
|
||||
visitLiteralExpr(ast: o.LiteralExpr): string { return `${ast.value}`; }
|
||||
visitLiteralArrayExpr(ast: o.LiteralArrayExpr): string {
|
||||
return ast.entries.map(entry => entry.visitExpression(this, null)).join(',');
|
||||
}
|
||||
|
||||
visitLiteralMapExpr(ast: o.LiteralMapExpr): string {
|
||||
const entries =
|
||||
ast.entries.map(entry => `${entry.key}:${entry.value.visitExpression(this, null)}`);
|
||||
return `{${entries.join(',')}`;
|
||||
}
|
||||
|
||||
visitReadVarExpr = invalid;
|
||||
visitWriteVarExpr = invalid;
|
||||
visitWriteKeyExpr = invalid;
|
||||
visitWritePropExpr = invalid;
|
||||
visitInvokeMethodExpr = invalid;
|
||||
visitInvokeFunctionExpr = invalid;
|
||||
visitInstantiateExpr = invalid;
|
||||
visitExternalExpr = invalid;
|
||||
visitConditionalExpr = invalid;
|
||||
visitNotExpr = invalid;
|
||||
visitAssertNotNullExpr = invalid;
|
||||
visitCastExpr = invalid;
|
||||
visitFunctionExpr = invalid;
|
||||
visitBinaryOperatorExpr = invalid;
|
||||
visitReadPropExpr = invalid;
|
||||
visitReadKeyExpr = invalid;
|
||||
visitCommaExpr = invalid;
|
||||
}
|
||||
|
||||
function invalid<T>(arg: o.Expression | o.Statement): never {
|
||||
throw new Error(
|
||||
`Invalid state: Visitor ${this.constructor.name} doesn't handle ${o.constructor.name}`);
|
||||
}
|
@ -9,6 +9,7 @@
|
||||
import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompileNgModuleMetadata, CompilePipeSummary, CompileProviderMetadata, CompileStylesheetMetadata, CompileTypeSummary, ProviderMeta, ProxyClass, identifierName, ngModuleJitUrl, sharedStylesheetJitUrl, templateJitUrl, templateSourceUrl} from '../compile_metadata';
|
||||
import {CompileReflector} from '../compile_reflector';
|
||||
import {CompilerConfig} from '../config';
|
||||
import {ConstantPool} from '../constant_pool';
|
||||
import {Type} from '../core';
|
||||
import {CompileMetadataResolver} from '../metadata_resolver';
|
||||
import {NgModuleCompiler} from '../ng_module_compiler';
|
||||
@ -355,5 +356,5 @@ function assertComponent(meta: CompileDirectiveMetadata) {
|
||||
function createOutputContext(): OutputContext {
|
||||
const importExpr = (symbol: any) =>
|
||||
ir.importExpr({name: identifierName(symbol), moduleName: null, runtime: symbol});
|
||||
return {statements: [], genFilePath: '', importExpr};
|
||||
return {statements: [], genFilePath: '', importExpr, constantPool: new ConstantPool()};
|
||||
}
|
||||
|
@ -8,6 +8,7 @@
|
||||
|
||||
|
||||
import {ParseSourceSpan} from '../parse_util';
|
||||
import {error} from '../util';
|
||||
|
||||
//// Types
|
||||
export enum TypeModifier {
|
||||
@ -644,7 +645,8 @@ export const TYPED_NULL_EXPR = new LiteralExpr(null, INFERRED_TYPE, null);
|
||||
export enum StmtModifier {
|
||||
Final,
|
||||
Private,
|
||||
Exported
|
||||
Exported,
|
||||
Static,
|
||||
}
|
||||
|
||||
export abstract class Statement {
|
||||
@ -739,7 +741,9 @@ export class AbstractClassPart {
|
||||
}
|
||||
|
||||
export class ClassField extends AbstractClassPart {
|
||||
constructor(public name: string, type?: Type|null, modifiers: StmtModifier[]|null = null) {
|
||||
constructor(
|
||||
public name: string, type?: Type|null, modifiers: StmtModifier[]|null = null,
|
||||
public initializer?: Expression) {
|
||||
super(type, modifiers);
|
||||
}
|
||||
isEquivalent(f: ClassField) { return this.name === f.name; }
|
||||
@ -1370,6 +1374,10 @@ export function fn(
|
||||
return new FunctionExpr(params, body, type, sourceSpan);
|
||||
}
|
||||
|
||||
export function ifStmt(condition: Expression, thenClause: Statement[], elseClause?: Statement[]) {
|
||||
return new IfStmt(condition, thenClause, elseClause);
|
||||
}
|
||||
|
||||
export function literal(
|
||||
value: any, type?: Type | null, sourceSpan?: ParseSourceSpan | null): LiteralExpr {
|
||||
return new LiteralExpr(value, type, sourceSpan);
|
||||
|
@ -216,8 +216,15 @@ class _TsEmitterVisitor extends AbstractEmitterVisitor implements o.TypeVisitor
|
||||
// comment out as a workaround for #10967
|
||||
ctx.print(null, `/*private*/ `);
|
||||
}
|
||||
if (field.hasModifier(o.StmtModifier.Static)) {
|
||||
ctx.print(null, 'static ');
|
||||
}
|
||||
ctx.print(null, field.name);
|
||||
this._printColonType(field.type, ctx);
|
||||
if (field.initializer) {
|
||||
ctx.print(null, ' = ');
|
||||
field.initializer.visitExpression(this, ctx);
|
||||
}
|
||||
ctx.println(null, `;`);
|
||||
}
|
||||
|
||||
|
@ -7,6 +7,7 @@
|
||||
*/
|
||||
import * as chars from './chars';
|
||||
import {CompileIdentifierMetadata, identifierModuleUrl, identifierName} from './compile_metadata';
|
||||
import {error} from './util';
|
||||
|
||||
export class ParseLocation {
|
||||
constructor(
|
||||
|
85
packages/compiler/src/render3/r3_identifiers.ts
Normal file
85
packages/compiler/src/render3/r3_identifiers.ts
Normal file
@ -0,0 +1,85 @@
|
||||
/**
|
||||
* @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 * as o from '../output/output_ast';
|
||||
|
||||
const CORE = '@angular/core';
|
||||
|
||||
// Copied from core and must be in sync with the value in the runtime.
|
||||
export const enum LifeCycleGuard {ON_INIT = 1, ON_DESTROY = 2, ON_CHANGES = 4}
|
||||
|
||||
// TODO: Include assignments that use the enum literals
|
||||
// e.g. { let a: core.LifeCycleGuard.ON_INIT = LifeCycleGuard.ON_INIT; ...}
|
||||
// Ensure these get removed in bundling.
|
||||
|
||||
export class Identifiers {
|
||||
/* Methods */
|
||||
static NEW_METHOD = 'n';
|
||||
static HOST_BINDING_METHOD = 'h';
|
||||
static REFRESH_METHOD = 'r';
|
||||
|
||||
/* Instructions */
|
||||
static createElement: o.ExternalReference = {name: 'ɵE', moduleName: CORE};
|
||||
|
||||
static elementEnd: o.ExternalReference = {name: 'ɵe', moduleName: CORE};
|
||||
|
||||
static elementProperty: o.ExternalReference = {name: 'ɵp', moduleName: CORE};
|
||||
|
||||
static elementAttribute: o.ExternalReference = {name: 'ɵa', moduleName: CORE};
|
||||
|
||||
static elementClass: o.ExternalReference = {name: 'ɵk', moduleName: CORE};
|
||||
|
||||
static elementStyle: o.ExternalReference = {name: 'ɵs', moduleName: CORE};
|
||||
|
||||
static containerCreate: o.ExternalReference = {name: 'ɵC', moduleName: CORE};
|
||||
|
||||
static containerEnd: o.ExternalReference = {name: 'ɵc', moduleName: CORE};
|
||||
|
||||
static directiveCreate: o.ExternalReference = {name: 'ɵD', moduleName: CORE};
|
||||
|
||||
static text: o.ExternalReference = {name: 'ɵT', moduleName: CORE};
|
||||
|
||||
static directiveInput: o.ExternalReference = {name: 'ɵi', moduleName: CORE};
|
||||
|
||||
static textCreateBound: o.ExternalReference = {name: 'ɵt', moduleName: CORE};
|
||||
|
||||
static bind: o.ExternalReference = {name: 'ɵb', moduleName: CORE};
|
||||
|
||||
static bind1: o.ExternalReference = {name: 'ɵb1', moduleName: CORE};
|
||||
static bind2: o.ExternalReference = {name: 'ɵb2', moduleName: CORE};
|
||||
static bind3: o.ExternalReference = {name: 'ɵb3', moduleName: CORE};
|
||||
static bind4: o.ExternalReference = {name: 'ɵb4', moduleName: CORE};
|
||||
static bind5: o.ExternalReference = {name: 'ɵb5', moduleName: CORE};
|
||||
static bind6: o.ExternalReference = {name: 'ɵb6', moduleName: CORE};
|
||||
static bind7: o.ExternalReference = {name: 'ɵb7', moduleName: CORE};
|
||||
static bind8: o.ExternalReference = {name: 'ɵb8', moduleName: CORE};
|
||||
static bind9: o.ExternalReference = {name: 'ɵb9', moduleName: CORE};
|
||||
static bindV: o.ExternalReference = {name: 'ɵbV', moduleName: CORE};
|
||||
|
||||
static refreshComponent: o.ExternalReference = {name: 'ɵr', moduleName: CORE};
|
||||
|
||||
static directiveLifeCycle: o.ExternalReference = {name: 'ɵl', moduleName: CORE};
|
||||
|
||||
static injectElementRef: o.ExternalReference = {name: 'ɵinjectElementRef', moduleName: CORE};
|
||||
|
||||
static injectTemplateRef: o.ExternalReference = {name: 'ɵinjectTemplateRef', moduleName: CORE};
|
||||
|
||||
static injectViewContainerRef:
|
||||
o.ExternalReference = {name: 'ɵinjectViewContainerRef', moduleName: CORE};
|
||||
|
||||
static inject: o.ExternalReference = {name: 'ɵinject', moduleName: CORE};
|
||||
|
||||
static defineComponent: o.ExternalReference = {name: 'ɵdefineComponent', moduleName: CORE};
|
||||
|
||||
static defineDirective: o.ExternalReference = {
|
||||
name: 'ɵdefineDirective',
|
||||
moduleName: CORE,
|
||||
};
|
||||
|
||||
static NgOnChangesFeature: o.ExternalReference = {name: 'ɵNgOnChangesFeature', moduleName: CORE};
|
||||
}
|
432
packages/compiler/src/render3/r3_view_compiler.ts
Normal file
432
packages/compiler/src/render3/r3_view_compiler.ts
Normal file
@ -0,0 +1,432 @@
|
||||
/**
|
||||
* @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 {CompileDirectiveMetadata, CompilePipeSummary, CompileTokenMetadata, CompileTypeMetadata, identifierName, rendererTypeName, tokenReference, viewClassName} from '../compile_metadata';
|
||||
import {CompileReflector} from '../compile_reflector';
|
||||
import {BindingForm, BuiltinConverter, EventHandlerVars, LocalResolver, convertActionBinding, convertPropertyBinding, convertPropertyBindingBuiltins} from '../compiler_util/expression_converter';
|
||||
import {ConstantPool, DefinitionKind} from '../constant_pool';
|
||||
import {AST} from '../expression_parser/ast';
|
||||
import {Identifiers} from '../identifiers';
|
||||
import * as o from '../output/output_ast';
|
||||
import {ParseSourceSpan} from '../parse_util';
|
||||
import {CssSelector} from '../selector';
|
||||
import {AttrAst, BoundDirectivePropertyAst, BoundElementPropertyAst, BoundEventAst, BoundTextAst, DirectiveAst, ElementAst, EmbeddedTemplateAst, NgContentAst, PropertyBindingType, ProviderAst, QueryMatch, ReferenceAst, TemplateAst, TemplateAstVisitor, TextAst, VariableAst, templateVisitAll} from '../template_parser/template_ast';
|
||||
import {OutputContext, error} from '../util';
|
||||
|
||||
import {Identifiers as R3} from './r3_identifiers';
|
||||
|
||||
|
||||
/** Name of the context parameter passed into a template function */
|
||||
const CONTEXT_NAME = 'ctx';
|
||||
|
||||
/** Name of the creation mode flag passed into a template function */
|
||||
const CREATION_MODE_FLAG = 'cm';
|
||||
|
||||
/** Name of the temporary to use during data binding */
|
||||
const TEMPORARY_NAME = '_t';
|
||||
|
||||
export function compileComponent(
|
||||
outputCtx: OutputContext, component: CompileDirectiveMetadata, template: TemplateAst[],
|
||||
reflector: CompileReflector) {
|
||||
const definitionMapValues: {key: string, quoted: boolean, value: o.Expression}[] = [];
|
||||
|
||||
// e.g. `type: MyApp`
|
||||
definitionMapValues.push(
|
||||
{key: 'type', value: outputCtx.importExpr(component.type.reference), quoted: false});
|
||||
|
||||
// e.g. `tag: 'my-app'
|
||||
// This is optional and only included if the first selector of a component has element.
|
||||
const selector = component.selector && CssSelector.parse(component.selector);
|
||||
const firstSelector = selector && selector[0];
|
||||
if (firstSelector && firstSelector.hasElementSelector()) {
|
||||
definitionMapValues.push({key: 'tag', value: o.literal(firstSelector.element), quoted: false});
|
||||
}
|
||||
|
||||
// e.g. `attr: ["class", ".my.app"]
|
||||
// This is optional an only included if the first selector of a component specifies attributes.
|
||||
if (firstSelector) {
|
||||
const selectorAttributes = firstSelector.getAttrs();
|
||||
if (selectorAttributes.length) {
|
||||
definitionMapValues.push({
|
||||
key: 'attrs',
|
||||
value: outputCtx.constantPool.getConstLiteral(o.literalArr(selectorAttributes.map(
|
||||
value => value != null ? o.literal(value) : o.literal(undefined)))),
|
||||
quoted: false
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// e.g. `template: function(_ctx, _cm) {...}`
|
||||
const templateFunctionExpression =
|
||||
new TemplateDefinitionBuilder(outputCtx, outputCtx.constantPool, CONTEXT_NAME)
|
||||
.buildTemplateFunction(template);
|
||||
definitionMapValues.push({key: 'template', value: templateFunctionExpression, quoted: false});
|
||||
|
||||
|
||||
// e.g. `factory: () => new MyApp(injectElementRef())`
|
||||
const templateFactory = createFactory(component.type, outputCtx, reflector);
|
||||
definitionMapValues.push({key: 'factory', value: templateFactory, quoted: false});
|
||||
|
||||
const className = identifierName(component.type) !;
|
||||
className || error(`Cannot resolver the name of ${component.type}`);
|
||||
|
||||
// Create the partial class to be merged with the actual class.
|
||||
outputCtx.statements.push(new o.ClassStmt(
|
||||
/* name */ className,
|
||||
/* parent */ null,
|
||||
/* fields */[new o.ClassField(
|
||||
/* name */ 'ngComponentDef',
|
||||
/* type */ o.INFERRED_TYPE,
|
||||
/* modifiers */[o.StmtModifier.Static],
|
||||
/* initializer */ o.importExpr(R3.defineComponent).callFn([o.literalMap(
|
||||
definitionMapValues)]))],
|
||||
/* getters */[],
|
||||
/* constructorMethod */ new o.ClassMethod(null, [], []),
|
||||
/* methods */[]));
|
||||
}
|
||||
|
||||
|
||||
// TODO: Remove these when the things are fully supported
|
||||
function unknown<T>(arg: o.Expression | o.Statement | TemplateAst): never {
|
||||
throw new Error(`Builder ${this.constructor.name} is unable to handle ${o.constructor.name} yet`);
|
||||
}
|
||||
function unsupported(feature: string): never {
|
||||
if (this) {
|
||||
throw new Error(`Builder ${this.constructor.name} doesn't support ${feature} yet`);
|
||||
}
|
||||
throw new Error(`Feature ${feature} is supported yet`);
|
||||
}
|
||||
|
||||
const BINDING_INSTRUCTION_MAP: {[index: number]: o.ExternalReference | undefined} = {
|
||||
[PropertyBindingType.Property]: R3.elementProperty,
|
||||
[PropertyBindingType.Attribute]: R3.elementAttribute,
|
||||
[PropertyBindingType.Class]: R3.elementClass,
|
||||
[PropertyBindingType.Style]: R3.elementStyle
|
||||
};
|
||||
|
||||
function interpolate(args: o.Expression[]): o.Expression {
|
||||
args = args.slice(1); // Ignore the length prefix added for render2
|
||||
switch (args.length) {
|
||||
case 3:
|
||||
return o.importExpr(R3.bind1).callFn(args);
|
||||
case 5:
|
||||
return o.importExpr(R3.bind2).callFn(args);
|
||||
case 7:
|
||||
return o.importExpr(R3.bind3).callFn(args);
|
||||
case 9:
|
||||
return o.importExpr(R3.bind4).callFn(args);
|
||||
case 11:
|
||||
return o.importExpr(R3.bind5).callFn(args);
|
||||
case 13:
|
||||
return o.importExpr(R3.bind6).callFn(args);
|
||||
case 15:
|
||||
return o.importExpr(R3.bind7).callFn(args);
|
||||
case 17:
|
||||
return o.importExpr(R3.bind8).callFn(args);
|
||||
case 19:
|
||||
return o.importExpr(R3.bind9).callFn(args);
|
||||
}
|
||||
(args.length > 19 && args.length % 2 == 1) ||
|
||||
error(`Invalid interpolation argument length ${args.length}`);
|
||||
return o.importExpr(R3.bindV).callFn(args);
|
||||
}
|
||||
|
||||
class TemplateDefinitionBuilder implements TemplateAstVisitor {
|
||||
private _dataIndex = 0;
|
||||
private _bindingContext = 0;
|
||||
private _temporaryAllocated = false;
|
||||
private _prefix: o.Statement[] = [];
|
||||
private _creationMode: o.Statement[] = [];
|
||||
private _bindingMode: o.Statement[] = [];
|
||||
private _hostMode: o.Statement[] = [];
|
||||
private _refreshMode: o.Statement[] = [];
|
||||
private _postfix: o.Statement[] = [];
|
||||
private unsupported = unsupported;
|
||||
private invalid = invalid;
|
||||
|
||||
constructor(
|
||||
private outputCtx: OutputContext, private constantPool: ConstantPool,
|
||||
private contextParameter: string, private level = 0) {}
|
||||
|
||||
buildTemplateFunction(asts: TemplateAst[]): o.FunctionExpr {
|
||||
templateVisitAll(this, asts);
|
||||
|
||||
return o.fn(
|
||||
[
|
||||
new o.FnParam(this.contextParameter, null), new o.FnParam(CREATION_MODE_FLAG, o.BOOL_TYPE)
|
||||
],
|
||||
[
|
||||
// Temporary variable declarations (i.e. let _t: any;)
|
||||
...this._prefix,
|
||||
|
||||
// Creating mode (i.e. if (cm) { ... })
|
||||
o.ifStmt(o.variable(CREATION_MODE_FLAG), this._creationMode),
|
||||
|
||||
// Binding mode (i.e. ɵp(...))
|
||||
...this._bindingMode,
|
||||
|
||||
// Host mode (i.e. Comp.h(...))
|
||||
...this._hostMode,
|
||||
|
||||
// Refesh mode (i.e. Comp.r(...))
|
||||
...this._refreshMode,
|
||||
|
||||
// Nested templates (i.e. function CompTemplate() {})
|
||||
...this._postfix
|
||||
],
|
||||
o.INFERRED_TYPE);
|
||||
}
|
||||
|
||||
// TODO(chuckj): Implement ng-content
|
||||
visitNgContent = unknown;
|
||||
|
||||
visitElement(ast: ElementAst) {
|
||||
let bindingCount = 0;
|
||||
const elementIndex = this.allocateNode();
|
||||
|
||||
// Element creation mode
|
||||
const component = findComponent(ast.directives);
|
||||
const parameters: o.Expression[] = [o.literal(elementIndex)];
|
||||
if (component) {
|
||||
parameters.push(this.typeReference(component.directive.type.reference));
|
||||
} else {
|
||||
parameters.push(o.literal(ast.name));
|
||||
}
|
||||
|
||||
const attributes: o.Expression[] = [];
|
||||
for (let attr of ast.attrs) {
|
||||
attributes.push(o.literal(attr.name), o.literal(attr.value));
|
||||
}
|
||||
|
||||
if (attributes.length !== 0) {
|
||||
parameters.push(this.constantPool.getConstLiteral(o.literalArr(attributes)));
|
||||
}
|
||||
|
||||
this.instruction(this._creationMode, ast.sourceSpan, R3.createElement, ...parameters);
|
||||
|
||||
const implicit = o.variable(this.contextParameter);
|
||||
|
||||
// Generate element input bindings
|
||||
for (let input of ast.inputs) {
|
||||
if (input.isAnimation) {
|
||||
this.unsupported('animations');
|
||||
}
|
||||
// TODO(chuckj): Builtins transform?
|
||||
const convertedBinding = convertPropertyBinding(
|
||||
null, implicit, input.value, this.bindingContext(), BindingForm.TrySimple, interpolate);
|
||||
this._bindingMode.push(...convertedBinding.stmts);
|
||||
const parameters =
|
||||
[o.literal(elementIndex), o.literal(input.name), convertedBinding.currValExpr];
|
||||
const instruction = BINDING_INSTRUCTION_MAP[input.type];
|
||||
if (instruction) {
|
||||
// TODO(chuckj): runtime: security context?
|
||||
this.instruction(
|
||||
this._bindingMode, input.sourceSpan, instruction, o.literal(elementIndex),
|
||||
o.literal(input.name), convertedBinding.currValExpr);
|
||||
} else {
|
||||
this.unsupported(`binding ${PropertyBindingType[input.type]}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Generate directives input bindings
|
||||
this._visitDirectives(ast.directives, implicit, elementIndex);
|
||||
|
||||
// Traverse element child nodes
|
||||
templateVisitAll(this, ast.children);
|
||||
|
||||
|
||||
// Finish element construction mode.
|
||||
this.instruction(this._creationMode, ast.endSourceSpan || ast.sourceSpan, R3.elementEnd);
|
||||
}
|
||||
|
||||
private _visitDirectives(directives: DirectiveAst[], implicit: o.Expression, nodeIndex: number) {
|
||||
for (let directive of directives) {
|
||||
const directiveIndex = this.allocateDirective();
|
||||
|
||||
// Creation mode
|
||||
// e.g. D(0, TodoComponentDef.n(), TodoComponentDef);
|
||||
const directiveType = directive.directive.type.reference;
|
||||
const kind =
|
||||
directive.directive.isComponent ? DefinitionKind.Component : DefinitionKind.Directive;
|
||||
|
||||
// Note: *do not cache* calls to this.directiveOf() as the constant pool needs to know if the
|
||||
// node is referenced multiple times to know that it must generate the reference into a
|
||||
// temporary.
|
||||
|
||||
this.instruction(
|
||||
this._creationMode, directive.sourceSpan, R3.directiveCreate, o.literal(directiveIndex),
|
||||
this.definitionOf(directiveType, kind)
|
||||
.callMethod(R3.NEW_METHOD, [], directive.sourceSpan),
|
||||
this.definitionOf(directiveType, kind));
|
||||
|
||||
// Bindings
|
||||
for (const input of directive.inputs) {
|
||||
const convertedBinding = convertPropertyBinding(
|
||||
null, implicit, input.value, this.bindingContext(), BindingForm.TrySimple, interpolate);
|
||||
this._bindingMode.push(...convertedBinding.stmts);
|
||||
this.instruction(
|
||||
this._bindingMode, directive.sourceSpan, R3.elementProperty,
|
||||
o.literal(input.templateName), o.literal(nodeIndex), convertedBinding.currValExpr);
|
||||
}
|
||||
|
||||
// e.g. TodoComponentDef.h(0, 0);
|
||||
this._hostMode.push(
|
||||
this.definitionOf(directiveType, kind)
|
||||
.callMethod(R3.HOST_BINDING_METHOD, [o.literal(directiveIndex), o.literal(nodeIndex)])
|
||||
.toStmt());
|
||||
|
||||
// e.g. TodoComponentDef.r(0, 0);
|
||||
this._refreshMode.push(
|
||||
this.definitionOf(directiveType, kind)
|
||||
.callMethod(R3.REFRESH_METHOD, [o.literal(directiveIndex), o.literal(nodeIndex)])
|
||||
.toStmt());
|
||||
}
|
||||
}
|
||||
|
||||
visitEmbeddedTemplate(ast: EmbeddedTemplateAst) {
|
||||
const templateIndex = this.allocateNode();
|
||||
|
||||
const templateName = `C${templateIndex}Template`;
|
||||
const templateContext = `ctx${this.level}`;
|
||||
|
||||
// TODO(chuckj): attrs?
|
||||
|
||||
// e.g. C(1, C1Template)
|
||||
this.instruction(
|
||||
this._creationMode, ast.sourceSpan, R3.containerCreate, o.literal(templateIndex),
|
||||
o.variable(templateName));
|
||||
|
||||
// Generate directies
|
||||
this._visitDirectives(
|
||||
ast.directives, o.variable(this.contextParameter),
|
||||
// TODO(chuckj): This should be the element index of the element that contained the template
|
||||
templateIndex);
|
||||
|
||||
// Create the template function
|
||||
const templateVisitor = new TemplateDefinitionBuilder(
|
||||
this.outputCtx, this.constantPool, templateContext, this.level + 1);
|
||||
const templateFunctionExpr = templateVisitor.buildTemplateFunction(ast.children);
|
||||
this._postfix.push(templateFunctionExpr.toDeclStmt(templateName, null));
|
||||
|
||||
// Terminate the definition
|
||||
this.instruction(this._creationMode, ast.sourceSpan, R3.containerEnd);
|
||||
}
|
||||
|
||||
// These should be handled in the template or element directly.
|
||||
readonly visitReference = invalid;
|
||||
readonly visitVariable = invalid;
|
||||
readonly visitEvent = invalid;
|
||||
readonly visitElementProperty = invalid;
|
||||
readonly visitAttr = invalid;
|
||||
|
||||
visitBoundText(ast: BoundTextAst) {
|
||||
const nodeIndex = this.allocateNode();
|
||||
|
||||
// Creation mode
|
||||
this.instruction(this._creationMode, ast.sourceSpan, R3.text, o.literal(nodeIndex));
|
||||
|
||||
// Refresh mode
|
||||
this.instruction(
|
||||
this._refreshMode, ast.sourceSpan, R3.textCreateBound, o.literal(nodeIndex),
|
||||
this.bind(o.variable(this.contextParameter), ast.value, ast.sourceSpan));
|
||||
}
|
||||
|
||||
visitText(ast: TextAst) {
|
||||
// Text is defined in creation mode only.
|
||||
this.instruction(this._creationMode, ast.sourceSpan, R3.text, o.literal(ast.value));
|
||||
}
|
||||
|
||||
// These should be handled in the template or element directly
|
||||
readonly visitDirective = invalid;
|
||||
readonly visitDirectiveProperty = invalid;
|
||||
|
||||
private allocateDirective() { return this._dataIndex++; }
|
||||
private allocateNode() { return this._dataIndex++; }
|
||||
private bindingContext() { return `${this._bindingContext++}`; }
|
||||
|
||||
private instruction(
|
||||
statements: o.Statement[], span: ParseSourceSpan, reference: o.ExternalReference,
|
||||
...params: o.Expression[]) {
|
||||
statements.push(o.importExpr(reference, null, span).callFn(params, span).toStmt());
|
||||
}
|
||||
|
||||
private typeReference(type: any): o.Expression { return this.outputCtx.importExpr(type); }
|
||||
|
||||
private definitionOf(type: any, kind: DefinitionKind): o.Expression {
|
||||
return this.constantPool.getDefinition(type, kind, this.outputCtx);
|
||||
}
|
||||
|
||||
private temp(): o.ReadVarExpr {
|
||||
if (!this._temporaryAllocated) {
|
||||
this._prefix.push(o.variable(TEMPORARY_NAME, o.DYNAMIC_TYPE, null)
|
||||
.set(o.literal(undefined))
|
||||
.toDeclStmt(o.DYNAMIC_TYPE));
|
||||
this._temporaryAllocated = true;
|
||||
}
|
||||
return o.variable(TEMPORARY_NAME);
|
||||
}
|
||||
|
||||
private convertPropertyBinding(implicit: o.Expression, value: AST): o.Expression {
|
||||
const convertedPropertyBinding = convertPropertyBinding(
|
||||
null, implicit, value, this.bindingContext(), BindingForm.TrySimple, interpolate);
|
||||
this._refreshMode.push(...convertedPropertyBinding.stmts);
|
||||
return convertedPropertyBinding.currValExpr;
|
||||
}
|
||||
|
||||
private bind(implicit: o.Expression, value: AST, sourceSpan: ParseSourceSpan): o.Expression {
|
||||
return o.importExpr(R3.bind).callFn([this.convertPropertyBinding(implicit, value)]);
|
||||
}
|
||||
}
|
||||
|
||||
function createFactory(
|
||||
type: CompileTypeMetadata, outputCtx: OutputContext,
|
||||
reflector: CompileReflector): o.FunctionExpr {
|
||||
let args: o.Expression[] = [];
|
||||
|
||||
const elementRef = reflector.resolveExternalReference(Identifiers.ElementRef);
|
||||
const templateRef = reflector.resolveExternalReference(Identifiers.TemplateRef);
|
||||
const viewContainerRef = reflector.resolveExternalReference(Identifiers.ViewContainerRef);
|
||||
|
||||
for (let dependency of type.diDeps) {
|
||||
if (dependency.isValue) {
|
||||
unsupported('value dependencies');
|
||||
}
|
||||
if (dependency.isHost) {
|
||||
unsupported('host dependencies');
|
||||
}
|
||||
const token = dependency.token;
|
||||
if (token) {
|
||||
const tokenRef = tokenReference(token);
|
||||
if (tokenRef === elementRef) {
|
||||
args.push(o.importExpr(R3.injectElementRef).callFn([]));
|
||||
} else if (tokenRef === templateRef) {
|
||||
args.push(o.importExpr(R3.injectTemplateRef).callFn([]));
|
||||
} else if (tokenRef === viewContainerRef) {
|
||||
args.push(o.importExpr(R3.injectViewContainerRef).callFn([]));
|
||||
} else {
|
||||
args.push(o.importExpr(R3.inject).callFn([outputCtx.importExpr(token)]));
|
||||
}
|
||||
} else {
|
||||
unsupported('dependency without a token');
|
||||
}
|
||||
}
|
||||
|
||||
return o.fn(
|
||||
[],
|
||||
[new o.ReturnStatement(new o.InstantiateExpr(outputCtx.importExpr(type.reference), args))],
|
||||
o.INFERRED_TYPE);
|
||||
}
|
||||
|
||||
function invalid<T>(arg: o.Expression | o.Statement | TemplateAst): never {
|
||||
throw new Error(
|
||||
`Invalid state: Visitor ${this.constructor.name} doesn't handle ${o.constructor.name}`);
|
||||
}
|
||||
|
||||
function findComponent(directives: DirectiveAst[]): DirectiveAst|undefined {
|
||||
return directives.filter(directive => directive.directive.isComponent)[0];
|
||||
}
|
@ -104,6 +104,14 @@ export class CssSelector {
|
||||
`<${tagName}${classAttr}${attrs}></${tagName}>`;
|
||||
}
|
||||
|
||||
getAttrs(): string[] {
|
||||
const result: string[] = [];
|
||||
if (this.classNames.length > 0) {
|
||||
result.push('class', this.classNames.join(' '));
|
||||
}
|
||||
return result.concat(this.attrs);
|
||||
}
|
||||
|
||||
addAttribute(name: string, value: string = '') {
|
||||
this.attrs.push(name, value && value.toLowerCase() || '');
|
||||
}
|
||||
|
@ -6,6 +6,8 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {ConstantPool} from './constant_pool';
|
||||
|
||||
import * as o from './output/output_ast';
|
||||
import {ParseError} from './parse_util';
|
||||
|
||||
@ -90,6 +92,10 @@ export const SyncAsync = {
|
||||
}
|
||||
};
|
||||
|
||||
export function error(msg: string): never {
|
||||
throw new Error(`Internal Error: ${msg}`);
|
||||
}
|
||||
|
||||
export function syntaxError(msg: string, parseErrors?: ParseError[]): Error {
|
||||
const error = Error(msg);
|
||||
(error as any)[ERROR_SYNTAX_ERROR] = true;
|
||||
@ -152,6 +158,7 @@ export function utf8Encode(str: string): string {
|
||||
export interface OutputContext {
|
||||
genFilePath: string;
|
||||
statements: o.Statement[];
|
||||
constantPool: ConstantPool;
|
||||
importExpr(reference: any, typeParams?: o.Type[]|null, useSummaries?: boolean): o.Expression;
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user