2017-11-20 10:21:17 -08:00
|
|
|
/**
|
|
|
|
* @license
|
|
|
|
* Copyright Google Inc. All Rights Reserved.
|
|
|
|
*
|
|
|
|
* Use of this source code is governed by an MIT-style license that can be
|
|
|
|
* found in the LICENSE file at https://angular.io/license
|
|
|
|
*/
|
|
|
|
|
2018-04-24 11:34:11 -07:00
|
|
|
import {flatten, sanitizeIdentifier} from '../../compile_metadata';
|
|
|
|
import {CompileReflector} from '../../compile_reflector';
|
|
|
|
import {BindingForm, BuiltinFunctionCall, LocalResolver, convertActionBinding, convertPropertyBinding} from '../../compiler_util/expression_converter';
|
|
|
|
import {ConstantPool} from '../../constant_pool';
|
|
|
|
import * as core from '../../core';
|
2018-05-15 21:07:59 +02:00
|
|
|
import {AST, AstMemoryEfficientTransformer, BindingPipe, BindingType, FunctionCall, ImplicitReceiver, Interpolation, LiteralArray, LiteralMap, LiteralPrimitive, PropertyRead} from '../../expression_parser/ast';
|
2018-05-09 08:35:25 -07:00
|
|
|
import {Lexer} from '../../expression_parser/lexer';
|
|
|
|
import {Parser} from '../../expression_parser/parser';
|
|
|
|
import * as html from '../../ml_parser/ast';
|
|
|
|
import {HtmlParser} from '../../ml_parser/html_parser';
|
2018-05-21 08:15:19 -07:00
|
|
|
import {WhitespaceVisitor} from '../../ml_parser/html_whitespaces';
|
2018-05-09 08:35:25 -07:00
|
|
|
import {DEFAULT_INTERPOLATION_CONFIG} from '../../ml_parser/interpolation_config';
|
2018-06-08 09:00:01 -07:00
|
|
|
import {splitNsName} from '../../ml_parser/tags';
|
2018-04-24 11:34:11 -07:00
|
|
|
import * as o from '../../output/output_ast';
|
2018-05-09 08:35:25 -07:00
|
|
|
import {ParseError, ParseSourceSpan} from '../../parse_util';
|
|
|
|
import {DomElementSchemaRegistry} from '../../schema/dom_element_schema_registry';
|
2018-04-24 11:34:11 -07:00
|
|
|
import {CssSelector, SelectorMatcher} from '../../selector';
|
2018-05-09 08:35:25 -07:00
|
|
|
import {BindingParser} from '../../template_parser/binding_parser';
|
2018-04-24 11:34:11 -07:00
|
|
|
import {OutputContext, error} from '../../util';
|
|
|
|
import * as t from '../r3_ast';
|
|
|
|
import {Identifiers as R3} from '../r3_identifiers';
|
2018-05-09 08:35:25 -07:00
|
|
|
import {htmlAstToRender3Ast} from '../r3_template_transform';
|
2018-04-24 11:34:11 -07:00
|
|
|
|
|
|
|
import {R3QueryMetadata} from './api';
|
2018-06-19 12:45:00 -07:00
|
|
|
import {parseStyle} from './styling';
|
2018-04-24 11:34:11 -07:00
|
|
|
import {CONTEXT_NAME, I18N_ATTR, I18N_ATTR_PREFIX, ID_SEPARATOR, IMPLICIT_REFERENCE, MEANING_SEPARATOR, REFERENCE_PREFIX, RENDER_FLAGS, TEMPORARY_NAME, asLiteral, getQueryPredicate, invalid, mapToExpression, noop, temporaryAllocator, trimTrailingNulls, unsupported} from './util';
|
2017-11-20 10:21:17 -08:00
|
|
|
|
2018-06-20 16:00:48 -07:00
|
|
|
function mapBindingToInstruction(type: BindingType): o.ExternalReference|undefined {
|
|
|
|
switch (type) {
|
|
|
|
case BindingType.Property:
|
|
|
|
return R3.elementProperty;
|
|
|
|
case BindingType.Attribute:
|
|
|
|
return R3.elementAttribute;
|
|
|
|
case BindingType.Class:
|
2018-07-11 09:56:47 -07:00
|
|
|
return R3.elementClassProp;
|
2018-06-20 16:00:48 -07:00
|
|
|
default:
|
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
}
|
2017-11-20 10:21:17 -08:00
|
|
|
|
2018-07-24 11:56:35 +02:00
|
|
|
// if (rf & flags) { .. }
|
|
|
|
export function renderFlagCheckIfStmt(
|
|
|
|
flags: core.RenderFlags, statements: o.Statement[]): o.IfStmt {
|
|
|
|
return o.ifStmt(o.variable(RENDER_FLAGS).bitwiseAnd(o.literal(flags), null, false), statements);
|
|
|
|
}
|
|
|
|
|
2018-04-24 11:34:11 -07:00
|
|
|
export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver {
|
2017-11-20 10:21:17 -08:00
|
|
|
private _dataIndex = 0;
|
|
|
|
private _bindingContext = 0;
|
2018-04-18 16:23:49 -07:00
|
|
|
private _prefixCode: o.Statement[] = [];
|
|
|
|
private _creationCode: o.Statement[] = [];
|
|
|
|
private _variableCode: o.Statement[] = [];
|
2018-07-18 01:59:49 +00:00
|
|
|
private _bindingCode: (() => o.Statement)[] = [];
|
|
|
|
private _nestedTemplates: (() => void)[] = [];
|
2018-02-14 17:12:05 -08:00
|
|
|
private _valueConverter: ValueConverter;
|
2018-04-18 16:23:49 -07:00
|
|
|
private _unsupported = unsupported;
|
|
|
|
private _bindingScope: BindingScope;
|
2017-11-20 10:21:17 -08:00
|
|
|
|
2018-02-14 10:54:00 -08:00
|
|
|
// Whether we are inside a translatable element (`<p i18n>... somewhere here ... </p>)
|
|
|
|
private _inI18nSection: boolean = false;
|
|
|
|
private _i18nSectionIndex = -1;
|
|
|
|
// Maps of placeholder to node indexes for each of the i18n section
|
|
|
|
private _phToNodeIdxes: {[phName: string]: number[]}[] = [{}];
|
|
|
|
|
2018-05-21 15:59:25 -07:00
|
|
|
// Number of slots to reserve for pureFunctions
|
|
|
|
private _pureFunctionSlots = 0;
|
|
|
|
|
2017-11-20 10:21:17 -08:00
|
|
|
constructor(
|
2018-04-24 11:34:11 -07:00
|
|
|
private constantPool: ConstantPool, private contextParameter: string,
|
2018-04-18 16:23:49 -07:00
|
|
|
parentBindingScope: BindingScope, private level = 0, private contextName: string|null,
|
2018-04-24 11:34:11 -07:00
|
|
|
private templateName: string|null, private viewQueries: R3QueryMetadata[],
|
|
|
|
private directiveMatcher: SelectorMatcher|null, private directives: Set<o.Expression>,
|
2018-06-08 09:00:01 -07:00
|
|
|
private pipeTypeByName: Map<string, o.Expression>, private pipes: Set<o.Expression>,
|
|
|
|
private _namespace: o.ExternalReference) {
|
2018-07-24 11:56:35 +02:00
|
|
|
// view queries can take up space in data and allocation happens earlier (in the "viewQuery"
|
|
|
|
// function)
|
|
|
|
this._dataIndex = viewQueries.length;
|
2018-04-18 16:23:49 -07:00
|
|
|
this._bindingScope =
|
2018-07-18 01:59:49 +00:00
|
|
|
parentBindingScope.nestedScope(level, (lhsVar: o.ReadVarExpr, rhsExpr: o.Expression) => {
|
|
|
|
this._variableCode.push(
|
|
|
|
lhsVar.set(rhsExpr).toDeclStmt(o.INFERRED_TYPE, [o.StmtModifier.Final]));
|
2018-04-05 11:38:06 -07:00
|
|
|
});
|
2018-02-14 17:12:05 -08:00
|
|
|
this._valueConverter = new ValueConverter(
|
2018-04-24 11:34:11 -07:00
|
|
|
constantPool, () => this.allocateDataSlot(),
|
2018-05-21 15:59:25 -07:00
|
|
|
(numSlots: number): number => this._pureFunctionSlots += numSlots,
|
2018-04-24 11:34:11 -07:00
|
|
|
(name, localName, slot, value: o.ReadVarExpr) => {
|
2018-04-18 16:23:49 -07:00
|
|
|
const pipeType = pipeTypeByName.get(name);
|
|
|
|
if (pipeType) {
|
|
|
|
this.pipes.add(pipeType);
|
|
|
|
}
|
|
|
|
this._bindingScope.set(localName, value);
|
|
|
|
this._creationCode.push(
|
2018-03-30 16:07:37 -07:00
|
|
|
o.importExpr(R3.pipe).callFn([o.literal(slot), o.literal(name)]).toStmt());
|
2018-02-05 17:31:12 -08:00
|
|
|
});
|
|
|
|
}
|
2017-11-20 10:21:17 -08:00
|
|
|
|
2018-04-18 16:23:49 -07:00
|
|
|
buildTemplateFunction(
|
|
|
|
nodes: t.Node[], variables: t.Variable[], hasNgContent: boolean = false,
|
|
|
|
ngContentSelectors: string[] = []): o.FunctionExpr {
|
2018-06-08 09:00:01 -07:00
|
|
|
if (this._namespace !== R3.namespaceHTML) {
|
2018-07-18 01:59:49 +00:00
|
|
|
this.creationInstruction(null, this._namespace);
|
2018-06-08 09:00:01 -07:00
|
|
|
}
|
|
|
|
|
2018-01-24 11:19:39 -08:00
|
|
|
// Create variable bindings
|
|
|
|
for (const variable of variables) {
|
|
|
|
const variableName = variable.name;
|
|
|
|
const expression =
|
|
|
|
o.variable(this.contextParameter).prop(variable.value || IMPLICIT_REFERENCE);
|
2018-04-18 16:23:49 -07:00
|
|
|
const scopedName = this._bindingScope.freshReferenceName();
|
2018-01-24 11:19:39 -08:00
|
|
|
// Add the reference to the local scope.
|
2018-04-18 16:23:49 -07:00
|
|
|
this._bindingScope.set(variableName, o.variable(variableName + scopedName), expression);
|
2018-01-24 11:19:39 -08:00
|
|
|
}
|
|
|
|
|
2018-04-18 16:23:49 -07:00
|
|
|
// Output a `ProjectionDef` instruction when some `<ng-content>` are present
|
|
|
|
if (hasNgContent) {
|
2018-07-03 20:04:36 -07:00
|
|
|
const parameters: o.Expression[] = [];
|
2018-04-18 16:23:49 -07:00
|
|
|
|
|
|
|
// Only selectors with a non-default value are generated
|
|
|
|
if (ngContentSelectors.length > 1) {
|
2018-04-20 08:52:07 -07:00
|
|
|
const r3Selectors = ngContentSelectors.map(s => core.parseSelectorToR3Selector(s));
|
2018-04-18 16:23:49 -07:00
|
|
|
// `projectionDef` needs both the parsed and raw value of the selectors
|
2018-04-24 11:34:11 -07:00
|
|
|
const parsed = this.constantPool.getConstLiteral(asLiteral(r3Selectors), true);
|
|
|
|
const unParsed = this.constantPool.getConstLiteral(asLiteral(ngContentSelectors), true);
|
2018-04-18 16:23:49 -07:00
|
|
|
parameters.push(parsed, unParsed);
|
2018-01-26 17:12:39 -08:00
|
|
|
}
|
2018-04-18 16:23:49 -07:00
|
|
|
|
2018-07-18 01:59:49 +00:00
|
|
|
this.creationInstruction(null, R3.projectionDef, ...parameters);
|
2018-01-26 17:12:39 -08:00
|
|
|
}
|
|
|
|
|
2018-04-18 16:23:49 -07:00
|
|
|
t.visitAll(this, nodes);
|
2017-11-20 10:21:17 -08:00
|
|
|
|
2018-04-18 16:23:49 -07:00
|
|
|
const creationCode = this._creationCode.length > 0 ?
|
2018-07-24 11:56:35 +02:00
|
|
|
[renderFlagCheckIfStmt(core.RenderFlags.Create, this._creationCode)] :
|
2018-04-10 20:57:20 -07:00
|
|
|
[];
|
|
|
|
|
2018-04-18 16:23:49 -07:00
|
|
|
const updateCode = this._bindingCode.length > 0 ?
|
2018-07-18 01:59:49 +00:00
|
|
|
[renderFlagCheckIfStmt(core.RenderFlags.Update, this._variableCode.concat(
|
|
|
|
this._variableCode.concat(this._bindingCode.map((fn: () => o.Statement) => fn()))))] :
|
2018-01-29 10:36:56 -08:00
|
|
|
[];
|
|
|
|
|
2018-07-18 01:59:49 +00:00
|
|
|
if (this._pureFunctionSlots > 0) {
|
|
|
|
this.creationInstruction(null, R3.reserveSlots, o.literal(this._pureFunctionSlots));
|
|
|
|
}
|
|
|
|
|
2018-02-14 10:54:00 -08:00
|
|
|
// Generate maps of placeholder name to node indexes
|
|
|
|
// TODO(vicb): This is a WIP, not fully supported yet
|
|
|
|
for (const phToNodeIdx of this._phToNodeIdxes) {
|
|
|
|
if (Object.keys(phToNodeIdx).length > 0) {
|
2018-04-18 16:23:49 -07:00
|
|
|
const scopedName = this._bindingScope.freshReferenceName();
|
2018-02-14 10:54:00 -08:00
|
|
|
const phMap = o.variable(scopedName)
|
|
|
|
.set(mapToExpression(phToNodeIdx, true))
|
|
|
|
.toDeclStmt(o.INFERRED_TYPE, [o.StmtModifier.Final]);
|
|
|
|
|
2018-04-18 16:23:49 -07:00
|
|
|
this._prefixCode.push(phMap);
|
2018-02-14 10:54:00 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-07-18 01:59:49 +00:00
|
|
|
this._nestedTemplates.forEach(buildTemplateFn => buildTemplateFn());
|
|
|
|
|
2017-11-20 10:21:17 -08:00
|
|
|
return o.fn(
|
2018-07-18 01:59:49 +00:00
|
|
|
// i.e. (rf: RenderFlags, ctx0: any, ctx: any)
|
|
|
|
[
|
|
|
|
new o.FnParam(RENDER_FLAGS, o.NUMBER_TYPE), ...this.getNestedContexts(),
|
|
|
|
new o.FnParam(CONTEXT_NAME, null)
|
|
|
|
],
|
2017-11-20 10:21:17 -08:00
|
|
|
[
|
2018-04-10 20:57:20 -07:00
|
|
|
// Temporary variable declarations for query refresh (i.e. let _t: any;)
|
2018-04-18 16:23:49 -07:00
|
|
|
...this._prefixCode,
|
2018-04-10 20:57:20 -07:00
|
|
|
// Creating mode (i.e. if (rf & RenderFlags.Create) { ... })
|
2018-04-18 16:23:49 -07:00
|
|
|
...creationCode,
|
2018-04-10 20:57:20 -07:00
|
|
|
// Binding and refresh mode (i.e. if (rf & RenderFlags.Update) {...})
|
2018-04-18 16:23:49 -07:00
|
|
|
...updateCode,
|
2017-11-20 10:21:17 -08:00
|
|
|
],
|
2018-01-22 15:35:18 -08:00
|
|
|
o.INFERRED_TYPE, null, this.templateName);
|
2017-11-20 10:21:17 -08:00
|
|
|
}
|
|
|
|
|
2018-02-05 17:31:12 -08:00
|
|
|
// LocalResolver
|
2018-04-18 16:23:49 -07:00
|
|
|
getLocal(name: string): o.Expression|null { return this._bindingScope.get(name); }
|
2018-01-11 15:37:56 -08:00
|
|
|
|
2018-04-18 16:23:49 -07:00
|
|
|
visitContent(ngContent: t.Content) {
|
|
|
|
const slot = this.allocateDataSlot();
|
|
|
|
const selectorIndex = ngContent.selectorIndex;
|
2018-07-03 20:04:36 -07:00
|
|
|
const parameters: o.Expression[] = [o.literal(slot)];
|
2018-04-18 16:23:49 -07:00
|
|
|
|
|
|
|
const attributeAsList: string[] = [];
|
|
|
|
|
|
|
|
ngContent.attributes.forEach((attribute) => {
|
|
|
|
const name = attribute.name;
|
|
|
|
if (name !== 'select') {
|
|
|
|
attributeAsList.push(name, attribute.value);
|
2018-04-16 11:49:11 -07:00
|
|
|
}
|
2018-04-18 16:23:49 -07:00
|
|
|
});
|
|
|
|
|
|
|
|
if (attributeAsList.length > 0) {
|
|
|
|
parameters.push(o.literal(selectorIndex), asLiteral(attributeAsList));
|
|
|
|
} else if (selectorIndex !== 0) {
|
|
|
|
parameters.push(o.literal(selectorIndex));
|
2018-01-26 17:12:39 -08:00
|
|
|
}
|
2018-04-18 16:23:49 -07:00
|
|
|
|
2018-07-18 01:59:49 +00:00
|
|
|
this.creationInstruction(ngContent.sourceSpan, R3.projection, ...parameters);
|
2018-01-26 17:12:39 -08:00
|
|
|
}
|
2017-11-20 10:21:17 -08:00
|
|
|
|
2018-06-08 09:00:01 -07:00
|
|
|
|
|
|
|
getNamespaceInstruction(namespaceKey: string|null) {
|
|
|
|
switch (namespaceKey) {
|
|
|
|
case 'math':
|
|
|
|
return R3.namespaceMathML;
|
|
|
|
case 'svg':
|
|
|
|
return R3.namespaceSVG;
|
|
|
|
default:
|
|
|
|
return R3.namespaceHTML;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
addNamespaceInstruction(nsInstruction: o.ExternalReference, element: t.Element) {
|
|
|
|
this._namespace = nsInstruction;
|
2018-07-18 01:59:49 +00:00
|
|
|
this.creationInstruction(element.sourceSpan, nsInstruction);
|
|
|
|
}
|
|
|
|
|
|
|
|
getNestedContexts(): o.FnParam[] {
|
|
|
|
const nestedContexts = [];
|
|
|
|
let nestingLevel = this.level - 1;
|
|
|
|
|
|
|
|
while (nestingLevel >= 0) {
|
|
|
|
nestedContexts.push(new o.FnParam(`ctx${nestingLevel}`, null));
|
|
|
|
nestingLevel--;
|
|
|
|
}
|
|
|
|
|
|
|
|
return nestedContexts;
|
2018-06-08 09:00:01 -07:00
|
|
|
}
|
|
|
|
|
2018-04-18 16:23:49 -07:00
|
|
|
visitElement(element: t.Element) {
|
2018-01-11 15:37:56 -08:00
|
|
|
const elementIndex = this.allocateDataSlot();
|
2018-02-14 10:54:00 -08:00
|
|
|
const wasInI18nSection = this._inI18nSection;
|
|
|
|
|
|
|
|
const outputAttrs: {[name: string]: string} = {};
|
|
|
|
const attrI18nMetas: {[name: string]: string} = {};
|
2018-06-06 13:38:00 -07:00
|
|
|
let i18nMeta: string = '';
|
2018-02-14 10:54:00 -08:00
|
|
|
|
2018-06-08 09:00:01 -07:00
|
|
|
const [namespaceKey, elementName] = splitNsName(element.name);
|
|
|
|
|
2018-02-14 10:54:00 -08:00
|
|
|
// Elements inside i18n sections are replaced with placeholders
|
|
|
|
// TODO(vicb): nested elements are a WIP in this phase
|
|
|
|
if (this._inI18nSection) {
|
|
|
|
const phName = element.name.toLowerCase();
|
|
|
|
if (!this._phToNodeIdxes[this._i18nSectionIndex][phName]) {
|
|
|
|
this._phToNodeIdxes[this._i18nSectionIndex][phName] = [];
|
|
|
|
}
|
|
|
|
this._phToNodeIdxes[this._i18nSectionIndex][phName].push(elementIndex);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Handle i18n attributes
|
2018-04-18 16:23:49 -07:00
|
|
|
for (const attr of element.attributes) {
|
2018-02-14 10:54:00 -08:00
|
|
|
const name = attr.name;
|
|
|
|
const value = attr.value;
|
|
|
|
if (name === I18N_ATTR) {
|
|
|
|
if (this._inI18nSection) {
|
|
|
|
throw new Error(
|
|
|
|
`Could not mark an element as translatable inside of a translatable section`);
|
|
|
|
}
|
|
|
|
this._inI18nSection = true;
|
|
|
|
this._i18nSectionIndex++;
|
|
|
|
this._phToNodeIdxes[this._i18nSectionIndex] = {};
|
|
|
|
i18nMeta = value;
|
|
|
|
} else if (name.startsWith(I18N_ATTR_PREFIX)) {
|
|
|
|
attrI18nMetas[name.slice(I18N_ATTR_PREFIX.length)] = value;
|
|
|
|
} else {
|
|
|
|
outputAttrs[name] = value;
|
|
|
|
}
|
|
|
|
}
|
2017-11-20 10:21:17 -08:00
|
|
|
|
2018-04-18 16:23:49 -07:00
|
|
|
// Match directives on non i18n attributes
|
|
|
|
if (this.directiveMatcher) {
|
|
|
|
const selector = createCssSelector(element.name, outputAttrs);
|
|
|
|
this.directiveMatcher.match(
|
|
|
|
selector, (sel: CssSelector, staticType: any) => { this.directives.add(staticType); });
|
|
|
|
}
|
|
|
|
|
2017-11-20 10:21:17 -08:00
|
|
|
// Element creation mode
|
2018-04-13 15:12:11 -07:00
|
|
|
const parameters: o.Expression[] = [
|
|
|
|
o.literal(elementIndex),
|
2018-06-08 09:00:01 -07:00
|
|
|
o.literal(elementName),
|
2018-04-13 15:12:11 -07:00
|
|
|
];
|
2018-01-11 15:37:56 -08:00
|
|
|
|
2018-02-14 10:54:00 -08:00
|
|
|
// Add the attributes
|
|
|
|
const i18nMessages: o.Statement[] = [];
|
2017-11-20 10:21:17 -08:00
|
|
|
const attributes: o.Expression[] = [];
|
2018-06-19 12:45:00 -07:00
|
|
|
const initialStyleDeclarations: o.Expression[] = [];
|
2018-07-11 09:56:47 -07:00
|
|
|
const initialClassDeclarations: o.Expression[] = [];
|
2018-02-14 10:54:00 -08:00
|
|
|
|
2018-06-19 12:45:00 -07:00
|
|
|
const styleInputs: t.BoundAttribute[] = [];
|
2018-07-11 09:56:47 -07:00
|
|
|
const classInputs: t.BoundAttribute[] = [];
|
2018-06-19 12:45:00 -07:00
|
|
|
const allOtherInputs: t.BoundAttribute[] = [];
|
|
|
|
|
|
|
|
element.inputs.forEach((input: t.BoundAttribute) => {
|
2018-07-11 09:56:47 -07:00
|
|
|
switch (input.type) {
|
|
|
|
// [attr.style] or [attr.class] should not be treated as styling-based
|
|
|
|
// bindings since they are intended to be written directly to the attr
|
|
|
|
// and therefore will skip all style/class resolution that is present
|
|
|
|
// with style="", [style]="" and [style.prop]="", class="",
|
|
|
|
// [class.prop]="". [class]="" assignments
|
|
|
|
case BindingType.Property:
|
|
|
|
if (input.name == 'style') {
|
|
|
|
// this should always go first in the compilation (for [style])
|
|
|
|
styleInputs.splice(0, 0, input);
|
|
|
|
} else if (isClassBinding(input)) {
|
|
|
|
// this should always go first in the compilation (for [class])
|
|
|
|
classInputs.splice(0, 0, input);
|
|
|
|
} else {
|
|
|
|
allOtherInputs.push(input);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case BindingType.Style:
|
|
|
|
styleInputs.push(input);
|
|
|
|
break;
|
|
|
|
case BindingType.Class:
|
|
|
|
classInputs.push(input);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
allOtherInputs.push(input);
|
|
|
|
break;
|
2018-06-19 12:45:00 -07:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
let currStyleIndex = 0;
|
2018-07-11 09:56:47 -07:00
|
|
|
let currClassIndex = 0;
|
2018-06-19 12:45:00 -07:00
|
|
|
let staticStylesMap: {[key: string]: any}|null = null;
|
2018-07-11 09:56:47 -07:00
|
|
|
let staticClassesMap: {[key: string]: boolean}|null = null;
|
2018-06-19 12:45:00 -07:00
|
|
|
const stylesIndexMap: {[key: string]: number} = {};
|
2018-07-11 09:56:47 -07:00
|
|
|
const classesIndexMap: {[key: string]: number} = {};
|
2018-02-14 10:54:00 -08:00
|
|
|
Object.getOwnPropertyNames(outputAttrs).forEach(name => {
|
|
|
|
const value = outputAttrs[name];
|
2018-06-19 12:45:00 -07:00
|
|
|
if (name == 'style') {
|
|
|
|
staticStylesMap = parseStyle(value);
|
|
|
|
Object.keys(staticStylesMap).forEach(prop => { stylesIndexMap[prop] = currStyleIndex++; });
|
2018-07-11 09:56:47 -07:00
|
|
|
} else if (name == 'class') {
|
|
|
|
staticClassesMap = {};
|
|
|
|
value.split(/\s+/g).forEach(className => {
|
|
|
|
classesIndexMap[className] = currClassIndex++;
|
|
|
|
staticClassesMap ![className] = true;
|
|
|
|
});
|
2018-02-14 10:54:00 -08:00
|
|
|
} else {
|
2018-06-19 12:45:00 -07:00
|
|
|
attributes.push(o.literal(name));
|
|
|
|
if (attrI18nMetas.hasOwnProperty(name)) {
|
|
|
|
const meta = parseI18nMeta(attrI18nMetas[name]);
|
|
|
|
const variable = this.constantPool.getTranslation(value, meta);
|
|
|
|
attributes.push(variable);
|
|
|
|
} else {
|
|
|
|
attributes.push(o.literal(value));
|
|
|
|
}
|
2018-02-14 10:54:00 -08:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2018-07-11 10:58:18 -07:00
|
|
|
let hasMapBasedStyling = false;
|
2018-06-19 12:45:00 -07:00
|
|
|
for (let i = 0; i < styleInputs.length; i++) {
|
|
|
|
const input = styleInputs[i];
|
|
|
|
const isMapBasedStyleBinding = i === 0 && input.name === 'style';
|
2018-07-11 10:58:18 -07:00
|
|
|
if (isMapBasedStyleBinding) {
|
|
|
|
hasMapBasedStyling = true;
|
|
|
|
} else if (!stylesIndexMap.hasOwnProperty(input.name)) {
|
2018-06-19 12:45:00 -07:00
|
|
|
stylesIndexMap[input.name] = currStyleIndex++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-07-11 09:56:47 -07:00
|
|
|
for (let i = 0; i < classInputs.length; i++) {
|
|
|
|
const input = classInputs[i];
|
|
|
|
const isMapBasedClassBinding = i === 0 && isClassBinding(input);
|
|
|
|
if (!isMapBasedClassBinding && !stylesIndexMap.hasOwnProperty(input.name)) {
|
|
|
|
classesIndexMap[input.name] = currClassIndex++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-07-11 10:58:18 -07:00
|
|
|
// in the event that a [style] binding is used then sanitization will
|
|
|
|
// always be imported because it is not possible to know ahead of time
|
|
|
|
// whether style bindings will use or not use any sanitizable properties
|
|
|
|
// that isStyleSanitizable() will detect
|
|
|
|
let useDefaultStyleSanitizer = hasMapBasedStyling;
|
|
|
|
|
2018-06-19 12:45:00 -07:00
|
|
|
// this will build the instructions so that they fall into the following syntax
|
|
|
|
// => [prop1, prop2, prop3, 0, prop1, value1, prop2, value2]
|
|
|
|
Object.keys(stylesIndexMap).forEach(prop => {
|
2018-07-11 10:58:18 -07:00
|
|
|
useDefaultStyleSanitizer = useDefaultStyleSanitizer || isStyleSanitizable(prop);
|
2018-06-19 12:45:00 -07:00
|
|
|
initialStyleDeclarations.push(o.literal(prop));
|
|
|
|
});
|
|
|
|
|
|
|
|
if (staticStylesMap) {
|
2018-07-11 09:56:47 -07:00
|
|
|
initialStyleDeclarations.push(o.literal(core.InitialStylingFlags.VALUES_MODE));
|
2018-06-19 12:45:00 -07:00
|
|
|
|
|
|
|
Object.keys(staticStylesMap).forEach(prop => {
|
|
|
|
initialStyleDeclarations.push(o.literal(prop));
|
|
|
|
const value = staticStylesMap ![prop];
|
|
|
|
initialStyleDeclarations.push(o.literal(value));
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2018-07-11 09:56:47 -07:00
|
|
|
Object.keys(classesIndexMap).forEach(prop => {
|
|
|
|
initialClassDeclarations.push(o.literal(prop));
|
|
|
|
});
|
|
|
|
|
|
|
|
if (staticClassesMap) {
|
|
|
|
initialClassDeclarations.push(o.literal(core.InitialStylingFlags.VALUES_MODE));
|
|
|
|
|
|
|
|
Object.keys(staticClassesMap).forEach(className => {
|
|
|
|
initialClassDeclarations.push(o.literal(className));
|
|
|
|
initialClassDeclarations.push(o.literal(true));
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
const hasStylingInstructions = initialStyleDeclarations.length || styleInputs.length ||
|
|
|
|
initialClassDeclarations.length || classInputs.length;
|
|
|
|
|
2018-05-10 15:58:27 -07:00
|
|
|
const attrArg: o.Expression = attributes.length > 0 ?
|
|
|
|
this.constantPool.getConstLiteral(o.literalArr(attributes), true) :
|
|
|
|
o.TYPED_NULL_EXPR;
|
2018-02-14 10:54:00 -08:00
|
|
|
parameters.push(attrArg);
|
2018-01-11 15:37:56 -08:00
|
|
|
|
2018-02-14 10:54:00 -08:00
|
|
|
if (element.references && element.references.length > 0) {
|
2018-04-18 16:23:49 -07:00
|
|
|
const references = flatten(element.references.map(reference => {
|
|
|
|
const slot = this.allocateDataSlot();
|
|
|
|
// Generate the update temporary.
|
|
|
|
const variableName = this._bindingScope.freshReferenceName();
|
2018-07-18 01:59:49 +00:00
|
|
|
// When the ref's binding is processed, we'll either generate a load() or a reference()
|
|
|
|
// instruction depending on the nesting level of the binding relative to the reference def.
|
|
|
|
const refLevel = this.level;
|
|
|
|
this._bindingScope.set(
|
|
|
|
reference.name, o.variable(variableName), undefined, (bindingLevel: number) => {
|
|
|
|
return bindingLevel === refLevel ?
|
|
|
|
o.importExpr(R3.load).callFn([o.literal(slot)]) :
|
|
|
|
o.importExpr(R3.reference).callFn([
|
|
|
|
o.literal(bindingLevel - refLevel), o.literal(slot)
|
|
|
|
]);
|
|
|
|
});
|
2018-04-18 16:23:49 -07:00
|
|
|
return [reference.name, reference.value];
|
|
|
|
}));
|
|
|
|
parameters.push(this.constantPool.getConstLiteral(asLiteral(references), true));
|
2018-01-11 15:37:56 -08:00
|
|
|
} else {
|
2018-04-13 15:12:11 -07:00
|
|
|
parameters.push(o.TYPED_NULL_EXPR);
|
2018-01-11 15:37:56 -08:00
|
|
|
}
|
2017-11-20 10:21:17 -08:00
|
|
|
|
2018-01-11 15:37:56 -08:00
|
|
|
// Generate the instruction create element instruction
|
2018-02-14 10:54:00 -08:00
|
|
|
if (i18nMessages.length > 0) {
|
2018-04-18 16:23:49 -07:00
|
|
|
this._creationCode.push(...i18nMessages);
|
2018-02-14 10:54:00 -08:00
|
|
|
}
|
2018-06-08 09:00:01 -07:00
|
|
|
|
|
|
|
const wasInNamespace = this._namespace;
|
|
|
|
const currentNamespace = this.getNamespaceInstruction(namespaceKey);
|
|
|
|
|
|
|
|
// If the namespace is changing now, include an instruction to change it
|
|
|
|
// during element creation.
|
|
|
|
if (currentNamespace !== wasInNamespace) {
|
|
|
|
this.addNamespaceInstruction(currentNamespace, element);
|
|
|
|
}
|
|
|
|
|
2018-04-05 11:38:06 -07:00
|
|
|
const implicit = o.variable(CONTEXT_NAME);
|
2017-11-20 10:21:17 -08:00
|
|
|
|
2018-06-19 12:45:00 -07:00
|
|
|
const createSelfClosingInstruction =
|
2018-07-11 09:56:47 -07:00
|
|
|
!hasStylingInstructions && element.children.length === 0 && element.outputs.length === 0;
|
2018-06-19 12:45:00 -07:00
|
|
|
|
|
|
|
if (createSelfClosingInstruction) {
|
2018-07-18 01:59:49 +00:00
|
|
|
this.creationInstruction(element.sourceSpan, R3.element, ...trimTrailingNulls(parameters));
|
2018-06-08 10:48:27 -07:00
|
|
|
} else {
|
|
|
|
// Generate the instruction create element instruction
|
|
|
|
if (i18nMessages.length > 0) {
|
|
|
|
this._creationCode.push(...i18nMessages);
|
|
|
|
}
|
2018-07-18 01:59:49 +00:00
|
|
|
this.creationInstruction(
|
|
|
|
element.sourceSpan, R3.elementStart, ...trimTrailingNulls(parameters));
|
2018-06-08 10:48:27 -07:00
|
|
|
|
2018-06-19 12:45:00 -07:00
|
|
|
// initial styling for static style="..." attributes
|
2018-07-11 09:56:47 -07:00
|
|
|
if (hasStylingInstructions) {
|
|
|
|
const paramsList: (o.Expression)[] = [];
|
|
|
|
|
2018-07-11 10:58:18 -07:00
|
|
|
if (initialClassDeclarations.length) {
|
|
|
|
// the template compiler handles initial class styling (e.g. class="foo") values
|
|
|
|
// in a special command called `elementClass` so that the initial class
|
|
|
|
// can be processed during runtime. These initial class values are bound to
|
|
|
|
// a constant because the inital class values do not change (since they're static).
|
|
|
|
paramsList.push(
|
|
|
|
this.constantPool.getConstLiteral(o.literalArr(initialClassDeclarations), true));
|
|
|
|
} else if (initialStyleDeclarations.length || useDefaultStyleSanitizer) {
|
|
|
|
// no point in having an extra `null` value unless there are follow-up params
|
|
|
|
paramsList.push(o.NULL_EXPR);
|
|
|
|
}
|
|
|
|
|
2018-06-19 12:45:00 -07:00
|
|
|
if (initialStyleDeclarations.length) {
|
2018-07-11 09:56:47 -07:00
|
|
|
// the template compiler handles initial style (e.g. style="foo") values
|
2018-06-19 12:45:00 -07:00
|
|
|
// in a special command called `elementStyle` so that the initial styles
|
|
|
|
// can be processed during runtime. These initial styles values are bound to
|
|
|
|
// a constant because the inital style values do not change (since they're static).
|
|
|
|
paramsList.push(
|
|
|
|
this.constantPool.getConstLiteral(o.literalArr(initialStyleDeclarations), true));
|
2018-07-11 10:58:18 -07:00
|
|
|
} else if (useDefaultStyleSanitizer) {
|
2018-07-11 09:56:47 -07:00
|
|
|
// no point in having an extra `null` value unless there are follow-up params
|
|
|
|
paramsList.push(o.NULL_EXPR);
|
|
|
|
}
|
|
|
|
|
2018-07-11 10:58:18 -07:00
|
|
|
|
|
|
|
if (useDefaultStyleSanitizer) {
|
|
|
|
paramsList.push(o.importExpr(R3.defaultStyleSanitizer));
|
2018-06-19 12:45:00 -07:00
|
|
|
}
|
2018-07-11 09:56:47 -07:00
|
|
|
|
2018-06-19 12:45:00 -07:00
|
|
|
this._creationCode.push(o.importExpr(R3.elementStyling).callFn(paramsList).toStmt());
|
|
|
|
}
|
|
|
|
|
2018-06-08 10:48:27 -07:00
|
|
|
// Generate Listeners (outputs)
|
|
|
|
element.outputs.forEach((outputAst: t.BoundEvent) => {
|
|
|
|
const elName = sanitizeIdentifier(element.name);
|
|
|
|
const evName = sanitizeIdentifier(outputAst.name);
|
|
|
|
const functionName = `${this.templateName}_${elName}_${evName}_listener`;
|
|
|
|
const localVars: o.Statement[] = [];
|
2018-07-18 01:59:49 +00:00
|
|
|
const bindingScope = this._bindingScope.nestedScope(
|
|
|
|
this.level + 1, (lhsVar: o.ReadVarExpr, rhsExpression: o.Expression) => {
|
2018-06-08 10:48:27 -07:00
|
|
|
localVars.push(
|
|
|
|
lhsVar.set(rhsExpression).toDeclStmt(o.INFERRED_TYPE, [o.StmtModifier.Final]));
|
|
|
|
});
|
|
|
|
const bindingExpr = convertActionBinding(
|
|
|
|
bindingScope, implicit, outputAst.handler, 'b',
|
|
|
|
() => error('Unexpected interpolation'));
|
|
|
|
const handler = o.fn(
|
|
|
|
[new o.FnParam('$event', o.DYNAMIC_TYPE)], [...localVars, ...bindingExpr.render3Stmts],
|
|
|
|
o.INFERRED_TYPE, null, functionName);
|
2018-07-18 01:59:49 +00:00
|
|
|
this.creationInstruction(
|
|
|
|
outputAst.sourceSpan, R3.listener, o.literal(outputAst.name), handler);
|
2018-06-08 10:48:27 -07:00
|
|
|
});
|
|
|
|
}
|
2018-03-23 10:55:17 -07:00
|
|
|
|
2018-07-11 09:56:47 -07:00
|
|
|
if ((styleInputs.length || classInputs.length) && hasStylingInstructions) {
|
|
|
|
const indexLiteral = o.literal(elementIndex);
|
|
|
|
|
|
|
|
const firstStyle = styleInputs[0];
|
|
|
|
const mapBasedStyleInput = firstStyle && firstStyle.name == 'style' ? firstStyle : null;
|
|
|
|
|
|
|
|
const firstClass = classInputs[0];
|
|
|
|
const mapBasedClassInput = firstClass && isClassBinding(firstClass) ? firstClass : null;
|
|
|
|
|
|
|
|
const stylingInput = mapBasedStyleInput || mapBasedClassInput;
|
|
|
|
if (stylingInput) {
|
|
|
|
const params: o.Expression[] = [];
|
2018-07-18 01:59:49 +00:00
|
|
|
let value: AST;
|
2018-07-11 09:56:47 -07:00
|
|
|
if (mapBasedClassInput) {
|
2018-07-18 01:59:49 +00:00
|
|
|
value = mapBasedClassInput.value.visit(this._valueConverter);
|
2018-07-11 10:58:18 -07:00
|
|
|
} else if (mapBasedStyleInput) {
|
|
|
|
params.push(o.NULL_EXPR);
|
|
|
|
}
|
2018-07-18 01:59:49 +00:00
|
|
|
|
2018-07-11 10:58:18 -07:00
|
|
|
if (mapBasedStyleInput) {
|
2018-07-18 01:59:49 +00:00
|
|
|
value = mapBasedStyleInput.value.visit(this._valueConverter);
|
2018-07-11 09:56:47 -07:00
|
|
|
}
|
2018-07-18 01:59:49 +00:00
|
|
|
|
|
|
|
this.updateInstruction(stylingInput.sourceSpan, R3.elementStylingMap, () => {
|
|
|
|
params.push(this.convertPropertyBinding(implicit, value, true));
|
|
|
|
return [indexLiteral, ...params];
|
|
|
|
});
|
2018-07-11 09:56:47 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
let lastInputCommand: t.BoundAttribute|null = null;
|
|
|
|
if (styleInputs.length) {
|
|
|
|
let i = mapBasedStyleInput ? 1 : 0;
|
|
|
|
for (i; i < styleInputs.length; i++) {
|
|
|
|
const input = styleInputs[i];
|
2018-07-18 01:59:49 +00:00
|
|
|
const params: any[] = [];
|
2018-07-11 10:58:18 -07:00
|
|
|
const sanitizationRef = resolveSanitizationFn(input, input.securityContext);
|
2018-07-18 01:59:49 +00:00
|
|
|
if (sanitizationRef) params.push(sanitizationRef);
|
2018-07-11 10:58:18 -07:00
|
|
|
|
2018-06-19 12:45:00 -07:00
|
|
|
const key = input.name;
|
2018-07-11 09:56:47 -07:00
|
|
|
const styleIndex: number = stylesIndexMap[key] !;
|
2018-07-18 01:59:49 +00:00
|
|
|
const value = input.value.visit(this._valueConverter);
|
|
|
|
this.updateInstruction(input.sourceSpan, R3.elementStyleProp, () => {
|
|
|
|
return [
|
|
|
|
indexLiteral, o.literal(styleIndex),
|
|
|
|
this.convertPropertyBinding(implicit, value, true), ...params
|
|
|
|
];
|
|
|
|
});
|
2018-06-19 12:45:00 -07:00
|
|
|
}
|
|
|
|
|
2018-07-11 09:56:47 -07:00
|
|
|
lastInputCommand = styleInputs[styleInputs.length - 1];
|
|
|
|
}
|
|
|
|
|
|
|
|
if (classInputs.length) {
|
|
|
|
let i = mapBasedClassInput ? 1 : 0;
|
|
|
|
for (i; i < classInputs.length; i++) {
|
|
|
|
const input = classInputs[i];
|
2018-07-18 01:59:49 +00:00
|
|
|
const params: any[] = [];
|
2018-07-11 10:58:18 -07:00
|
|
|
const sanitizationRef = resolveSanitizationFn(input, input.securityContext);
|
2018-07-18 01:59:49 +00:00
|
|
|
if (sanitizationRef) params.push(sanitizationRef);
|
2018-07-11 10:58:18 -07:00
|
|
|
|
2018-07-11 09:56:47 -07:00
|
|
|
const key = input.name;
|
|
|
|
const classIndex: number = classesIndexMap[key] !;
|
2018-07-18 01:59:49 +00:00
|
|
|
const value = input.value.visit(this._valueConverter);
|
|
|
|
this.updateInstruction(input.sourceSpan, R3.elementClassProp, () => {
|
|
|
|
return [
|
|
|
|
indexLiteral, o.literal(classIndex),
|
|
|
|
this.convertPropertyBinding(implicit, value, true), ...params
|
|
|
|
];
|
|
|
|
});
|
2018-07-11 09:56:47 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
lastInputCommand = classInputs[classInputs.length - 1];
|
|
|
|
}
|
|
|
|
|
2018-07-18 01:59:49 +00:00
|
|
|
this.updateInstruction(
|
|
|
|
lastInputCommand !.sourceSpan, R3.elementStylingApply, () => [indexLiteral]);
|
2018-06-19 12:45:00 -07:00
|
|
|
}
|
|
|
|
|
2017-11-20 10:21:17 -08:00
|
|
|
// Generate element input bindings
|
2018-06-19 12:45:00 -07:00
|
|
|
allOtherInputs.forEach((input: t.BoundAttribute) => {
|
2018-04-24 14:22:55 -07:00
|
|
|
if (input.type === BindingType.Animation) {
|
2018-07-02 11:26:16 -07:00
|
|
|
console.error('warning: animation bindings not yet supported');
|
|
|
|
return;
|
2017-11-20 10:21:17 -08:00
|
|
|
}
|
2018-06-19 12:45:00 -07:00
|
|
|
|
2018-06-20 16:00:48 -07:00
|
|
|
const instruction = mapBindingToInstruction(input.type);
|
2017-11-20 10:21:17 -08:00
|
|
|
if (instruction) {
|
2018-07-18 01:59:49 +00:00
|
|
|
const params: any[] = [];
|
2018-07-11 10:58:18 -07:00
|
|
|
const sanitizationRef = resolveSanitizationFn(input, input.securityContext);
|
2018-07-18 01:59:49 +00:00
|
|
|
if (sanitizationRef) params.push(sanitizationRef);
|
2018-07-11 10:58:18 -07:00
|
|
|
|
2017-11-20 10:21:17 -08:00
|
|
|
// TODO(chuckj): runtime: security context?
|
2018-07-18 01:59:49 +00:00
|
|
|
const value = input.value.visit(this._valueConverter);
|
|
|
|
this.updateInstruction(input.sourceSpan, instruction, () => {
|
|
|
|
return [
|
|
|
|
o.literal(elementIndex), o.literal(input.name),
|
|
|
|
this.convertPropertyBinding(implicit, value), ...params
|
|
|
|
];
|
|
|
|
});
|
2017-11-20 10:21:17 -08:00
|
|
|
} else {
|
2018-04-20 11:28:34 -07:00
|
|
|
this._unsupported(`binding type ${input.type}`);
|
2017-11-20 10:21:17 -08:00
|
|
|
}
|
2018-04-18 16:23:49 -07:00
|
|
|
});
|
2017-11-20 10:21:17 -08:00
|
|
|
|
|
|
|
// Traverse element child nodes
|
2018-02-14 10:54:00 -08:00
|
|
|
if (this._inI18nSection && element.children.length == 1 &&
|
2018-04-18 16:23:49 -07:00
|
|
|
element.children[0] instanceof t.Text) {
|
|
|
|
const text = element.children[0] as t.Text;
|
2018-02-14 10:54:00 -08:00
|
|
|
this.visitSingleI18nTextChild(text, i18nMeta);
|
|
|
|
} else {
|
2018-04-18 16:23:49 -07:00
|
|
|
t.visitAll(this, element.children);
|
2018-02-14 10:54:00 -08:00
|
|
|
}
|
2017-11-20 10:21:17 -08:00
|
|
|
|
2018-06-19 12:45:00 -07:00
|
|
|
if (!createSelfClosingInstruction) {
|
2018-06-08 10:48:27 -07:00
|
|
|
// Finish element construction mode.
|
2018-07-18 01:59:49 +00:00
|
|
|
this.creationInstruction(element.endSourceSpan || element.sourceSpan, R3.elementEnd);
|
2018-06-08 10:48:27 -07:00
|
|
|
}
|
2018-06-06 13:38:19 -07:00
|
|
|
|
2018-02-14 10:54:00 -08:00
|
|
|
// Restore the state before exiting this node
|
|
|
|
this._inI18nSection = wasInI18nSection;
|
2017-11-20 10:21:17 -08:00
|
|
|
}
|
|
|
|
|
2018-04-18 16:23:49 -07:00
|
|
|
visitTemplate(template: t.Template) {
|
|
|
|
const templateIndex = this.allocateDataSlot();
|
|
|
|
|
|
|
|
let elName = '';
|
|
|
|
if (template.children.length === 1 && template.children[0] instanceof t.Element) {
|
|
|
|
// When the template as a single child, derive the context name from the tag
|
|
|
|
elName = sanitizeIdentifier((template.children[0] as t.Element).name);
|
2017-11-20 10:21:17 -08:00
|
|
|
}
|
|
|
|
|
2018-04-18 16:23:49 -07:00
|
|
|
const contextName = elName ? `${this.contextName}_${elName}` : '';
|
2017-11-20 10:21:17 -08:00
|
|
|
|
2018-01-22 15:35:18 -08:00
|
|
|
const templateName =
|
|
|
|
contextName ? `${contextName}_Template_${templateIndex}` : `Template_${templateIndex}`;
|
2018-04-18 16:23:49 -07:00
|
|
|
|
2017-11-20 10:21:17 -08:00
|
|
|
const templateContext = `ctx${this.level}`;
|
|
|
|
|
2018-04-18 16:23:49 -07:00
|
|
|
const parameters: o.Expression[] = [
|
|
|
|
o.literal(templateIndex),
|
|
|
|
o.variable(templateName),
|
|
|
|
o.TYPED_NULL_EXPR,
|
|
|
|
];
|
|
|
|
|
2018-03-29 12:58:41 -07:00
|
|
|
const attributeNames: o.Expression[] = [];
|
2018-04-18 16:23:49 -07:00
|
|
|
const attributeMap: {[name: string]: string} = {};
|
|
|
|
|
|
|
|
template.attributes.forEach(a => {
|
|
|
|
attributeNames.push(asLiteral(a.name), asLiteral(''));
|
|
|
|
attributeMap[a.name] = a.value;
|
2018-03-29 12:58:41 -07:00
|
|
|
});
|
2018-04-18 16:23:49 -07:00
|
|
|
|
|
|
|
// Match directives on template attributes
|
|
|
|
if (this.directiveMatcher) {
|
|
|
|
const selector = createCssSelector('ng-template', attributeMap);
|
|
|
|
this.directiveMatcher.match(
|
|
|
|
selector, (cssSelector, staticType) => { this.directives.add(staticType); });
|
|
|
|
}
|
|
|
|
|
2018-03-29 12:58:41 -07:00
|
|
|
if (attributeNames.length) {
|
2018-04-18 16:23:49 -07:00
|
|
|
parameters.push(this.constantPool.getConstLiteral(o.literalArr(attributeNames), true));
|
2018-03-29 12:58:41 -07:00
|
|
|
}
|
2017-11-20 10:21:17 -08:00
|
|
|
|
|
|
|
// e.g. C(1, C1Template)
|
2018-07-18 01:59:49 +00:00
|
|
|
this.creationInstruction(
|
|
|
|
template.sourceSpan, R3.containerCreate, ...trimTrailingNulls(parameters));
|
2017-11-20 10:21:17 -08:00
|
|
|
|
2018-04-18 16:23:49 -07:00
|
|
|
// e.g. p(1, 'forOf', ɵb(ctx.items));
|
|
|
|
const context = o.variable(CONTEXT_NAME);
|
|
|
|
template.inputs.forEach(input => {
|
2018-07-18 01:59:49 +00:00
|
|
|
const value = input.value.visit(this._valueConverter);
|
|
|
|
this.updateInstruction(template.sourceSpan, R3.elementProperty, () => {
|
|
|
|
return [
|
|
|
|
o.literal(templateIndex), o.literal(input.name),
|
|
|
|
this.convertPropertyBinding(context, value)
|
|
|
|
];
|
|
|
|
});
|
2018-04-18 16:23:49 -07:00
|
|
|
});
|
2018-01-11 15:37:56 -08:00
|
|
|
|
2017-11-20 10:21:17 -08:00
|
|
|
// Create the template function
|
|
|
|
const templateVisitor = new TemplateDefinitionBuilder(
|
2018-04-24 11:34:11 -07:00
|
|
|
this.constantPool, templateContext, this._bindingScope, this.level + 1, contextName,
|
2018-06-08 09:00:01 -07:00
|
|
|
templateName, [], this.directiveMatcher, this.directives, this.pipeTypeByName, this.pipes,
|
|
|
|
this._namespace);
|
2018-07-18 01:59:49 +00:00
|
|
|
|
|
|
|
// Nested templates must not be visited until after their parent templates have completed
|
|
|
|
// processing, so they are queued here until after the initial pass. Otherwise, we wouldn't
|
|
|
|
// be able to support bindings in nested templates to local refs that occur after the
|
|
|
|
// template definition. e.g. <div *ngIf="showing"> {{ foo }} </div> <div #foo></div>
|
|
|
|
this._nestedTemplates.push(() => {
|
|
|
|
const templateFunctionExpr =
|
|
|
|
templateVisitor.buildTemplateFunction(template.children, template.variables);
|
|
|
|
this.constantPool.statements.push(templateFunctionExpr.toDeclStmt(templateName, null));
|
|
|
|
});
|
2017-11-20 10:21:17 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
// These should be handled in the template or element directly.
|
|
|
|
readonly visitReference = invalid;
|
|
|
|
readonly visitVariable = invalid;
|
2018-04-24 14:22:55 -07:00
|
|
|
readonly visitTextAttribute = invalid;
|
2018-04-18 16:23:49 -07:00
|
|
|
readonly visitBoundAttribute = invalid;
|
|
|
|
readonly visitBoundEvent = invalid;
|
2017-11-20 10:21:17 -08:00
|
|
|
|
2018-04-18 16:23:49 -07:00
|
|
|
visitBoundText(text: t.BoundText) {
|
2018-01-11 15:37:56 -08:00
|
|
|
const nodeIndex = this.allocateDataSlot();
|
2017-11-20 10:21:17 -08:00
|
|
|
|
2018-07-18 01:59:49 +00:00
|
|
|
this.creationInstruction(text.sourceSpan, R3.text, o.literal(nodeIndex));
|
2017-11-20 10:21:17 -08:00
|
|
|
|
2018-07-18 01:59:49 +00:00
|
|
|
const value = text.value.visit(this._valueConverter);
|
|
|
|
this.updateInstruction(
|
|
|
|
text.sourceSpan, R3.textBinding,
|
|
|
|
() => [o.literal(nodeIndex), this.convertPropertyBinding(o.variable(CONTEXT_NAME), value)]);
|
2017-11-20 10:21:17 -08:00
|
|
|
}
|
|
|
|
|
2018-04-18 16:23:49 -07:00
|
|
|
visitText(text: t.Text) {
|
2018-07-18 01:59:49 +00:00
|
|
|
this.creationInstruction(
|
|
|
|
text.sourceSpan, R3.text, o.literal(this.allocateDataSlot()), o.literal(text.value));
|
2017-11-20 10:21:17 -08:00
|
|
|
}
|
|
|
|
|
2018-02-14 10:54:00 -08:00
|
|
|
// When the content of the element is a single text node the translation can be inlined:
|
|
|
|
//
|
|
|
|
// `<p i18n="desc|mean">some content</p>`
|
|
|
|
// compiles to
|
|
|
|
// ```
|
|
|
|
// /**
|
|
|
|
// * @desc desc
|
|
|
|
// * @meaning mean
|
|
|
|
// */
|
|
|
|
// const MSG_XYZ = goog.getMsg('some content');
|
|
|
|
// i0.ɵT(1, MSG_XYZ);
|
|
|
|
// ```
|
2018-04-18 16:23:49 -07:00
|
|
|
visitSingleI18nTextChild(text: t.Text, i18nMeta: string) {
|
2018-03-22 15:03:06 -07:00
|
|
|
const meta = parseI18nMeta(i18nMeta);
|
|
|
|
const variable = this.constantPool.getTranslation(text.value, meta);
|
2018-07-18 01:59:49 +00:00
|
|
|
this.creationInstruction(
|
|
|
|
text.sourceSpan, R3.text, o.literal(this.allocateDataSlot()), variable);
|
2018-02-14 10:54:00 -08:00
|
|
|
}
|
|
|
|
|
2018-01-11 15:37:56 -08:00
|
|
|
private allocateDataSlot() { return this._dataIndex++; }
|
2017-11-20 10:21:17 -08:00
|
|
|
private bindingContext() { return `${this._bindingContext++}`; }
|
|
|
|
|
|
|
|
private instruction(
|
2018-07-18 01:59:49 +00:00
|
|
|
span: ParseSourceSpan|null, reference: o.ExternalReference,
|
|
|
|
params: o.Expression[]): o.Statement {
|
|
|
|
return o.importExpr(reference, null, span).callFn(params, span).toStmt();
|
|
|
|
}
|
|
|
|
|
|
|
|
private creationInstruction(
|
|
|
|
span: ParseSourceSpan|null, reference: o.ExternalReference, ...params: o.Expression[]) {
|
|
|
|
this._creationCode.push(this.instruction(span, reference, params));
|
|
|
|
}
|
|
|
|
|
|
|
|
// Bindings must only be resolved after all local refs have been visited, so update mode
|
|
|
|
// instructions are queued in callbacks that execute once the initial pass has completed.
|
|
|
|
// Otherwise, we wouldn't be able to support local refs that are defined after their
|
|
|
|
// bindings. e.g. {{ foo }} <div #foo></div>
|
|
|
|
private updateInstruction(
|
|
|
|
span: ParseSourceSpan|null, reference: o.ExternalReference, paramsFn: () => o.Expression[]) {
|
|
|
|
this._bindingCode.push(() => { return this.instruction(span, reference, paramsFn()); });
|
2017-11-20 10:21:17 -08:00
|
|
|
}
|
|
|
|
|
2018-06-19 12:45:00 -07:00
|
|
|
private convertPropertyBinding(implicit: o.Expression, value: AST, skipBindFn?: boolean):
|
|
|
|
o.Expression {
|
2018-07-18 01:59:49 +00:00
|
|
|
const interpolationFn =
|
|
|
|
value instanceof Interpolation ? interpolate : () => error('Unexpected interpolation');
|
|
|
|
|
|
|
|
const convertedPropertyBinding = convertPropertyBinding(
|
|
|
|
this, implicit, value, this.bindingContext(), BindingForm.TrySimple, interpolationFn);
|
|
|
|
this._variableCode.push(...convertedPropertyBinding.stmts);
|
|
|
|
|
|
|
|
const valExpr = convertedPropertyBinding.currValExpr;
|
|
|
|
return value instanceof Interpolation || skipBindFn ? valExpr :
|
|
|
|
o.importExpr(R3.bind).callFn([valExpr]);
|
2017-11-20 10:21:17 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-02-14 17:12:05 -08:00
|
|
|
class ValueConverter extends AstMemoryEfficientTransformer {
|
2018-02-05 17:31:12 -08:00
|
|
|
constructor(
|
2018-04-24 11:34:11 -07:00
|
|
|
private constantPool: ConstantPool, private allocateSlot: () => number,
|
2018-05-21 15:59:25 -07:00
|
|
|
private allocatePureFunctionSlots: (numSlots: number) => number,
|
2018-02-05 17:31:12 -08:00
|
|
|
private definePipe:
|
|
|
|
(name: string, localName: string, slot: number, value: o.Expression) => void) {
|
|
|
|
super();
|
|
|
|
}
|
|
|
|
|
|
|
|
// AstMemoryEfficientTransformer
|
2018-04-13 15:12:11 -07:00
|
|
|
visitPipe(pipe: BindingPipe, context: any): AST {
|
2018-02-05 17:31:12 -08:00
|
|
|
// Allocate a slot to create the pipe
|
2018-03-30 16:07:37 -07:00
|
|
|
const slot = this.allocateSlot();
|
2018-02-05 17:31:12 -08:00
|
|
|
const slotPseudoLocal = `PIPE:${slot}`;
|
2018-05-21 15:59:25 -07:00
|
|
|
// Allocate one slot for the result plus one slot per pipe argument
|
|
|
|
const pureFunctionSlot = this.allocatePureFunctionSlots(2 + pipe.args.length);
|
2018-04-13 15:12:11 -07:00
|
|
|
const target = new PropertyRead(pipe.span, new ImplicitReceiver(pipe.span), slotPseudoLocal);
|
2018-05-22 10:48:11 -07:00
|
|
|
const {identifier, isVarLength} = pipeBindingCallInfo(pipe.args);
|
|
|
|
this.definePipe(pipe.name, slotPseudoLocal, slot, o.importExpr(identifier));
|
|
|
|
const args: AST[] = [pipe.exp, ...pipe.args];
|
|
|
|
const convertedArgs: AST[] =
|
|
|
|
isVarLength ? this.visitAll([new LiteralArray(pipe.span, args)]) : this.visitAll(args);
|
2018-02-05 17:31:12 -08:00
|
|
|
|
2018-05-21 15:59:25 -07:00
|
|
|
return new FunctionCall(pipe.span, target, [
|
|
|
|
new LiteralPrimitive(pipe.span, slot),
|
|
|
|
new LiteralPrimitive(pipe.span, pureFunctionSlot),
|
2018-05-22 10:48:11 -07:00
|
|
|
...convertedArgs,
|
2018-05-21 15:59:25 -07:00
|
|
|
]);
|
2018-02-05 17:31:12 -08:00
|
|
|
}
|
2018-02-14 17:12:05 -08:00
|
|
|
|
2018-04-13 15:12:11 -07:00
|
|
|
visitLiteralArray(array: LiteralArray, context: any): AST {
|
|
|
|
return new BuiltinFunctionCall(array.span, this.visitAll(array.expressions), values => {
|
2018-02-14 10:54:00 -08:00
|
|
|
// If the literal has calculated (non-literal) elements transform it into
|
2018-02-14 17:12:05 -08:00
|
|
|
// calls to literal factories that compose the literal and will cache intermediate
|
|
|
|
// values. Otherwise, just return an literal array that contains the values.
|
|
|
|
const literal = o.literalArr(values);
|
2018-05-21 15:59:25 -07:00
|
|
|
return values.every(a => a.isConstant()) ?
|
|
|
|
this.constantPool.getConstLiteral(literal, true) :
|
|
|
|
getLiteralFactory(this.constantPool, literal, this.allocatePureFunctionSlots);
|
2018-02-14 17:12:05 -08:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2018-04-13 15:12:11 -07:00
|
|
|
visitLiteralMap(map: LiteralMap, context: any): AST {
|
|
|
|
return new BuiltinFunctionCall(map.span, this.visitAll(map.values), values => {
|
2018-02-14 17:12:05 -08:00
|
|
|
// If the literal has calculated (non-literal) elements transform it into
|
|
|
|
// calls to literal factories that compose the literal and will cache intermediate
|
|
|
|
// values. Otherwise, just return an literal array that contains the values.
|
|
|
|
const literal = o.literalMap(values.map(
|
2018-04-13 15:12:11 -07:00
|
|
|
(value, index) => ({key: map.keys[index].key, value, quoted: map.keys[index].quoted})));
|
2018-05-21 15:59:25 -07:00
|
|
|
return values.every(a => a.isConstant()) ?
|
|
|
|
this.constantPool.getConstLiteral(literal, true) :
|
|
|
|
getLiteralFactory(this.constantPool, literal, this.allocatePureFunctionSlots);
|
2018-02-14 17:12:05 -08:00
|
|
|
});
|
|
|
|
}
|
2018-02-05 17:31:12 -08:00
|
|
|
}
|
|
|
|
|
2018-04-24 11:34:11 -07:00
|
|
|
// Pipes always have at least one parameter, the value they operate on
|
|
|
|
const pipeBindingIdentifiers = [R3.pipeBind1, R3.pipeBind2, R3.pipeBind3, R3.pipeBind4];
|
|
|
|
|
2018-05-22 10:48:11 -07:00
|
|
|
function pipeBindingCallInfo(args: o.Expression[]) {
|
|
|
|
const identifier = pipeBindingIdentifiers[args.length];
|
|
|
|
return {
|
|
|
|
identifier: identifier || R3.pipeBindV,
|
|
|
|
isVarLength: !identifier,
|
|
|
|
};
|
2017-11-20 10:21:17 -08:00
|
|
|
}
|
|
|
|
|
2018-04-24 11:34:11 -07:00
|
|
|
const pureFunctionIdentifiers = [
|
|
|
|
R3.pureFunction0, R3.pureFunction1, R3.pureFunction2, R3.pureFunction3, R3.pureFunction4,
|
|
|
|
R3.pureFunction5, R3.pureFunction6, R3.pureFunction7, R3.pureFunction8
|
|
|
|
];
|
2018-05-22 10:47:50 -07:00
|
|
|
|
|
|
|
function pureFunctionCallInfo(args: o.Expression[]) {
|
|
|
|
const identifier = pureFunctionIdentifiers[args.length];
|
|
|
|
return {
|
|
|
|
identifier: identifier || R3.pureFunctionV,
|
|
|
|
isVarLength: !identifier,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2018-04-24 11:34:11 -07:00
|
|
|
function getLiteralFactory(
|
2018-05-21 15:59:25 -07:00
|
|
|
constantPool: ConstantPool, literal: o.LiteralArrayExpr | o.LiteralMapExpr,
|
|
|
|
allocateSlots: (numSlots: number) => number): o.Expression {
|
2018-04-24 11:34:11 -07:00
|
|
|
const {literalFactory, literalFactoryArguments} = constantPool.getLiteralFactory(literal);
|
2018-05-21 15:59:25 -07:00
|
|
|
// Allocate 1 slot for the result plus 1 per argument
|
|
|
|
const startSlot = allocateSlots(1 + literalFactoryArguments.length);
|
2018-04-24 11:34:11 -07:00
|
|
|
literalFactoryArguments.length > 0 || error(`Expected arguments to a literal factory function`);
|
2018-05-22 10:47:50 -07:00
|
|
|
const {identifier, isVarLength} = pureFunctionCallInfo(literalFactoryArguments);
|
2018-04-24 11:34:11 -07:00
|
|
|
|
|
|
|
// Literal factories are pure functions that only need to be re-invoked when the parameters
|
|
|
|
// change.
|
2018-05-22 10:47:50 -07:00
|
|
|
const args = [
|
|
|
|
o.literal(startSlot),
|
|
|
|
literalFactory,
|
|
|
|
];
|
|
|
|
|
|
|
|
if (isVarLength) {
|
|
|
|
args.push(o.literalArr(literalFactoryArguments));
|
|
|
|
} else {
|
|
|
|
args.push(...literalFactoryArguments);
|
|
|
|
}
|
|
|
|
|
|
|
|
return o.importExpr(identifier).callFn(args);
|
2018-01-26 17:12:39 -08:00
|
|
|
}
|
2018-01-29 10:36:56 -08:00
|
|
|
|
2018-04-24 11:34:11 -07:00
|
|
|
/**
|
|
|
|
* Function which is executed whenever a variable is referenced for the first time in a given
|
|
|
|
* scope.
|
|
|
|
*
|
|
|
|
* It is expected that the function creates the `const localName = expression`; statement.
|
|
|
|
*/
|
|
|
|
export type DeclareLocalVarCallback = (lhsVar: o.ReadVarExpr, rhsExpression: o.Expression) => void;
|
|
|
|
|
|
|
|
export class BindingScope implements LocalResolver {
|
|
|
|
/**
|
|
|
|
* Keeps a map from local variables to their expressions.
|
|
|
|
*
|
|
|
|
* This is used when one refers to variable such as: 'let abc = a.b.c`.
|
|
|
|
* - key to the map is the string literal `"abc"`.
|
|
|
|
* - value `lhs` is the left hand side which is an AST representing `abc`.
|
|
|
|
* - value `rhs` is the right hand side which is an AST representing `a.b.c`.
|
|
|
|
* - value `declared` is true if the `declareLocalVarCallback` has been called for this scope
|
|
|
|
* already.
|
|
|
|
*/
|
|
|
|
private map = new Map < string, {
|
|
|
|
lhs: o.ReadVarExpr;
|
|
|
|
rhs: o.Expression|undefined;
|
|
|
|
declared: boolean;
|
2018-07-18 01:59:49 +00:00
|
|
|
rhsCallback?: (level: number) => o.Expression;
|
2018-04-18 16:23:49 -07:00
|
|
|
}
|
2018-04-24 11:34:11 -07:00
|
|
|
> ();
|
|
|
|
private referenceNameIndex = 0;
|
|
|
|
|
|
|
|
static ROOT_SCOPE = new BindingScope().set('$event', o.variable('$event'));
|
|
|
|
|
|
|
|
private constructor(
|
2018-07-18 01:59:49 +00:00
|
|
|
private level: number = 0, private parent: BindingScope|null = null,
|
2018-04-24 11:34:11 -07:00
|
|
|
private declareLocalVarCallback: DeclareLocalVarCallback = noop) {}
|
|
|
|
|
|
|
|
get(name: string): o.Expression|null {
|
|
|
|
let current: BindingScope|null = this;
|
|
|
|
while (current) {
|
|
|
|
let value = current.map.get(name);
|
|
|
|
if (value != null) {
|
|
|
|
if (current !== this) {
|
|
|
|
// make a local copy and reset the `declared` state.
|
2018-07-18 01:59:49 +00:00
|
|
|
value = {lhs: value.lhs, rhs: value.rhs, rhsCallback: value.rhsCallback, declared: false};
|
2018-04-24 11:34:11 -07:00
|
|
|
// Cache the value locally.
|
|
|
|
this.map.set(name, value);
|
|
|
|
}
|
2018-07-18 01:59:49 +00:00
|
|
|
const rhs = value.rhs || value.rhsCallback && value.rhsCallback(this.level);
|
|
|
|
if (rhs && !value.declared) {
|
2018-04-24 11:34:11 -07:00
|
|
|
// if it is first time we are referencing the variable in the scope
|
2018-07-18 01:59:49 +00:00
|
|
|
// then invoke the callback to insert variable declaration.
|
|
|
|
this.declareLocalVarCallback(value.lhs, rhs);
|
2018-04-24 11:34:11 -07:00
|
|
|
value.declared = true;
|
|
|
|
}
|
|
|
|
return value.lhs;
|
|
|
|
}
|
|
|
|
current = current.parent;
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Create a local variable for later reference.
|
|
|
|
*
|
|
|
|
* @param name Name of the variable.
|
|
|
|
* @param lhs AST representing the left hand side of the `let lhs = rhs;`.
|
|
|
|
* @param rhs AST representing the right hand side of the `let lhs = rhs;`. The `rhs` can be
|
|
|
|
* `undefined` for variable that are ambient such as `$event` and which don't have `rhs`
|
|
|
|
* declaration.
|
|
|
|
*/
|
2018-07-18 01:59:49 +00:00
|
|
|
set(name: string, lhs: o.ReadVarExpr, rhs?: o.Expression,
|
|
|
|
rhsCallback?: (level: number) => o.Expression): BindingScope {
|
2018-04-24 11:34:11 -07:00
|
|
|
!this.map.has(name) ||
|
|
|
|
error(`The name ${name} is already defined in scope to be ${this.map.get(name)}`);
|
2018-07-18 01:59:49 +00:00
|
|
|
this.map.set(name, {lhs: lhs, rhs: rhs, declared: false, rhsCallback: rhsCallback});
|
2018-04-24 11:34:11 -07:00
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
|
|
|
getLocal(name: string): (o.Expression|null) { return this.get(name); }
|
2018-04-18 16:23:49 -07:00
|
|
|
|
2018-07-18 01:59:49 +00:00
|
|
|
nestedScope(level: number, declareCallback: DeclareLocalVarCallback): BindingScope {
|
|
|
|
return new BindingScope(level, this, declareCallback);
|
2018-04-24 11:34:11 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
freshReferenceName(): string {
|
|
|
|
let current: BindingScope = this;
|
|
|
|
// Find the top scope as it maintains the global reference count
|
|
|
|
while (current.parent) current = current.parent;
|
|
|
|
const ref = `${REFERENCE_PREFIX}${current.referenceNameIndex++}`;
|
|
|
|
return ref;
|
|
|
|
}
|
2018-02-14 10:54:00 -08:00
|
|
|
}
|
|
|
|
|
2018-04-18 16:23:49 -07:00
|
|
|
/**
|
2018-04-24 11:34:11 -07:00
|
|
|
* Creates a `CssSelector` given a tag name and a map of attributes
|
2018-04-18 16:23:49 -07:00
|
|
|
*/
|
2018-04-24 11:34:11 -07:00
|
|
|
function createCssSelector(tag: string, attributes: {[name: string]: string}): CssSelector {
|
|
|
|
const cssSelector = new CssSelector();
|
|
|
|
|
|
|
|
cssSelector.setElement(tag);
|
|
|
|
|
|
|
|
Object.getOwnPropertyNames(attributes).forEach((name) => {
|
|
|
|
const value = attributes[name];
|
|
|
|
|
|
|
|
cssSelector.addAttribute(name, value);
|
|
|
|
if (name.toLowerCase() === 'class') {
|
|
|
|
const classes = value.trim().split(/\s+/g);
|
|
|
|
classes.forEach(className => cssSelector.addClassName(className));
|
2018-04-18 16:23:49 -07:00
|
|
|
}
|
2018-04-24 11:34:11 -07:00
|
|
|
});
|
|
|
|
|
|
|
|
return cssSelector;
|
2018-04-18 16:23:49 -07:00
|
|
|
}
|
|
|
|
|
2018-02-14 10:54:00 -08:00
|
|
|
// Parse i18n metas like:
|
|
|
|
// - "@@id",
|
|
|
|
// - "description[@@id]",
|
|
|
|
// - "meaning|description[@@id]"
|
|
|
|
function parseI18nMeta(i18n?: string): {description?: string, id?: string, meaning?: string} {
|
|
|
|
let meaning: string|undefined;
|
|
|
|
let description: string|undefined;
|
|
|
|
let id: string|undefined;
|
|
|
|
|
|
|
|
if (i18n) {
|
|
|
|
// TODO(vicb): figure out how to force a message ID with closure ?
|
|
|
|
const idIndex = i18n.indexOf(ID_SEPARATOR);
|
|
|
|
|
|
|
|
const descIndex = i18n.indexOf(MEANING_SEPARATOR);
|
|
|
|
let meaningAndDesc: string;
|
|
|
|
[meaningAndDesc, id] =
|
|
|
|
(idIndex > -1) ? [i18n.slice(0, idIndex), i18n.slice(idIndex + 2)] : [i18n, ''];
|
|
|
|
[meaning, description] = (descIndex > -1) ?
|
|
|
|
[meaningAndDesc.slice(0, descIndex), meaningAndDesc.slice(descIndex + 1)] :
|
|
|
|
['', meaningAndDesc];
|
|
|
|
}
|
|
|
|
|
|
|
|
return {description, id, meaning};
|
|
|
|
}
|
2018-04-18 16:23:49 -07:00
|
|
|
|
2018-04-24 11:34:11 -07:00
|
|
|
function interpolate(args: o.Expression[]): o.Expression {
|
|
|
|
args = args.slice(1); // Ignore the length prefix added for render2
|
|
|
|
switch (args.length) {
|
|
|
|
case 3:
|
|
|
|
return o.importExpr(R3.interpolation1).callFn(args);
|
|
|
|
case 5:
|
|
|
|
return o.importExpr(R3.interpolation2).callFn(args);
|
|
|
|
case 7:
|
|
|
|
return o.importExpr(R3.interpolation3).callFn(args);
|
|
|
|
case 9:
|
|
|
|
return o.importExpr(R3.interpolation4).callFn(args);
|
|
|
|
case 11:
|
|
|
|
return o.importExpr(R3.interpolation5).callFn(args);
|
|
|
|
case 13:
|
|
|
|
return o.importExpr(R3.interpolation6).callFn(args);
|
|
|
|
case 15:
|
|
|
|
return o.importExpr(R3.interpolation7).callFn(args);
|
|
|
|
case 17:
|
|
|
|
return o.importExpr(R3.interpolation8).callFn(args);
|
|
|
|
}
|
|
|
|
(args.length >= 19 && args.length % 2 == 1) ||
|
|
|
|
error(`Invalid interpolation argument length ${args.length}`);
|
|
|
|
return o.importExpr(R3.interpolationV).callFn([o.literalArr(args)]);
|
2018-04-18 16:23:49 -07:00
|
|
|
}
|
2018-05-09 08:35:25 -07:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Parse a template into render3 `Node`s and additional metadata, with no other dependencies.
|
|
|
|
*
|
|
|
|
* @param template text of the template to parse
|
|
|
|
* @param templateUrl URL to use for source mapping of the parsed template
|
|
|
|
*/
|
2018-05-21 08:15:19 -07:00
|
|
|
export function parseTemplate(
|
2018-05-31 15:50:02 -07:00
|
|
|
template: string, templateUrl: string, options: {preserveWhitespaces?: boolean} = {}):
|
2018-05-09 08:35:25 -07:00
|
|
|
{errors?: ParseError[], nodes: t.Node[], hasNgContent: boolean, ngContentSelectors: string[]} {
|
|
|
|
const bindingParser = makeBindingParser();
|
|
|
|
const htmlParser = new HtmlParser();
|
|
|
|
const parseResult = htmlParser.parse(template, templateUrl);
|
2018-05-21 08:15:19 -07:00
|
|
|
|
2018-05-09 08:35:25 -07:00
|
|
|
if (parseResult.errors && parseResult.errors.length > 0) {
|
|
|
|
return {errors: parseResult.errors, nodes: [], hasNgContent: false, ngContentSelectors: []};
|
|
|
|
}
|
2018-05-21 08:15:19 -07:00
|
|
|
|
|
|
|
let rootNodes: html.Node[] = parseResult.rootNodes;
|
2018-05-31 15:50:02 -07:00
|
|
|
if (!options.preserveWhitespaces) {
|
2018-05-21 08:15:19 -07:00
|
|
|
rootNodes = html.visitAll(new WhitespaceVisitor(), rootNodes);
|
|
|
|
}
|
|
|
|
|
2018-05-09 08:35:25 -07:00
|
|
|
const {nodes, hasNgContent, ngContentSelectors, errors} =
|
2018-05-21 08:15:19 -07:00
|
|
|
htmlAstToRender3Ast(rootNodes, bindingParser);
|
2018-05-09 08:35:25 -07:00
|
|
|
if (errors && errors.length > 0) {
|
|
|
|
return {errors, nodes: [], hasNgContent: false, ngContentSelectors: []};
|
|
|
|
}
|
|
|
|
|
|
|
|
return {nodes, hasNgContent, ngContentSelectors};
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Construct a `BindingParser` with a default configuration.
|
|
|
|
*/
|
|
|
|
export function makeBindingParser(): BindingParser {
|
|
|
|
return new BindingParser(
|
2018-06-26 10:44:22 -07:00
|
|
|
new Parser(new Lexer()), DEFAULT_INTERPOLATION_CONFIG, new DomElementSchemaRegistry(), null,
|
2018-05-09 08:35:25 -07:00
|
|
|
[]);
|
|
|
|
}
|
2018-07-11 09:56:47 -07:00
|
|
|
|
|
|
|
function isClassBinding(input: t.BoundAttribute): boolean {
|
|
|
|
return input.name == 'className' || input.name == 'class';
|
|
|
|
}
|
2018-07-11 10:58:18 -07:00
|
|
|
|
|
|
|
function resolveSanitizationFn(input: t.BoundAttribute, context: core.SecurityContext) {
|
|
|
|
switch (context) {
|
|
|
|
case core.SecurityContext.HTML:
|
|
|
|
return o.importExpr(R3.sanitizeHtml);
|
|
|
|
case core.SecurityContext.SCRIPT:
|
|
|
|
return o.importExpr(R3.sanitizeScript);
|
|
|
|
case core.SecurityContext.STYLE:
|
|
|
|
// the compiler does not fill in an instruction for [style.prop?] binding
|
|
|
|
// values because the style algorithm knows internally what props are subject
|
|
|
|
// to sanitization (only [attr.style] values are explicitly sanitized)
|
|
|
|
return input.type === BindingType.Attribute ? o.importExpr(R3.sanitizeStyle) : null;
|
|
|
|
case core.SecurityContext.URL:
|
|
|
|
return o.importExpr(R3.sanitizeUrl);
|
|
|
|
case core.SecurityContext.RESOURCE_URL:
|
|
|
|
return o.importExpr(R3.sanitizeResourceUrl);
|
|
|
|
default:
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function isStyleSanitizable(prop: string): boolean {
|
|
|
|
switch (prop) {
|
|
|
|
case 'background-image':
|
|
|
|
case 'background':
|
|
|
|
case 'border-image':
|
|
|
|
case 'filter':
|
|
|
|
case 'list-style':
|
|
|
|
case 'list-style-image':
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|