parent
8c51c276c7
commit
86d9612230
@ -19,7 +19,7 @@ import {NgModuleCompiler} from '../ng_module_compiler';
|
|||||||
import {OutputEmitter} from '../output/abstract_emitter';
|
import {OutputEmitter} from '../output/abstract_emitter';
|
||||||
import * as o from '../output/output_ast';
|
import * as o from '../output/output_ast';
|
||||||
import {ParseError} from '../parse_util';
|
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 {CompiledStylesheet, StyleCompiler} from '../style_compiler';
|
||||||
import {SummaryResolver} from '../summary_resolver';
|
import {SummaryResolver} from '../summary_resolver';
|
||||||
import {TemplateAst} from '../template_parser/template_ast';
|
import {TemplateAst} from '../template_parser/template_ast';
|
||||||
@ -325,7 +325,7 @@ export class AotCompiler {
|
|||||||
|
|
||||||
const context = this._createOutputContext(fileName);
|
const context = this._createOutputContext(fileName);
|
||||||
|
|
||||||
// Process all components
|
// Process all components and directives
|
||||||
directives.forEach(directiveType => {
|
directives.forEach(directiveType => {
|
||||||
const directiveMetadata = this._metadataResolver.getDirectiveMetadata(directiveType);
|
const directiveMetadata = this._metadataResolver.getDirectiveMetadata(directiveType);
|
||||||
if (directiveMetadata.isComponent) {
|
if (directiveMetadata.isComponent) {
|
||||||
@ -337,6 +337,8 @@ export class AotCompiler {
|
|||||||
const {template: parsedTemplate} =
|
const {template: parsedTemplate} =
|
||||||
this._parseTemplate(directiveMetadata, module, module.transitiveModule.directives);
|
this._parseTemplate(directiveMetadata, module, module.transitiveModule.directives);
|
||||||
compileIvyComponent(context, directiveMetadata, parsedTemplate, this._reflector);
|
compileIvyComponent(context, directiveMetadata, parsedTemplate, this._reflector);
|
||||||
|
} else {
|
||||||
|
compileIvyDirective(context, directiveMetadata, this._reflector);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -9,6 +9,8 @@
|
|||||||
import * as o from './output/output_ast';
|
import * as o from './output/output_ast';
|
||||||
import {OutputContext, error} from './util';
|
import {OutputContext, error} from './util';
|
||||||
|
|
||||||
|
const CONSTANT_PREFIX = '_c';
|
||||||
|
|
||||||
export const enum DefinitionKind {Injector, Directive, Component}
|
export const enum DefinitionKind {Injector, Directive, Component}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -48,29 +50,34 @@ export class ConstantPool {
|
|||||||
private literals = new Map<string, FixupExpression>();
|
private literals = new Map<string, FixupExpression>();
|
||||||
private injectorDefinitions = new Map<any, FixupExpression>();
|
private injectorDefinitions = new Map<any, FixupExpression>();
|
||||||
private directiveDefinitions = 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;
|
private nextNameIndex = 0;
|
||||||
|
|
||||||
getConstLiteral(literal: o.Expression): o.Expression {
|
getConstLiteral(literal: o.Expression, forceShared?: boolean): o.Expression {
|
||||||
const key = this.keyOf(literal);
|
const key = this.keyOf(literal);
|
||||||
let fixup = this.literals.get(key);
|
let fixup = this.literals.get(key);
|
||||||
|
let newValue = false;
|
||||||
if (!fixup) {
|
if (!fixup) {
|
||||||
fixup = new FixupExpression(literal);
|
fixup = new FixupExpression(literal);
|
||||||
this.literals.set(key, fixup);
|
this.literals.set(key, fixup);
|
||||||
} else if (!fixup.shared) {
|
newValue = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((!newValue && !fixup.shared) || (newValue && forceShared)) {
|
||||||
// Replace the expression with a variable
|
// Replace the expression with a variable
|
||||||
const name = this.freshName();
|
const name = this.freshName();
|
||||||
this.statements.push(
|
this.statements.push(
|
||||||
o.variable(name).set(literal).toDeclStmt(o.INFERRED_TYPE, [o.StmtModifier.Final]));
|
o.variable(name).set(literal).toDeclStmt(o.INFERRED_TYPE, [o.StmtModifier.Final]));
|
||||||
fixup.fixup(o.variable(name));
|
fixup.fixup(o.variable(name));
|
||||||
}
|
}
|
||||||
|
|
||||||
return fixup;
|
return fixup;
|
||||||
}
|
}
|
||||||
|
|
||||||
getDefinition(type: any, kind: DefinitionKind, ctx: OutputContext): o.Expression {
|
getDefinition(type: any, kind: DefinitionKind, ctx: OutputContext): o.Expression {
|
||||||
const declarations = kind == DefinitionKind.Component ?
|
const declarations = kind == DefinitionKind.Component ?
|
||||||
this.componentDefintions :
|
this.componentDefinitions :
|
||||||
kind == DefinitionKind.Directive ? this.directiveDefinitions : this.injectorDefinitions;
|
kind == DefinitionKind.Directive ? this.directiveDefinitions : this.injectorDefinitions;
|
||||||
let fixup = declarations.get(type);
|
let fixup = declarations.get(type);
|
||||||
if (!fixup) {
|
if (!fixup) {
|
||||||
@ -97,7 +104,7 @@ export class ConstantPool {
|
|||||||
*/
|
*/
|
||||||
uniqueName(prefix: string): string { return `${prefix}${this.nextNameIndex++}`; }
|
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) {
|
private keyOf(expression: o.Expression) {
|
||||||
return expression.visitExpression(new KeyVisitor(), null);
|
return expression.visitExpression(new KeyVisitor(), null);
|
||||||
@ -105,15 +112,22 @@ export class ConstantPool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class KeyVisitor implements o.ExpressionVisitor {
|
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 {
|
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 {
|
visitLiteralMapExpr(ast: o.LiteralMapExpr): string {
|
||||||
const entries =
|
const mapEntry = (entry: o.LiteralMapEntry) =>
|
||||||
ast.entries.map(entry => `${entry.key}:${entry.value.visitExpression(this, null)}`);
|
`${entry.key}:${entry.value.visitExpression(this, null)}`;
|
||||||
return `{${entries.join(',')}`;
|
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;
|
visitReadVarExpr = invalid;
|
||||||
@ -123,7 +137,6 @@ class KeyVisitor implements o.ExpressionVisitor {
|
|||||||
visitInvokeMethodExpr = invalid;
|
visitInvokeMethodExpr = invalid;
|
||||||
visitInvokeFunctionExpr = invalid;
|
visitInvokeFunctionExpr = invalid;
|
||||||
visitInstantiateExpr = invalid;
|
visitInstantiateExpr = invalid;
|
||||||
visitExternalExpr = invalid;
|
|
||||||
visitConditionalExpr = invalid;
|
visitConditionalExpr = invalid;
|
||||||
visitNotExpr = invalid;
|
visitNotExpr = invalid;
|
||||||
visitAssertNotNullExpr = invalid;
|
visitAssertNotNullExpr = invalid;
|
||||||
|
@ -40,9 +40,9 @@ export type ReferenceFilter = (reference: o.ExternalReference) => boolean;
|
|||||||
export class TypeScriptEmitter implements OutputEmitter {
|
export class TypeScriptEmitter implements OutputEmitter {
|
||||||
emitStatementsAndContext(
|
emitStatementsAndContext(
|
||||||
genFilePath: string, stmts: o.Statement[], preamble: string = '',
|
genFilePath: string, stmts: o.Statement[], preamble: string = '',
|
||||||
emitSourceMaps: boolean = true,
|
emitSourceMaps: boolean = true, referenceFilter?: ReferenceFilter,
|
||||||
referenceFilter?: ReferenceFilter): {sourceText: string, context: EmitterVisitorContext} {
|
importFilter?: ReferenceFilter): {sourceText: string, context: EmitterVisitorContext} {
|
||||||
const converter = new _TsEmitterVisitor(referenceFilter);
|
const converter = new _TsEmitterVisitor(referenceFilter, importFilter);
|
||||||
|
|
||||||
const ctx = EmitterVisitorContext.createRoot();
|
const ctx = EmitterVisitorContext.createRoot();
|
||||||
|
|
||||||
@ -83,7 +83,9 @@ export class TypeScriptEmitter implements OutputEmitter {
|
|||||||
class _TsEmitterVisitor extends AbstractEmitterVisitor implements o.TypeVisitor {
|
class _TsEmitterVisitor extends AbstractEmitterVisitor implements o.TypeVisitor {
|
||||||
private typeExpression = 0;
|
private typeExpression = 0;
|
||||||
|
|
||||||
constructor(private referenceFilter?: ReferenceFilter) { super(false); }
|
constructor(private referenceFilter?: ReferenceFilter, private importFilter?: ReferenceFilter) {
|
||||||
|
super(false);
|
||||||
|
}
|
||||||
|
|
||||||
importsWithPrefixes = new Map<string, string>();
|
importsWithPrefixes = new Map<string, string>();
|
||||||
reexports = new Map<string, {name: string, as: 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)');
|
ctx.print(null, '(null as any)');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (moduleName) {
|
if (moduleName && (!this.importFilter || !this.importFilter(value))) {
|
||||||
let prefix = this.importsWithPrefixes.get(moduleName);
|
let prefix = this.importsWithPrefixes.get(moduleName);
|
||||||
if (prefix == null) {
|
if (prefix == null) {
|
||||||
prefix = `i${this.importsWithPrefixes.size}`;
|
prefix = `i${this.importsWithPrefixes.size}`;
|
||||||
|
@ -40,6 +40,10 @@ export class Identifiers {
|
|||||||
|
|
||||||
static containerEnd: o.ExternalReference = {name: 'ɵc', moduleName: CORE};
|
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 directiveCreate: o.ExternalReference = {name: 'ɵD', moduleName: CORE};
|
||||||
|
|
||||||
static text: o.ExternalReference = {name: 'ɵT', 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 bind9: o.ExternalReference = {name: 'ɵb9', moduleName: CORE};
|
||||||
static bindV: o.ExternalReference = {name: 'ɵbV', 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 refreshComponent: o.ExternalReference = {name: 'ɵr', moduleName: CORE};
|
||||||
|
|
||||||
static directiveLifeCycle: o.ExternalReference = {name: 'ɵl', moduleName: CORE};
|
static directiveLifeCycle: o.ExternalReference = {name: 'ɵl', moduleName: CORE};
|
||||||
|
@ -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, 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 {CompileReflector} from '../compile_reflector';
|
||||||
import {BindingForm, BuiltinConverter, EventHandlerVars, LocalResolver, convertActionBinding, convertPropertyBinding, convertPropertyBindingBuiltins} from '../compiler_util/expression_converter';
|
import {BindingForm, BuiltinConverter, EventHandlerVars, LocalResolver, convertActionBinding, convertPropertyBinding, convertPropertyBindingBuiltins} from '../compiler_util/expression_converter';
|
||||||
import {ConstantPool, DefinitionKind} from '../constant_pool';
|
import {ConstantPool, DefinitionKind} from '../constant_pool';
|
||||||
@ -21,6 +21,7 @@ import {OutputContext, error} from '../util';
|
|||||||
import {Identifiers as R3} from './r3_identifiers';
|
import {Identifiers as R3} from './r3_identifiers';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/** Name of the context parameter passed into a template function */
|
/** Name of the context parameter passed into a template function */
|
||||||
const CONTEXT_NAME = 'ctx';
|
const CONTEXT_NAME = 'ctx';
|
||||||
|
|
||||||
@ -30,15 +31,40 @@ const CREATION_MODE_FLAG = 'cm';
|
|||||||
/** Name of the temporary to use during data binding */
|
/** Name of the temporary to use during data binding */
|
||||||
const TEMPORARY_NAME = '_t';
|
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(
|
export function compileComponent(
|
||||||
outputCtx: OutputContext, component: CompileDirectiveMetadata, template: TemplateAst[],
|
outputCtx: OutputContext, component: CompileDirectiveMetadata, template: TemplateAst[],
|
||||||
reflector: CompileReflector) {
|
reflector: CompileReflector) {
|
||||||
const definitionMapValues: {key: string, quoted: boolean, value: o.Expression}[] = [];
|
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'
|
// e.g. `tag: 'my-app'
|
||||||
// This is optional and only included if the first selector of a component has element.
|
// This is optional and only included if the first selector of a component has element.
|
||||||
const selector = component.selector && CssSelector.parse(component.selector);
|
const selector = component.selector && CssSelector.parse(component.selector);
|
||||||
@ -54,24 +80,27 @@ export function compileComponent(
|
|||||||
if (selectorAttributes.length) {
|
if (selectorAttributes.length) {
|
||||||
definitionMapValues.push({
|
definitionMapValues.push({
|
||||||
key: 'attrs',
|
key: 'attrs',
|
||||||
value: outputCtx.constantPool.getConstLiteral(o.literalArr(selectorAttributes.map(
|
value: outputCtx.constantPool.getConstLiteral(
|
||||||
value => value != null ? o.literal(value) : o.literal(undefined)))),
|
o.literalArr(selectorAttributes.map(
|
||||||
|
value => value != null ? o.literal(value) : o.literal(undefined))),
|
||||||
|
/* forceShared */ true),
|
||||||
quoted: false
|
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())`
|
// e.g. `factory: () => new MyApp(injectElementRef())`
|
||||||
const templateFactory = createFactory(component.type, outputCtx, reflector);
|
const templateFactory = createFactory(component.type, outputCtx, reflector);
|
||||||
definitionMapValues.push({key: 'factory', value: templateFactory, quoted: false});
|
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) !;
|
const className = identifierName(component.type) !;
|
||||||
className || error(`Cannot resolver the name of ${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);
|
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 _dataIndex = 0;
|
||||||
private _bindingContext = 0;
|
private _bindingContext = 0;
|
||||||
|
private _referenceIndex = 0;
|
||||||
private _temporaryAllocated = false;
|
private _temporaryAllocated = false;
|
||||||
private _prefix: o.Statement[] = [];
|
private _prefix: o.Statement[] = [];
|
||||||
private _creationMode: o.Statement[] = [];
|
private _creationMode: o.Statement[] = [];
|
||||||
@ -151,7 +220,7 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor {
|
|||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private outputCtx: OutputContext, private constantPool: ConstantPool,
|
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 {
|
buildTemplateFunction(asts: TemplateAst[]): o.FunctionExpr {
|
||||||
templateVisitAll(this, asts);
|
templateVisitAll(this, asts);
|
||||||
@ -173,7 +242,7 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor {
|
|||||||
// Host mode (i.e. Comp.h(...))
|
// Host mode (i.e. Comp.h(...))
|
||||||
...this._hostMode,
|
...this._hostMode,
|
||||||
|
|
||||||
// Refesh mode (i.e. Comp.r(...))
|
// Refresh mode (i.e. Comp.r(...))
|
||||||
...this._refreshMode,
|
...this._refreshMode,
|
||||||
|
|
||||||
// Nested templates (i.e. function CompTemplate() {})
|
// Nested templates (i.e. function CompTemplate() {})
|
||||||
@ -182,31 +251,91 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor {
|
|||||||
o.INFERRED_TYPE);
|
o.INFERRED_TYPE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getLocal(name: string): o.Expression|null { return this.bindingScope.get(name); }
|
||||||
|
|
||||||
// TODO(chuckj): Implement ng-content
|
// TODO(chuckj): Implement ng-content
|
||||||
visitNgContent = unknown;
|
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) {
|
visitElement(ast: ElementAst) {
|
||||||
let bindingCount = 0;
|
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
|
// Element creation mode
|
||||||
const component = findComponent(ast.directives);
|
const component = findComponent(ast.directives);
|
||||||
|
const nullNode = o.literal(null, o.INFERRED_TYPE);
|
||||||
const parameters: o.Expression[] = [o.literal(elementIndex)];
|
const parameters: o.Expression[] = [o.literal(elementIndex)];
|
||||||
|
|
||||||
|
// Add component type or element tag
|
||||||
if (component) {
|
if (component) {
|
||||||
parameters.push(this.typeReference(component.directive.type.reference));
|
parameters.push(this.typeReference(component.directive.type.reference));
|
||||||
|
componentIndex = this.allocateDataSlot();
|
||||||
} else {
|
} else {
|
||||||
parameters.push(o.literal(ast.name));
|
parameters.push(o.literal(ast.name));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add attributes array
|
||||||
const attributes: o.Expression[] = [];
|
const attributes: o.Expression[] = [];
|
||||||
for (let attr of ast.attrs) {
|
for (let attr of ast.attrs) {
|
||||||
attributes.push(o.literal(attr.name), o.literal(attr.value));
|
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) {
|
// Add directives array
|
||||||
parameters.push(this.constantPool.getConstLiteral(o.literalArr(attributes)));
|
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);
|
this.instruction(this._creationMode, ast.sourceSpan, R3.createElement, ...parameters);
|
||||||
|
|
||||||
const implicit = o.variable(this.contextParameter);
|
const implicit = o.variable(this.contextParameter);
|
||||||
@ -216,9 +345,9 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor {
|
|||||||
if (input.isAnimation) {
|
if (input.isAnimation) {
|
||||||
this.unsupported('animations');
|
this.unsupported('animations');
|
||||||
}
|
}
|
||||||
// TODO(chuckj): Builtins transform?
|
// TODO(chuckj): Built-in transform?
|
||||||
const convertedBinding = convertPropertyBinding(
|
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._bindingMode.push(...convertedBinding.stmts);
|
||||||
const parameters =
|
const parameters =
|
||||||
[o.literal(elementIndex), o.literal(input.name), convertedBinding.currValExpr];
|
[o.literal(elementIndex), o.literal(input.name), convertedBinding.currValExpr];
|
||||||
@ -234,19 +363,20 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Generate directives input bindings
|
// Generate directives input bindings
|
||||||
this._visitDirectives(ast.directives, implicit, elementIndex);
|
this._visitDirectives(ast.directives, implicit, elementIndex, directiveIndexMap);
|
||||||
|
|
||||||
// Traverse element child nodes
|
// Traverse element child nodes
|
||||||
templateVisitAll(this, ast.children);
|
templateVisitAll(this, ast.children);
|
||||||
|
|
||||||
|
|
||||||
// Finish element construction mode.
|
// Finish element construction mode.
|
||||||
this.instruction(this._creationMode, ast.endSourceSpan || ast.sourceSpan, R3.elementEnd);
|
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) {
|
for (let directive of directives) {
|
||||||
const directiveIndex = this.allocateDirective();
|
const directiveIndex = directiveIndexMap.get(directive.directive.type.reference);
|
||||||
|
|
||||||
// Creation mode
|
// Creation mode
|
||||||
// e.g. D(0, TodoComponentDef.n(), TodoComponentDef);
|
// 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
|
// node is referenced multiple times to know that it must generate the reference into a
|
||||||
// temporary.
|
// 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
|
// Bindings
|
||||||
for (const input of directive.inputs) {
|
for (const input of directive.inputs) {
|
||||||
const convertedBinding = convertPropertyBinding(
|
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._bindingMode.push(...convertedBinding.stmts);
|
||||||
this.instruction(
|
this.instruction(
|
||||||
this._bindingMode, directive.sourceSpan, R3.elementProperty,
|
this._bindingMode, directive.sourceSpan, R3.elementProperty,
|
||||||
o.literal(input.templateName), o.literal(nodeIndex), convertedBinding.currValExpr);
|
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);
|
// e.g. TodoComponentDef.r(0, 0);
|
||||||
this._refreshMode.push(
|
this._refreshMode.push(
|
||||||
this.definitionOf(directiveType, kind)
|
this.definitionOf(directiveType, kind)
|
||||||
@ -289,32 +407,35 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
visitEmbeddedTemplate(ast: EmbeddedTemplateAst) {
|
visitEmbeddedTemplate(ast: EmbeddedTemplateAst) {
|
||||||
const templateIndex = this.allocateNode();
|
const templateIndex = this.allocateDataSlot();
|
||||||
|
|
||||||
const templateName = `C${templateIndex}Template`;
|
const templateName = `C${templateIndex}Template`;
|
||||||
const templateContext = `ctx${this.level}`;
|
const templateContext = `ctx${this.level}`;
|
||||||
|
|
||||||
// TODO(chuckj): attrs?
|
const {directivesArray, directiveIndexMap} = this._computeDirectivesArray(ast.directives);
|
||||||
|
|
||||||
// e.g. C(1, C1Template)
|
// e.g. C(1, C1Template)
|
||||||
this.instruction(
|
this.instruction(
|
||||||
this._creationMode, ast.sourceSpan, R3.containerCreate, o.literal(templateIndex),
|
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(
|
this._visitDirectives(
|
||||||
ast.directives, o.variable(this.contextParameter),
|
ast.directives, o.variable(this.contextParameter), templateIndex, directiveIndexMap);
|
||||||
// TODO(chuckj): This should be the element index of the element that contained the template
|
|
||||||
templateIndex);
|
// e.g. cr();
|
||||||
|
this.instruction(this._refreshMode, ast.sourceSpan, R3.containerRefreshEnd);
|
||||||
|
|
||||||
// Create the template function
|
// Create the template function
|
||||||
const templateVisitor = new TemplateDefinitionBuilder(
|
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);
|
const templateFunctionExpr = templateVisitor.buildTemplateFunction(ast.children);
|
||||||
this._postfix.push(templateFunctionExpr.toDeclStmt(templateName, null));
|
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.
|
// These should be handled in the template or element directly.
|
||||||
@ -325,7 +446,7 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor {
|
|||||||
readonly visitAttr = invalid;
|
readonly visitAttr = invalid;
|
||||||
|
|
||||||
visitBoundText(ast: BoundTextAst) {
|
visitBoundText(ast: BoundTextAst) {
|
||||||
const nodeIndex = this.allocateNode();
|
const nodeIndex = this.allocateDataSlot();
|
||||||
|
|
||||||
// Creation mode
|
// Creation mode
|
||||||
this.instruction(this._creationMode, ast.sourceSpan, R3.text, o.literal(nodeIndex));
|
this.instruction(this._creationMode, ast.sourceSpan, R3.text, o.literal(nodeIndex));
|
||||||
@ -333,20 +454,21 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor {
|
|||||||
// Refresh mode
|
// Refresh mode
|
||||||
this.instruction(
|
this.instruction(
|
||||||
this._refreshMode, ast.sourceSpan, R3.textCreateBound, o.literal(nodeIndex),
|
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) {
|
visitText(ast: TextAst) {
|
||||||
// Text is defined in creation mode only.
|
// 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
|
// These should be handled in the template or element directly
|
||||||
readonly visitDirective = invalid;
|
readonly visitDirective = invalid;
|
||||||
readonly visitDirectiveProperty = invalid;
|
readonly visitDirectiveProperty = invalid;
|
||||||
|
|
||||||
private allocateDirective() { return this._dataIndex++; }
|
private allocateDataSlot() { return this._dataIndex++; }
|
||||||
private allocateNode() { return this._dataIndex++; }
|
|
||||||
private bindingContext() { return `${this._bindingContext++}`; }
|
private bindingContext() { return `${this._bindingContext++}`; }
|
||||||
|
|
||||||
private instruction(
|
private instruction(
|
||||||
@ -373,13 +495,13 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor {
|
|||||||
|
|
||||||
private convertPropertyBinding(implicit: o.Expression, value: AST): o.Expression {
|
private convertPropertyBinding(implicit: o.Expression, value: AST): o.Expression {
|
||||||
const convertedPropertyBinding = convertPropertyBinding(
|
const convertedPropertyBinding = convertPropertyBinding(
|
||||||
null, implicit, value, this.bindingContext(), BindingForm.TrySimple, interpolate);
|
this, implicit, value, this.bindingContext(), BindingForm.TrySimple, interpolate);
|
||||||
this._refreshMode.push(...convertedPropertyBinding.stmts);
|
this._refreshMode.push(...convertedPropertyBinding.stmts);
|
||||||
return convertedPropertyBinding.currValExpr;
|
return convertedPropertyBinding.currValExpr;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bind(implicit: o.Expression, value: AST, sourceSpan: ParseSourceSpan): o.Expression {
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -110,8 +110,8 @@ export class BoundEventAst implements TemplateAst {
|
|||||||
*/
|
*/
|
||||||
export class ReferenceAst implements TemplateAst {
|
export class ReferenceAst implements TemplateAst {
|
||||||
constructor(
|
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 {
|
visit(visitor: TemplateAstVisitor, context: any): any {
|
||||||
return visitor.visitReference(this, context);
|
return visitor.visitReference(this, context);
|
||||||
}
|
}
|
||||||
|
@ -574,7 +574,7 @@ class TemplateParseVisitor implements html.Visitor {
|
|||||||
if ((elOrDirRef.value.length === 0 && directive.isComponent) ||
|
if ((elOrDirRef.value.length === 0 && directive.isComponent) ||
|
||||||
(elOrDirRef.isReferenceToDirective(directive))) {
|
(elOrDirRef.isReferenceToDirective(directive))) {
|
||||||
targetReferences.push(new ReferenceAst(
|
targetReferences.push(new ReferenceAst(
|
||||||
elOrDirRef.name, createTokenForReference(directive.type.reference),
|
elOrDirRef.name, createTokenForReference(directive.type.reference), elOrDirRef.value,
|
||||||
elOrDirRef.sourceSpan));
|
elOrDirRef.sourceSpan));
|
||||||
matchedReferences.add(elOrDirRef.name);
|
matchedReferences.add(elOrDirRef.name);
|
||||||
}
|
}
|
||||||
@ -598,7 +598,8 @@ class TemplateParseVisitor implements html.Visitor {
|
|||||||
if (isTemplateElement) {
|
if (isTemplateElement) {
|
||||||
refToken = createTokenForExternalReference(this.reflector, Identifiers.TemplateRef);
|
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;
|
return directiveAsts;
|
||||||
|
@ -6,13 +6,13 @@
|
|||||||
* found in the LICENSE file at https://angular.io/license
|
* 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 {ViewEncapsulation} from '@angular/core';
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
import {ConstantPool} from '../../src/constant_pool';
|
import {ConstantPool} from '../../src/constant_pool';
|
||||||
import * as o from '../../src/output/output_ast';
|
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 {OutputContext} from '../../src/util';
|
||||||
import {MockAotCompilerHost, MockCompilerHost, MockData, MockDirectory, arrayToMockDir, settings, setup, toMockFileArray} from '../aot/test_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);
|
const result = compile(files, angularFiles);
|
||||||
expect(result.source).toContain('@angular/core');
|
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(
|
function compile(
|
||||||
data: MockDirectory, angularFiles: MockData, options: AotCompilerOptions = {},
|
data: MockDirectory, angularFiles: MockData, options: AotCompilerOptions = {},
|
||||||
errorCollector: (error: any, fileName?: string) => void = error => { throw error; }) {
|
errorCollector: (error: any, fileName?: string) => void = error => { throw error; }) {
|
||||||
@ -156,7 +475,7 @@ function compile(
|
|||||||
|
|
||||||
const directives = Array.from(analyzedModules.ngModuleByPipeOrDirective.keys());
|
const directives = Array.from(analyzedModules.ngModuleByPipeOrDirective.keys());
|
||||||
|
|
||||||
const fakeOuputContext: OutputContext = {
|
const fakeOutputContext: OutputContext = {
|
||||||
genFilePath: 'fakeFactory.ts',
|
genFilePath: 'fakeFactory.ts',
|
||||||
statements: [],
|
statements: [],
|
||||||
importExpr(symbol: StaticSymbol, typeParams: o.Type[]) {
|
importExpr(symbol: StaticSymbol, typeParams: o.Type[]) {
|
||||||
@ -182,7 +501,10 @@ function compile(
|
|||||||
|
|
||||||
// Compile the directives.
|
// Compile the directives.
|
||||||
for (const directive of 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)) {
|
if (resolver.isDirective(directive)) {
|
||||||
const metadata = resolver.getDirectiveMetadata(directive);
|
const metadata = resolver.getDirectiveMetadata(directive);
|
||||||
if (metadata.isComponent) {
|
if (metadata.isComponent) {
|
||||||
@ -196,17 +518,24 @@ function compile(
|
|||||||
const parsedTemplate = templateParser.parse(
|
const parsedTemplate = templateParser.parse(
|
||||||
metadata, htmlAst, directives, pipes, module.schemas, fakeUrl, false);
|
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 emitter = new TypeScriptEmitter();
|
||||||
|
|
||||||
const result = emitter.emitStatementsAndContext(
|
const moduleName = compilerHost.fileNameToModuleName(
|
||||||
fakeOuputContext.genFilePath, fakeOuputContext.statements, '', false);
|
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};
|
||||||
}
|
}
|
@ -407,7 +407,7 @@ class ArrayConsole implements Console {
|
|||||||
expectVisitedNode(
|
expectVisitedNode(
|
||||||
new class extends
|
new class extends
|
||||||
NullVisitor{visitReference(ast: ReferenceAst, context: any): any{return ast;}},
|
NullVisitor{visitReference(ast: ReferenceAst, context: any): any{return ast;}},
|
||||||
new ReferenceAst('foo', null !, null !));
|
new ReferenceAst('foo', null !, null !, null !));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should visit VariableAst', () => {
|
it('should visit VariableAst', () => {
|
||||||
@ -474,7 +474,7 @@ class ArrayConsole implements Console {
|
|||||||
new NgContentAst(0, 0, null !),
|
new NgContentAst(0, 0, null !),
|
||||||
new EmbeddedTemplateAst([], [], [], [], [], [], false, [], [], 0, null !),
|
new EmbeddedTemplateAst([], [], [], [], [], [], false, [], [], 0, null !),
|
||||||
new ElementAst('foo', [], [], [], [], [], [], false, [], [], 0, null !, 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 BoundEventAst('foo', 'bar', 'goo', null !, null !),
|
||||||
new BoundElementPropertyAst('foo', null !, null !, null !, 'bar', null !),
|
new BoundElementPropertyAst('foo', null !, null !, null !, 'bar', null !),
|
||||||
new AttrAst('foo', 'bar', null !), new BoundTextAst(null !, 0, null !),
|
new AttrAst('foo', 'bar', null !), new BoundTextAst(null !, 0, null !),
|
||||||
|
Loading…
x
Reference in New Issue
Block a user