feat(ivy): update compiler to specification (#21657)

PR Close #21657
This commit is contained in:
Chuck Jazdzewski 2018-01-11 15:37:56 -08:00 committed by Miško Hevery
parent 8c51c276c7
commit 86d9612230
9 changed files with 565 additions and 90 deletions

View File

@ -19,7 +19,7 @@ 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 {compileComponent as compileIvyComponent, compileDirective as compileIvyDirective} from '../render3/r3_view_compiler';
import {CompiledStylesheet, StyleCompiler} from '../style_compiler';
import {SummaryResolver} from '../summary_resolver';
import {TemplateAst} from '../template_parser/template_ast';
@ -325,7 +325,7 @@ export class AotCompiler {
const context = this._createOutputContext(fileName);
// Process all components
// Process all components and directives
directives.forEach(directiveType => {
const directiveMetadata = this._metadataResolver.getDirectiveMetadata(directiveType);
if (directiveMetadata.isComponent) {
@ -337,6 +337,8 @@ export class AotCompiler {
const {template: parsedTemplate} =
this._parseTemplate(directiveMetadata, module, module.transitiveModule.directives);
compileIvyComponent(context, directiveMetadata, parsedTemplate, this._reflector);
} else {
compileIvyDirective(context, directiveMetadata, this._reflector);
}
});

View File

@ -9,6 +9,8 @@
import * as o from './output/output_ast';
import {OutputContext, error} from './util';
const CONSTANT_PREFIX = '_c';
export const enum DefinitionKind {Injector, Directive, Component}
/**
@ -48,29 +50,34 @@ export class ConstantPool {
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 componentDefinitions = new Map<any, FixupExpression>();
private nextNameIndex = 0;
getConstLiteral(literal: o.Expression): o.Expression {
getConstLiteral(literal: o.Expression, forceShared?: boolean): o.Expression {
const key = this.keyOf(literal);
let fixup = this.literals.get(key);
let newValue = false;
if (!fixup) {
fixup = new FixupExpression(literal);
this.literals.set(key, fixup);
} else if (!fixup.shared) {
newValue = true;
}
if ((!newValue && !fixup.shared) || (newValue && forceShared)) {
// 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 :
this.componentDefinitions :
kind == DefinitionKind.Directive ? this.directiveDefinitions : this.injectorDefinitions;
let fixup = declarations.get(type);
if (!fixup) {
@ -97,7 +104,7 @@ export class ConstantPool {
*/
uniqueName(prefix: string): string { return `${prefix}${this.nextNameIndex++}`; }
private freshName(): string { return this.uniqueName(`_$`); }
private freshName(): string { return this.uniqueName(CONSTANT_PREFIX); }
private keyOf(expression: o.Expression) {
return expression.visitExpression(new KeyVisitor(), null);
@ -105,15 +112,22 @@ export class ConstantPool {
}
class KeyVisitor implements o.ExpressionVisitor {
visitLiteralExpr(ast: o.LiteralExpr): string { return `${ast.value}`; }
visitLiteralExpr(ast: o.LiteralExpr): string {
return `${typeof ast.value === 'string' ? '"' + ast.value + '"' : ast.value}`;
}
visitLiteralArrayExpr(ast: o.LiteralArrayExpr): string {
return ast.entries.map(entry => entry.visitExpression(this, null)).join(',');
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(',')}`;
const mapEntry = (entry: o.LiteralMapEntry) =>
`${entry.key}:${entry.value.visitExpression(this, null)}`;
return `{${ast.entries.map(mapEntry).join(',')}`;
}
visitExternalExpr(ast: o.ExternalExpr): string {
return ast.value.moduleName ? `EX:${ast.value.moduleName}:${ast.value.name}` :
`EX:${ast.value.runtime.name}`;
}
visitReadVarExpr = invalid;
@ -123,7 +137,6 @@ class KeyVisitor implements o.ExpressionVisitor {
visitInvokeMethodExpr = invalid;
visitInvokeFunctionExpr = invalid;
visitInstantiateExpr = invalid;
visitExternalExpr = invalid;
visitConditionalExpr = invalid;
visitNotExpr = invalid;
visitAssertNotNullExpr = invalid;

View File

@ -40,9 +40,9 @@ export type ReferenceFilter = (reference: o.ExternalReference) => boolean;
export class TypeScriptEmitter implements OutputEmitter {
emitStatementsAndContext(
genFilePath: string, stmts: o.Statement[], preamble: string = '',
emitSourceMaps: boolean = true,
referenceFilter?: ReferenceFilter): {sourceText: string, context: EmitterVisitorContext} {
const converter = new _TsEmitterVisitor(referenceFilter);
emitSourceMaps: boolean = true, referenceFilter?: ReferenceFilter,
importFilter?: ReferenceFilter): {sourceText: string, context: EmitterVisitorContext} {
const converter = new _TsEmitterVisitor(referenceFilter, importFilter);
const ctx = EmitterVisitorContext.createRoot();
@ -83,7 +83,9 @@ export class TypeScriptEmitter implements OutputEmitter {
class _TsEmitterVisitor extends AbstractEmitterVisitor implements o.TypeVisitor {
private typeExpression = 0;
constructor(private referenceFilter?: ReferenceFilter) { super(false); }
constructor(private referenceFilter?: ReferenceFilter, private importFilter?: ReferenceFilter) {
super(false);
}
importsWithPrefixes = new Map<string, string>();
reexports = new Map<string, {name: string, as: string}[]>();
@ -390,7 +392,7 @@ class _TsEmitterVisitor extends AbstractEmitterVisitor implements o.TypeVisitor
ctx.print(null, '(null as any)');
return;
}
if (moduleName) {
if (moduleName && (!this.importFilter || !this.importFilter(value))) {
let prefix = this.importsWithPrefixes.get(moduleName);
if (prefix == null) {
prefix = `i${this.importsWithPrefixes.size}`;

View File

@ -40,6 +40,10 @@ export class Identifiers {
static containerEnd: o.ExternalReference = {name: 'ɵc', moduleName: CORE};
static containerRefreshStart: o.ExternalReference = {name: 'ɵcR', moduleName: CORE};
static containerRefreshEnd: o.ExternalReference = {name: 'ɵcr', moduleName: CORE};
static directiveCreate: o.ExternalReference = {name: 'ɵD', moduleName: CORE};
static text: o.ExternalReference = {name: 'ɵT', moduleName: CORE};
@ -61,6 +65,8 @@ export class Identifiers {
static bind9: o.ExternalReference = {name: 'ɵb9', moduleName: CORE};
static bindV: o.ExternalReference = {name: 'ɵbV', moduleName: CORE};
static memory: o.ExternalReference = {name: 'ɵm', moduleName: CORE};
static refreshComponent: o.ExternalReference = {name: 'ɵr', moduleName: CORE};
static directiveLifeCycle: o.ExternalReference = {name: 'ɵl', moduleName: CORE};

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
import {CompileDirectiveMetadata, CompilePipeSummary, CompileTokenMetadata, CompileTypeMetadata, identifierName, rendererTypeName, tokenReference, viewClassName} from '../compile_metadata';
import {CompileDirectiveMetadata, CompilePipeSummary, CompileTokenMetadata, CompileTypeMetadata, flatten, 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';
@ -21,6 +21,7 @@ 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';
@ -30,15 +31,40 @@ const CREATION_MODE_FLAG = 'cm';
/** Name of the temporary to use during data binding */
const TEMPORARY_NAME = '_t';
/** The prefix reference variables */
const REFERENCE_PREFIX = '_r';
export function compileDirective(
outputCtx: OutputContext, directive: CompileDirectiveMetadata, reflector: CompileReflector) {
const definitionMapValues: {key: string, quoted: boolean, value: o.Expression}[] = [];
// e.g. `factory: () => new MyApp(injectElementRef())`
const templateFactory = createFactory(directive.type, outputCtx, reflector);
definitionMapValues.push({key: 'factory', value: templateFactory, quoted: false});
const className = identifierName(directive.type) !;
className || error(`Cannot resolver the name of ${directive.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 */ 'ngDirectiveDef',
/* type */ o.INFERRED_TYPE,
/* modifiers */[o.StmtModifier.Static],
/* initializer */ o.importExpr(R3.defineDirective).callFn([o.literalMap(
definitionMapValues)]))],
/* getters */[],
/* constructorMethod */ new o.ClassMethod(null, [], []),
/* methods */[]));
}
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);
@ -54,24 +80,27 @@ export function compileComponent(
if (selectorAttributes.length) {
definitionMapValues.push({
key: 'attrs',
value: outputCtx.constantPool.getConstLiteral(o.literalArr(selectorAttributes.map(
value => value != null ? o.literal(value) : o.literal(undefined)))),
value: outputCtx.constantPool.getConstLiteral(
o.literalArr(selectorAttributes.map(
value => value != null ? o.literal(value) : o.literal(undefined))),
/* forceShared */ true),
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});
// e.g. `template: function(_ctx, _cm) {...}`
const templateFunctionExpression =
new TemplateDefinitionBuilder(
outputCtx, outputCtx.constantPool, CONTEXT_NAME, ROOT_SCOPE.nestedScope())
.buildTemplateFunction(template);
definitionMapValues.push({key: 'template', value: templateFunctionExpression, quoted: false});
const className = identifierName(component.type) !;
className || error(`Cannot resolver the name of ${component.type}`);
@ -136,9 +165,49 @@ function interpolate(args: o.Expression[]): o.Expression {
return o.importExpr(R3.bindV).callFn(args);
}
class TemplateDefinitionBuilder implements TemplateAstVisitor {
class BindingScope {
private map = new Map<string, o.Expression>();
private referenceNameIndex = 0;
constructor(private parent: BindingScope|null) {}
get(name: string): o.Expression|null {
let current: BindingScope|null = this;
while (current) {
const value = current.map.get(name);
if (value != null) {
// Cache the value locally.
this.map.set(name, value);
return value;
}
current = current.parent;
}
return null;
}
set(name: string, variableName: string): BindingScope {
!this.map.has(name) ||
error(`The name ${name} is already defined in scope to be ${this.map.get(name)}`);
this.map.set(name, o.variable(variableName));
return this;
}
nestedScope(): BindingScope { return new BindingScope(this); }
freshReferenceName(): string {
let current: BindingScope|null = this;
// Find the top scope as it maintains the global reference count
while (current.parent) current = current.parent;
return `${REFERENCE_PREFIX}${current.referenceNameIndex++}`;
}
}
const ROOT_SCOPE = new BindingScope(null).set('$event', '$event');
class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
private _dataIndex = 0;
private _bindingContext = 0;
private _referenceIndex = 0;
private _temporaryAllocated = false;
private _prefix: o.Statement[] = [];
private _creationMode: o.Statement[] = [];
@ -151,7 +220,7 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor {
constructor(
private outputCtx: OutputContext, private constantPool: ConstantPool,
private contextParameter: string, private level = 0) {}
private contextParameter: string, private bindingScope: BindingScope, private level = 0) {}
buildTemplateFunction(asts: TemplateAst[]): o.FunctionExpr {
templateVisitAll(this, asts);
@ -173,7 +242,7 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor {
// Host mode (i.e. Comp.h(...))
...this._hostMode,
// Refesh mode (i.e. Comp.r(...))
// Refresh mode (i.e. Comp.r(...))
...this._refreshMode,
// Nested templates (i.e. function CompTemplate() {})
@ -182,31 +251,91 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor {
o.INFERRED_TYPE);
}
getLocal(name: string): o.Expression|null { return this.bindingScope.get(name); }
// TODO(chuckj): Implement ng-content
visitNgContent = unknown;
private _computeDirectivesArray(directives: DirectiveAst[]) {
const directiveIndexMap = new Map<any, number>();
const directiveExpressions: o.Expression[] =
directives.filter(directive => !directive.directive.isComponent).map(directive => {
directiveIndexMap.set(directive.directive.type.reference, this.allocateDataSlot());
return this.typeReference(directive.directive.type.reference);
});
return {
directivesArray: directiveExpressions.length ?
this.constantPool.getConstLiteral(
o.literalArr(directiveExpressions), /* forceShared */ true) :
o.literal(null),
directiveIndexMap
};
}
visitElement(ast: ElementAst) {
let bindingCount = 0;
const elementIndex = this.allocateNode();
const elementIndex = this.allocateDataSlot();
let componentIndex: number|undefined = undefined;
const referenceDataSlots = new Map<string, number>();
// Element creation mode
const component = findComponent(ast.directives);
const nullNode = o.literal(null, o.INFERRED_TYPE);
const parameters: o.Expression[] = [o.literal(elementIndex)];
// Add component type or element tag
if (component) {
parameters.push(this.typeReference(component.directive.type.reference));
componentIndex = this.allocateDataSlot();
} else {
parameters.push(o.literal(ast.name));
}
// Add attributes array
const attributes: o.Expression[] = [];
for (let attr of ast.attrs) {
attributes.push(o.literal(attr.name), o.literal(attr.value));
}
parameters.push(
attributes.length > 0 ?
this.constantPool.getConstLiteral(o.literalArr(attributes), /* forceShared */ true) :
nullNode);
if (attributes.length !== 0) {
parameters.push(this.constantPool.getConstLiteral(o.literalArr(attributes)));
// Add directives array
const {directivesArray, directiveIndexMap} = this._computeDirectivesArray(ast.directives);
parameters.push(directiveIndexMap.size > 0 ? directivesArray : nullNode);
if (component && componentIndex != null) {
// Record the data slot for the component
directiveIndexMap.set(component.directive.type.reference, componentIndex);
}
// Add references array
if (ast.references && ast.references.length > 0) {
const references =
flatten(ast.references.map(reference => {
const slot = this.allocateDataSlot();
referenceDataSlots.set(reference.name, slot);
// Generate the update temporary.
const variableName = this.bindingScope.freshReferenceName();
this._bindingMode.push(o.variable(variableName, o.INFERRED_TYPE)
.set(o.importExpr(R3.memory).callFn([o.literal(slot)]))
.toDeclStmt(o.INFERRED_TYPE, [o.StmtModifier.Final]));
this.bindingScope.set(reference.name, variableName);
return [reference.name, reference.originalValue];
})).map(value => o.literal(value));
parameters.push(
this.constantPool.getConstLiteral(o.literalArr(references), /* forceShared */ true));
} else {
parameters.push(nullNode);
}
// Remove trailing null nodes as they are implied.
while (parameters[parameters.length - 1] === nullNode) {
parameters.pop();
}
// Generate the instruction create element instruction
this.instruction(this._creationMode, ast.sourceSpan, R3.createElement, ...parameters);
const implicit = o.variable(this.contextParameter);
@ -216,9 +345,9 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor {
if (input.isAnimation) {
this.unsupported('animations');
}
// TODO(chuckj): Builtins transform?
// TODO(chuckj): Built-in transform?
const convertedBinding = convertPropertyBinding(
null, implicit, input.value, this.bindingContext(), BindingForm.TrySimple, interpolate);
this, implicit, input.value, this.bindingContext(), BindingForm.TrySimple, interpolate);
this._bindingMode.push(...convertedBinding.stmts);
const parameters =
[o.literal(elementIndex), o.literal(input.name), convertedBinding.currValExpr];
@ -234,19 +363,20 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor {
}
// Generate directives input bindings
this._visitDirectives(ast.directives, implicit, elementIndex);
this._visitDirectives(ast.directives, implicit, elementIndex, directiveIndexMap);
// 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) {
private _visitDirectives(
directives: DirectiveAst[], implicit: o.Expression, nodeIndex: number,
directiveIndexMap: Map<any, number>) {
for (let directive of directives) {
const directiveIndex = this.allocateDirective();
const directiveIndex = directiveIndexMap.get(directive.directive.type.reference);
// Creation mode
// e.g. D(0, TodoComponentDef.n(), TodoComponentDef);
@ -258,28 +388,16 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor {
// 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, 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)
@ -289,32 +407,35 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor {
}
visitEmbeddedTemplate(ast: EmbeddedTemplateAst) {
const templateIndex = this.allocateNode();
const templateIndex = this.allocateDataSlot();
const templateName = `C${templateIndex}Template`;
const templateContext = `ctx${this.level}`;
// TODO(chuckj): attrs?
const {directivesArray, directiveIndexMap} = this._computeDirectivesArray(ast.directives);
// e.g. C(1, C1Template)
this.instruction(
this._creationMode, ast.sourceSpan, R3.containerCreate, o.literal(templateIndex),
o.variable(templateName));
directivesArray, o.variable(templateName));
// Generate directies
// e.g. Cr(1)
this.instruction(
this._refreshMode, ast.sourceSpan, R3.containerRefreshStart, o.literal(templateIndex));
// Generate directives
this._visitDirectives(
ast.directives, o.variable(this.contextParameter),
// TODO(chuckj): This should be the element index of the element that contained the template
templateIndex);
ast.directives, o.variable(this.contextParameter), templateIndex, directiveIndexMap);
// e.g. cr();
this.instruction(this._refreshMode, ast.sourceSpan, R3.containerRefreshEnd);
// Create the template function
const templateVisitor = new TemplateDefinitionBuilder(
this.outputCtx, this.constantPool, templateContext, this.level + 1);
this.outputCtx, this.constantPool, templateContext, this.bindingScope.nestedScope(),
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.
@ -325,7 +446,7 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor {
readonly visitAttr = invalid;
visitBoundText(ast: BoundTextAst) {
const nodeIndex = this.allocateNode();
const nodeIndex = this.allocateDataSlot();
// Creation mode
this.instruction(this._creationMode, ast.sourceSpan, R3.text, o.literal(nodeIndex));
@ -333,20 +454,21 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor {
// Refresh mode
this.instruction(
this._refreshMode, ast.sourceSpan, R3.textCreateBound, o.literal(nodeIndex),
this.bind(o.variable(this.contextParameter), ast.value, ast.sourceSpan));
this.bind(o.variable(CONTEXT_NAME), 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));
this.instruction(
this._creationMode, ast.sourceSpan, R3.text, o.literal(this.allocateDataSlot()),
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 allocateDataSlot() { return this._dataIndex++; }
private bindingContext() { return `${this._bindingContext++}`; }
private instruction(
@ -373,13 +495,13 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor {
private convertPropertyBinding(implicit: o.Expression, value: AST): o.Expression {
const convertedPropertyBinding = convertPropertyBinding(
null, implicit, value, this.bindingContext(), BindingForm.TrySimple, interpolate);
this, 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)]);
return this.convertPropertyBinding(implicit, value);
}
}

View File

@ -110,8 +110,8 @@ export class BoundEventAst implements TemplateAst {
*/
export class ReferenceAst implements TemplateAst {
constructor(
public name: string, public value: CompileTokenMetadata, public sourceSpan: ParseSourceSpan) {
}
public name: string, public value: CompileTokenMetadata, public originalValue: string,
public sourceSpan: ParseSourceSpan) {}
visit(visitor: TemplateAstVisitor, context: any): any {
return visitor.visitReference(this, context);
}

View File

@ -574,7 +574,7 @@ class TemplateParseVisitor implements html.Visitor {
if ((elOrDirRef.value.length === 0 && directive.isComponent) ||
(elOrDirRef.isReferenceToDirective(directive))) {
targetReferences.push(new ReferenceAst(
elOrDirRef.name, createTokenForReference(directive.type.reference),
elOrDirRef.name, createTokenForReference(directive.type.reference), elOrDirRef.value,
elOrDirRef.sourceSpan));
matchedReferences.add(elOrDirRef.name);
}
@ -598,7 +598,8 @@ class TemplateParseVisitor implements html.Visitor {
if (isTemplateElement) {
refToken = createTokenForExternalReference(this.reflector, Identifiers.TemplateRef);
}
targetReferences.push(new ReferenceAst(elOrDirRef.name, refToken, elOrDirRef.sourceSpan));
targetReferences.push(
new ReferenceAst(elOrDirRef.name, refToken, elOrDirRef.value, elOrDirRef.sourceSpan));
}
});
return directiveAsts;

View File

@ -6,13 +6,13 @@
* found in the LICENSE file at https://angular.io/license
*/
import {AotCompilerHost, AotCompilerOptions, AotSummaryResolver, CompileMetadataResolver, CompilerConfig, DirectiveNormalizer, DirectiveResolver, DomElementSchemaRegistry, HtmlParser, I18NHtmlParser, Lexer, NgModuleResolver, Parser, PipeResolver, StaticReflector, StaticSymbol, StaticSymbolCache, StaticSymbolResolver, TemplateParser, TypeScriptEmitter, analyzeNgModules, createAotUrlResolver} from '@angular/compiler';
import {AotCompilerHost, AotCompilerOptions, AotSummaryResolver, CompileDirectiveMetadata, CompileMetadataResolver, CompilerConfig, DirectiveNormalizer, DirectiveResolver, DomElementSchemaRegistry, HtmlParser, I18NHtmlParser, Lexer, NgModuleResolver, Parser, PipeResolver, StaticReflector, StaticSymbol, StaticSymbolCache, StaticSymbolResolver, TemplateParser, TypeScriptEmitter, analyzeNgModules, createAotUrlResolver} from '@angular/compiler';
import {ViewEncapsulation} from '@angular/core';
import * as ts from 'typescript';
import {ConstantPool} from '../../src/constant_pool';
import * as o from '../../src/output/output_ast';
import {compileComponent} from '../../src/render3/r3_view_compiler';
import {compileComponent, compileDirective} from '../../src/render3/r3_view_compiler';
import {OutputContext} from '../../src/util';
import {MockAotCompilerHost, MockCompilerHost, MockData, MockDirectory, arrayToMockDir, settings, setup, toMockFileArray} from '../aot/test_util';
@ -99,8 +99,327 @@ describe('r3_view_compiler', () => {
const result = compile(files, angularFiles);
expect(result.source).toContain('@angular/core');
});
/* These tests are codified version of the tests in compiler_canonical_spec.ts. Every
* test in compiler_canonical_spec.ts should have a corresponding test here.
*/
describe('compiler conformance', () => {
describe('elements', () => {
it('should translate DOM structure', () => {
const files = {
app: {
'spec.ts': `
import {Component, NgModule} from '@angular/core';
@Component({
selector: 'my-component',
template: \`<div class="my-app" title="Hello">Hello <b>World</b>!</div>\`
})
export class MyComponent {}
@NgModule({declarations: [MyComponent]})
export class MyModule {}
`
}
};
// The factory should look like this:
const factory = 'factory: () => { return new MyComponent(); }';
// The template should look like this (where IDENT is a wild card for an identifier):
const template = `
template: (ctx: IDENT, cm: IDENT) => {
if (cm) {
IDENT.ɵE(0, 'div', IDENT);
IDENT.ɵT(1, 'Hello ');
IDENT.ɵE(2, 'b');
IDENT.ɵT(3, 'World');
IDENT.ɵe();
IDENT.ɵT(4, '!');
IDENT.ɵe();
}
}
`;
// The compiler should also emit a const array like this:
const constants = `const IDENT = ['class', 'my-app', 'title', 'Hello'];`;
const result = compile(files, angularFiles);
expectEmit(result.source, factory, 'Incorrect factory');
expectEmit(result.source, template, 'Incorrect template');
expectEmit(result.source, constants, 'Incorrect shared constants');
});
});
});
describe('components & directives', () => {
it('should instantiate directives', () => {
const files = {
app: {
'spec.ts': `
import {Component, Directive, NgModule} from '@angular/core';
@Component({selector: 'child', template: 'child-view'})
export class ChildComponent {}
@Directive({selector: '[some-directive]'})
export class SomeDirective {}
@Component({selector: 'my-component', template: '<child some-directive></child>!'})
export class MyComponent {}
@NgModule({declarations: [ChildComponent, SomeDirective, MyComponent]})
export class MyModule{}
`
}
};
// ChildComponent definition should be:
const ChildComponentDefinition = `
static ngComponentDef = IDENT.ɵdefineComponent({
tag: 'child',
factory: () => { return new ChildComponent(); },
template: (ctx: IDENT, cm: IDENT) => {
if (cm) {
IDENT.ɵT(0, 'child-view');
}
}
});`;
// SomeDirective definition should be:
const SomeDirectiveDefinition = `
static ngDirectiveDef = IDENT.ɵdefineDirective({
factory: () => {return new SomeDirective(); }
});
`;
// MyComponent definition should be:
const MyComponentDefinition = `
static ngComponentDef = IDENT.ɵdefineComponent({
tag: 'my-component',
factory: () => { return new MyComponent(); },
template: (ctx: IDENT, cm: IDENT) => {
if (cm) {
IDENT.ɵE(0, ChildComponent, IDENT, IDENT);
IDENT.ɵe();
IDENT.ɵT(3, '!');
}
ChildComponent.ngComponentDef.r(1, 0);
SomeDirective.ngDirectiveDef.r(2, 0);
}
});
`;
// The following constants should be emitted as well.
const AttributesConstant = `const IDENT = ['some-directive', ''];`;
const DirectivesConstant = `const IDENT = [SomeDirective];`;
const result = compile(files, angularFiles);
const source = result.source;
expectEmit(source, ChildComponentDefinition, 'Incorrect ChildComponent.ngComponentDef');
expectEmit(source, SomeDirectiveDefinition, 'Incorrect SomeDirective.ngDirectiveDef');
expectEmit(source, MyComponentDefinition, 'Incorrect MyComponentDefinition.ngComponentDef');
expectEmit(source, AttributesConstant, 'Incorrect shared attributes constant');
expectEmit(source, DirectivesConstant, 'Incorrect share directives constant');
});
it('should support structural directives', () => {
const files = {
app: {
'spec.ts': `
import {Component, Directive, NgModule, TemplateRef} from '@angular/core';
@Directive({selector: '[if]'})
export class IfDirective {
constructor(template: TemplateRef<any>) { }
}
@Component({
selector: 'my-component',
template: '<ul #foo><li *if>{{salutation}} {{foo}}</li></ul>'
})
export class MyComponent {
salutation = 'Hello';
}
@NgModule({declarations: [IfDirective, MyComponent]})
export class MyModule {}
`
}
};
const IfDirectiveDefinition = `
static ngDirectiveDef = IDENT.ɵdefineDirective({
factory: () => { return new IfDirective(IDENT.ɵinjectTemplateRef()); }
});`;
const MyComponentDefinition = `
static ngComponentDef = IDENT.ɵdefineComponent({
tag: 'my-component',
factory: () => { return new MyComponent(); },
template: (ctx: IDENT, cm: IDENT) => {
if (cm) {
IDENT.ɵE(0, 'ul', null, null, IDENT);
IDENT.ɵC(2, IDENT, C2Template);
IDENT.ɵe();
}
const IDENT = IDENT.ɵm(1);
IDENT.ɵcR(2);
IfDirective.ngDirectiveDef.r(3,2);
IDENT.ɵcr();
function C2Template(ctx0: IDENT, cm: IDENT) {
if (cm) {
IDENT.ɵE(0, 'li');
IDENT.ɵT(1);
IDENT.ɵe();
}
IDENT.ɵt(1, IDENT.ɵb2('', ctx.salutation, ' ', IDENT, ''));
}
}
});`;
const locals = `const IDENT = ['foo', ''];`;
const directives = `const IDENT = [IfDirective];`;
const result = compile(files, angularFiles);
const source = result.source;
expectEmit(source, IfDirectiveDefinition, 'Incorrect IfDirective.ngDirectiveDef');
expectEmit(source, MyComponentDefinition, 'Incorrect MyComponent.ngComponentDef');
expectEmit(source, locals, 'Incorrect share locals constant');
expectEmit(source, directives, 'Incorrect shared directive constant');
});
it('local reference', () => {
const files = {
app: {
'spec.ts': `
import {Component, NgModule} from '@angular/core';
@Component({selector: 'my-component', template: '<input #user>Hello {{user.value}}!'})
export class MyComponent {}
@NgModule({declarations: [MyComponent]})
export class MyModule {}
`
}
};
const MyComponentDefinition = `
static ngComponentDef = IDENT.ɵdefineComponent({
tag: 'my-component',
factory: () => { return new MyComponent(); },
template: (ctx: IDENT, cm: IDENT) => {
if (cm) {
IDENT.ɵE(0, 'input', null, null, IDENT);
IDENT.ɵe();
IDENT.ɵT(2);
}
const IDENT = IDENT.ɵm(1);
IDENT.ɵt(2, IDENT.ɵb1('Hello ', IDENT.value, '!'));
}
});
`;
const locals = `
const IDENT = ['user', ''];
`;
const result = compile(files, angularFiles);
const source = result.source;
expectEmit(source, MyComponentDefinition, 'Incorrect MyComponent.ngComponentDef');
expectEmit(source, locals, 'Incorrect locals constant definition');
});
});
});
const IDENTIFIER = /[A-Za-z_$ɵ][A-Za-z0-9_$]*/;
const OPERATOR =
/!|%|\*|\/|\^|\&|\&\&\|\||\|\||\(|\)|\{|\}|\[|\]|:|;|\.|<|<=|>|>=|=|==|===|!=|!==|=>|\+|\+\+|-|--|@|,|\.|\.\.\./;
const STRING = /\'[^'\n]*\'|"[^'\n]*"|`[^`]*`/;
const NUMBER = /[0-9]+/;
const TOKEN = new RegExp(
`^((${IDENTIFIER.source})|(${OPERATOR.source})|(${STRING.source})|${NUMBER.source})`);
const WHITESPACE = /^\s+/;
type Piece = string | RegExp;
const IDENT = /[A-Za-z$_][A-Za-z0-9$_]*/;
function tokenize(text: string): Piece[] {
function matches(exp: RegExp): string|false {
const m = text.match(exp);
if (!m) return false;
text = text.substr(m[0].length);
return m[0];
}
function next(): string {
const result = matches(TOKEN);
if (!result) {
throw Error(`Invalid test, no token found for '${text.substr(0, 30)}...'`);
}
matches(WHITESPACE);
return result;
}
const pieces: Piece[] = [];
matches(WHITESPACE);
while (text) {
const token = next();
if (token === 'IDENT') {
pieces.push(IDENT);
} else {
pieces.push(token);
}
}
return pieces;
}
const contextWidth = 100;
function expectEmit(source: string, emitted: string, description: string) {
const pieces = tokenize(emitted);
const expr = r(...pieces);
if (!expr.test(source)) {
let last: number = 0;
for (let i = 1; i < pieces.length; i++) {
let t = r(...pieces.slice(0, i));
let m = source.match(t);
let expected = pieces[i - 1] == IDENT ? '<IDENT>' : pieces[i - 1];
if (!m) {
const contextPieceWidth = contextWidth / 2;
fail(
`${description}: Expected to find ${expected} '${source.substr(0,last)}[<---HERE]${source.substr(last)}'`);
return;
} else {
last = (m.index || 0) + m[0].length;
}
}
fail(
'Test helper failure: Expected expression failed but the reporting logic could not find where it failed');
}
}
const IDENT_LIKE = /^[a-z][A-Z]/;
const SPECIAL_RE_CHAR = /\/|\(|\)|\||\*|\+|\[|\]|\{|\}/g;
function r(...pieces: (string | RegExp)[]): RegExp {
let results: string[] = [];
let first = true;
for (const piece of pieces) {
if (!first)
results.push(`\\s${typeof piece === 'string' && IDENT_LIKE.test(piece) ? '+' : '*'}`);
first = false;
if (typeof piece === 'string') {
results.push(piece.replace(SPECIAL_RE_CHAR, s => '\\' + s));
} else {
results.push('(' + piece.source + ')');
}
}
return new RegExp(results.join(''));
}
function compile(
data: MockDirectory, angularFiles: MockData, options: AotCompilerOptions = {},
errorCollector: (error: any, fileName?: string) => void = error => { throw error; }) {
@ -156,7 +475,7 @@ function compile(
const directives = Array.from(analyzedModules.ngModuleByPipeOrDirective.keys());
const fakeOuputContext: OutputContext = {
const fakeOutputContext: OutputContext = {
genFilePath: 'fakeFactory.ts',
statements: [],
importExpr(symbol: StaticSymbol, typeParams: o.Type[]) {
@ -182,7 +501,10 @@ function compile(
// Compile the directives.
for (const directive of directives) {
const module = analyzedModules.ngModuleByPipeOrDirective.get(directive) !;
const module = analyzedModules.ngModuleByPipeOrDirective.get(directive);
if (!module || !module.type.reference.filePath.startsWith('/app')) {
continue;
}
if (resolver.isDirective(directive)) {
const metadata = resolver.getDirectiveMetadata(directive);
if (metadata.isComponent) {
@ -196,17 +518,24 @@ function compile(
const parsedTemplate = templateParser.parse(
metadata, htmlAst, directives, pipes, module.schemas, fakeUrl, false);
compileComponent(fakeOuputContext, metadata, parsedTemplate.template, staticReflector);
compileComponent(fakeOutputContext, metadata, parsedTemplate.template, staticReflector);
} else {
compileDirective(fakeOutputContext, metadata, staticReflector);
}
}
}
fakeOuputContext.statements.unshift(...fakeOuputContext.constantPool.statements);
fakeOutputContext.statements.unshift(...fakeOutputContext.constantPool.statements);
const emitter = new TypeScriptEmitter();
const result = emitter.emitStatementsAndContext(
fakeOuputContext.genFilePath, fakeOuputContext.statements, '', false);
const moduleName = compilerHost.fileNameToModuleName(
fakeOutputContext.genFilePath, fakeOutputContext.genFilePath);
return {source: result.sourceText, outputContext: fakeOuputContext};
const result = emitter.emitStatementsAndContext(
fakeOutputContext.genFilePath, fakeOutputContext.statements, '', false,
/* referenceFilter */ undefined,
/* importFilter */ e => e.moduleName != null && e.moduleName.startsWith('/app'));
return {source: result.sourceText, outputContext: fakeOutputContext};
}

View File

@ -407,7 +407,7 @@ class ArrayConsole implements Console {
expectVisitedNode(
new class extends
NullVisitor{visitReference(ast: ReferenceAst, context: any): any{return ast;}},
new ReferenceAst('foo', null !, null !));
new ReferenceAst('foo', null !, null !, null !));
});
it('should visit VariableAst', () => {
@ -474,7 +474,7 @@ class ArrayConsole implements Console {
new NgContentAst(0, 0, null !),
new EmbeddedTemplateAst([], [], [], [], [], [], false, [], [], 0, null !),
new ElementAst('foo', [], [], [], [], [], [], false, [], [], 0, null !, null !),
new ReferenceAst('foo', null !, null !), new VariableAst('foo', 'bar', null !),
new ReferenceAst('foo', null !, 'bar', null !), new VariableAst('foo', 'bar', null !),
new BoundEventAst('foo', 'bar', 'goo', null !, null !),
new BoundElementPropertyAst('foo', null !, null !, null !, 'bar', null !),
new AttrAst('foo', 'bar', null !), new BoundTextAst(null !, 0, null !),