@angular/compiler
Version:
Angular - the compiler library
908 lines • 153 kB
JavaScript
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import { flatten, sanitizeIdentifier } from '../../compile_metadata';
import { BindingForm, BuiltinFunctionCall, convertActionBinding, convertPropertyBinding } from '../../compiler_util/expression_converter';
import * as core from '../../core';
import { AstMemoryEfficientTransformer, FunctionCall, ImplicitReceiver, Interpolation, LiteralArray, LiteralPrimitive, PropertyRead } from '../../expression_parser/ast';
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';
import { WhitespaceVisitor } from '../../ml_parser/html_whitespaces';
import { DEFAULT_INTERPOLATION_CONFIG } from '../../ml_parser/interpolation_config';
import { splitNsName } from '../../ml_parser/tags';
import * as o from '../../output/output_ast';
import { DomElementSchemaRegistry } from '../../schema/dom_element_schema_registry';
import { CssSelector } from '../../selector';
import { BindingParser } from '../../template_parser/binding_parser';
import { error } from '../../util';
import * as t from '../r3_ast';
import { Identifiers as R3 } from '../r3_identifiers';
import { htmlAstToRender3Ast } from '../r3_template_transform';
import { parseStyle } from './styling';
import { CONTEXT_NAME, I18N_ATTR, I18N_ATTR_PREFIX, ID_SEPARATOR, IMPLICIT_REFERENCE, MEANING_SEPARATOR, REFERENCE_PREFIX, RENDER_FLAGS, asLiteral, invalid, mapToExpression, noop, trimTrailingNulls, unsupported } from './util';
function mapBindingToInstruction(type) {
switch (type) {
case 0 /* Property */:
return R3.elementProperty;
case 1 /* Attribute */:
return R3.elementAttribute;
case 2 /* Class */:
return R3.elementClassProp;
default:
return undefined;
}
}
// if (rf & flags) { .. }
export function renderFlagCheckIfStmt(flags, statements) {
return o.ifStmt(o.variable(RENDER_FLAGS).bitwiseAnd(o.literal(flags), null, false), statements);
}
export class TemplateDefinitionBuilder {
constructor(constantPool, contextParameter, parentBindingScope, level = 0, contextName, templateName, viewQueries, directiveMatcher, directives, pipeTypeByName, pipes, _namespace) {
this.constantPool = constantPool;
this.contextParameter = contextParameter;
this.level = level;
this.contextName = contextName;
this.templateName = templateName;
this.viewQueries = viewQueries;
this.directiveMatcher = directiveMatcher;
this.directives = directives;
this.pipeTypeByName = pipeTypeByName;
this.pipes = pipes;
this._namespace = _namespace;
this._dataIndex = 0;
this._bindingContext = 0;
this._prefixCode = [];
this._creationCode = [];
this._variableCode = [];
this._bindingCode = [];
this._postfixCode = [];
this._unsupported = unsupported;
// Whether we are inside a translatable element (`<p i18n>... somewhere here ... </p>)
this._inI18nSection = false;
this._i18nSectionIndex = -1;
// Maps of placeholder to node indexes for each of the i18n section
this._phToNodeIdxes = [{}];
// Number of slots to reserve for pureFunctions
this._pureFunctionSlots = 0;
// These should be handled in the template or element directly.
this.visitReference = invalid;
this.visitVariable = invalid;
this.visitTextAttribute = invalid;
this.visitBoundAttribute = invalid;
this.visitBoundEvent = invalid;
// view queries can take up space in data and allocation happens earlier (in the "viewQuery"
// function)
this._dataIndex = viewQueries.length;
this._bindingScope =
parentBindingScope.nestedScope((lhsVar, expression) => {
this._bindingCode.push(lhsVar.set(expression).toDeclStmt(o.INFERRED_TYPE, [o.StmtModifier.Final]));
});
this._valueConverter = new ValueConverter(constantPool, () => this.allocateDataSlot(), (numSlots) => this._pureFunctionSlots += numSlots, (name, localName, slot, value) => {
const pipeType = pipeTypeByName.get(name);
if (pipeType) {
this.pipes.add(pipeType);
}
this._bindingScope.set(localName, value);
this._creationCode.push(o.importExpr(R3.pipe).callFn([o.literal(slot), o.literal(name)]).toStmt());
});
}
buildTemplateFunction(nodes, variables, hasNgContent = false, ngContentSelectors = []) {
if (this._namespace !== R3.namespaceHTML) {
this.instruction(this._creationCode, null, this._namespace);
}
// Create variable bindings
for (const variable of variables) {
const variableName = variable.name;
const expression = o.variable(this.contextParameter).prop(variable.value || IMPLICIT_REFERENCE);
const scopedName = this._bindingScope.freshReferenceName();
// Add the reference to the local scope.
this._bindingScope.set(variableName, o.variable(variableName + scopedName), expression);
}
// Output a `ProjectionDef` instruction when some `<ng-content>` are present
if (hasNgContent) {
const parameters = [];
// Only selectors with a non-default value are generated
if (ngContentSelectors.length > 1) {
const r3Selectors = ngContentSelectors.map(s => core.parseSelectorToR3Selector(s));
// `projectionDef` needs both the parsed and raw value of the selectors
const parsed = this.constantPool.getConstLiteral(asLiteral(r3Selectors), true);
const unParsed = this.constantPool.getConstLiteral(asLiteral(ngContentSelectors), true);
parameters.push(parsed, unParsed);
}
this.instruction(this._creationCode, null, R3.projectionDef, ...parameters);
}
t.visitAll(this, nodes);
if (this._pureFunctionSlots > 0) {
this.instruction(this._creationCode, null, R3.reserveSlots, o.literal(this._pureFunctionSlots));
}
const creationCode = this._creationCode.length > 0 ?
[renderFlagCheckIfStmt(1 /* Create */, this._creationCode)] :
[];
const updateCode = this._bindingCode.length > 0 ?
[renderFlagCheckIfStmt(2 /* Update */, this._bindingCode)] :
[];
// 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) {
const scopedName = this._bindingScope.freshReferenceName();
const phMap = o.variable(scopedName)
.set(mapToExpression(phToNodeIdx, true))
.toDeclStmt(o.INFERRED_TYPE, [o.StmtModifier.Final]);
this._prefixCode.push(phMap);
}
}
return o.fn([new o.FnParam(RENDER_FLAGS, o.NUMBER_TYPE), new o.FnParam(this.contextParameter, null)], [
// Temporary variable declarations for query refresh (i.e. let _t: any;)
...this._prefixCode,
// Creating mode (i.e. if (rf & RenderFlags.Create) { ... })
...creationCode,
// Temporary variable declarations for local refs (i.e. const tmp = ld(1) as any)
...this._variableCode,
// Binding and refresh mode (i.e. if (rf & RenderFlags.Update) {...})
...updateCode,
// Nested templates (i.e. function CompTemplate() {})
...this._postfixCode
], o.INFERRED_TYPE, null, this.templateName);
}
// LocalResolver
getLocal(name) { return this._bindingScope.get(name); }
visitContent(ngContent) {
const slot = this.allocateDataSlot();
const selectorIndex = ngContent.selectorIndex;
const parameters = [o.literal(slot)];
const attributeAsList = [];
ngContent.attributes.forEach((attribute) => {
const name = attribute.name;
if (name !== 'select') {
attributeAsList.push(name, attribute.value);
}
});
if (attributeAsList.length > 0) {
parameters.push(o.literal(selectorIndex), asLiteral(attributeAsList));
}
else if (selectorIndex !== 0) {
parameters.push(o.literal(selectorIndex));
}
this.instruction(this._creationCode, ngContent.sourceSpan, R3.projection, ...parameters);
}
getNamespaceInstruction(namespaceKey) {
switch (namespaceKey) {
case 'math':
return R3.namespaceMathML;
case 'svg':
return R3.namespaceSVG;
default:
return R3.namespaceHTML;
}
}
addNamespaceInstruction(nsInstruction, element) {
this._namespace = nsInstruction;
this.instruction(this._creationCode, element.sourceSpan, nsInstruction);
}
visitElement(element) {
const elementIndex = this.allocateDataSlot();
const referenceDataSlots = new Map();
const wasInI18nSection = this._inI18nSection;
const outputAttrs = {};
const attrI18nMetas = {};
let i18nMeta = '';
const [namespaceKey, elementName] = splitNsName(element.name);
// 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
for (const attr of element.attributes) {
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;
}
}
// Match directives on non i18n attributes
if (this.directiveMatcher) {
const selector = createCssSelector(element.name, outputAttrs);
this.directiveMatcher.match(selector, (sel, staticType) => { this.directives.add(staticType); });
}
// Element creation mode
const parameters = [
o.literal(elementIndex),
o.literal(elementName),
];
// Add the attributes
const i18nMessages = [];
const attributes = [];
const initialStyleDeclarations = [];
const initialClassDeclarations = [];
const styleInputs = [];
const classInputs = [];
const allOtherInputs = [];
element.inputs.forEach((input) => {
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 0 /* 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 3 /* Style */:
styleInputs.push(input);
break;
case 2 /* Class */:
classInputs.push(input);
break;
default:
allOtherInputs.push(input);
break;
}
});
let currStyleIndex = 0;
let currClassIndex = 0;
let staticStylesMap = null;
let staticClassesMap = null;
const stylesIndexMap = {};
const classesIndexMap = {};
Object.getOwnPropertyNames(outputAttrs).forEach(name => {
const value = outputAttrs[name];
if (name == 'style') {
staticStylesMap = parseStyle(value);
Object.keys(staticStylesMap).forEach(prop => { stylesIndexMap[prop] = currStyleIndex++; });
}
else if (name == 'class') {
staticClassesMap = {};
value.split(/\s+/g).forEach(className => {
classesIndexMap[className] = currClassIndex++;
staticClassesMap[className] = true;
});
}
else {
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));
}
}
});
let hasMapBasedStyling = false;
for (let i = 0; i < styleInputs.length; i++) {
const input = styleInputs[i];
const isMapBasedStyleBinding = i === 0 && input.name === 'style';
if (isMapBasedStyleBinding) {
hasMapBasedStyling = true;
}
else if (!stylesIndexMap.hasOwnProperty(input.name)) {
stylesIndexMap[input.name] = currStyleIndex++;
}
}
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++;
}
}
// 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;
// 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 => {
useDefaultStyleSanitizer = useDefaultStyleSanitizer || isStyleSanitizable(prop);
initialStyleDeclarations.push(o.literal(prop));
});
if (staticStylesMap) {
initialStyleDeclarations.push(o.literal(1 /* VALUES_MODE */));
Object.keys(staticStylesMap).forEach(prop => {
initialStyleDeclarations.push(o.literal(prop));
const value = staticStylesMap[prop];
initialStyleDeclarations.push(o.literal(value));
});
}
Object.keys(classesIndexMap).forEach(prop => {
initialClassDeclarations.push(o.literal(prop));
});
if (staticClassesMap) {
initialClassDeclarations.push(o.literal(1 /* 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;
const attrArg = attributes.length > 0 ?
this.constantPool.getConstLiteral(o.literalArr(attributes), true) :
o.TYPED_NULL_EXPR;
parameters.push(attrArg);
if (element.references && element.references.length > 0) {
const references = flatten(element.references.map(reference => {
const slot = this.allocateDataSlot();
referenceDataSlots.set(reference.name, slot);
// Generate the update temporary.
const variableName = this._bindingScope.freshReferenceName();
this._variableCode.push(o.variable(variableName, o.INFERRED_TYPE)
.set(o.importExpr(R3.load).callFn([o.literal(slot)]))
.toDeclStmt(o.INFERRED_TYPE, [o.StmtModifier.Final]));
this._bindingScope.set(reference.name, o.variable(variableName));
return [reference.name, reference.value];
}));
parameters.push(this.constantPool.getConstLiteral(asLiteral(references), true));
}
else {
parameters.push(o.TYPED_NULL_EXPR);
}
// Generate the instruction create element instruction
if (i18nMessages.length > 0) {
this._creationCode.push(...i18nMessages);
}
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);
}
const implicit = o.variable(CONTEXT_NAME);
const createSelfClosingInstruction = !hasStylingInstructions && element.children.length === 0 && element.outputs.length === 0;
if (createSelfClosingInstruction) {
this.instruction(this._creationCode, element.sourceSpan, R3.element, ...trimTrailingNulls(parameters));
}
else {
// Generate the instruction create element instruction
if (i18nMessages.length > 0) {
this._creationCode.push(...i18nMessages);
}
this.instruction(this._creationCode, element.sourceSpan, R3.elementStart, ...trimTrailingNulls(parameters));
// initial styling for static style="..." attributes
if (hasStylingInstructions) {
const paramsList = [];
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);
}
if (initialStyleDeclarations.length) {
// the template compiler handles initial style (e.g. style="foo") values
// 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));
}
else if (useDefaultStyleSanitizer) {
// no point in having an extra `null` value unless there are follow-up params
paramsList.push(o.NULL_EXPR);
}
if (useDefaultStyleSanitizer) {
paramsList.push(o.importExpr(R3.defaultStyleSanitizer));
}
this._creationCode.push(o.importExpr(R3.elementStyling).callFn(paramsList).toStmt());
}
// Generate Listeners (outputs)
element.outputs.forEach((outputAst) => {
const elName = sanitizeIdentifier(element.name);
const evName = sanitizeIdentifier(outputAst.name);
const functionName = `${this.templateName}_${elName}_${evName}_listener`;
const localVars = [];
const bindingScope = this._bindingScope.nestedScope((lhsVar, rhsExpression) => {
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);
this.instruction(this._creationCode, outputAst.sourceSpan, R3.listener, o.literal(outputAst.name), handler);
});
}
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 = [];
if (mapBasedClassInput) {
params.push(this.convertPropertyBinding(implicit, mapBasedClassInput.value, true));
}
else if (mapBasedStyleInput) {
params.push(o.NULL_EXPR);
}
if (mapBasedStyleInput) {
params.push(this.convertPropertyBinding(implicit, mapBasedStyleInput.value, true));
}
this.instruction(this._bindingCode, stylingInput.sourceSpan, R3.elementStylingMap, indexLiteral, ...params);
}
let lastInputCommand = null;
if (styleInputs.length) {
let i = mapBasedStyleInput ? 1 : 0;
for (i; i < styleInputs.length; i++) {
const input = styleInputs[i];
const convertedBinding = this.convertPropertyBinding(implicit, input.value, true);
const params = [convertedBinding];
const sanitizationRef = resolveSanitizationFn(input, input.securityContext);
if (sanitizationRef) {
params.push(sanitizationRef);
}
const key = input.name;
const styleIndex = stylesIndexMap[key];
this.instruction(this._bindingCode, input.sourceSpan, R3.elementStyleProp, indexLiteral, o.literal(styleIndex), ...params);
}
lastInputCommand = styleInputs[styleInputs.length - 1];
}
if (classInputs.length) {
let i = mapBasedClassInput ? 1 : 0;
for (i; i < classInputs.length; i++) {
const input = classInputs[i];
const convertedBinding = this.convertPropertyBinding(implicit, input.value, true);
const params = [convertedBinding];
const sanitizationRef = resolveSanitizationFn(input, input.securityContext);
if (sanitizationRef) {
params.push(sanitizationRef);
}
const key = input.name;
const classIndex = classesIndexMap[key];
this.instruction(this._bindingCode, input.sourceSpan, R3.elementClassProp, indexLiteral, o.literal(classIndex), ...params);
}
lastInputCommand = classInputs[classInputs.length - 1];
}
this.instruction(this._bindingCode, lastInputCommand.sourceSpan, R3.elementStylingApply, indexLiteral);
}
// Generate element input bindings
allOtherInputs.forEach((input) => {
if (input.type === 4 /* Animation */) {
console.error('warning: animation bindings not yet supported');
return;
}
const convertedBinding = this.convertPropertyBinding(implicit, input.value);
const instruction = mapBindingToInstruction(input.type);
if (instruction) {
const params = [convertedBinding];
const sanitizationRef = resolveSanitizationFn(input, input.securityContext);
if (sanitizationRef) {
params.push(sanitizationRef);
}
// TODO(chuckj): runtime: security context?
this.instruction(this._bindingCode, input.sourceSpan, instruction, o.literal(elementIndex), o.literal(input.name), ...params);
}
else {
this._unsupported(`binding type ${input.type}`);
}
});
// Traverse element child nodes
if (this._inI18nSection && element.children.length == 1 &&
element.children[0] instanceof t.Text) {
const text = element.children[0];
this.visitSingleI18nTextChild(text, i18nMeta);
}
else {
t.visitAll(this, element.children);
}
if (!createSelfClosingInstruction) {
// Finish element construction mode.
this.instruction(this._creationCode, element.endSourceSpan || element.sourceSpan, R3.elementEnd);
}
// Restore the state before exiting this node
this._inI18nSection = wasInI18nSection;
}
visitTemplate(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].name);
}
const contextName = elName ? `${this.contextName}_${elName}` : '';
const templateName = contextName ? `${contextName}_Template_${templateIndex}` : `Template_${templateIndex}`;
const templateContext = `ctx${this.level}`;
const parameters = [
o.literal(templateIndex),
o.variable(templateName),
o.TYPED_NULL_EXPR,
];
const attributeNames = [];
const attributeMap = {};
template.attributes.forEach(a => {
attributeNames.push(asLiteral(a.name), asLiteral(''));
attributeMap[a.name] = a.value;
});
// Match directives on template attributes
if (this.directiveMatcher) {
const selector = createCssSelector('ng-template', attributeMap);
this.directiveMatcher.match(selector, (cssSelector, staticType) => { this.directives.add(staticType); });
}
if (attributeNames.length) {
parameters.push(this.constantPool.getConstLiteral(o.literalArr(attributeNames), true));
}
// e.g. C(1, C1Template)
this.instruction(this._creationCode, template.sourceSpan, R3.containerCreate, ...trimTrailingNulls(parameters));
// e.g. p(1, 'forOf', ɵb(ctx.items));
const context = o.variable(CONTEXT_NAME);
template.inputs.forEach(input => {
const convertedBinding = this.convertPropertyBinding(context, input.value);
this.instruction(this._bindingCode, template.sourceSpan, R3.elementProperty, o.literal(templateIndex), o.literal(input.name), convertedBinding);
});
// Create the template function
const templateVisitor = new TemplateDefinitionBuilder(this.constantPool, templateContext, this._bindingScope, this.level + 1, contextName, templateName, [], this.directiveMatcher, this.directives, this.pipeTypeByName, this.pipes, this._namespace);
const templateFunctionExpr = templateVisitor.buildTemplateFunction(template.children, template.variables);
this._postfixCode.push(templateFunctionExpr.toDeclStmt(templateName, null));
}
visitBoundText(text) {
const nodeIndex = this.allocateDataSlot();
this.instruction(this._creationCode, text.sourceSpan, R3.text, o.literal(nodeIndex));
this.instruction(this._bindingCode, text.sourceSpan, R3.textBinding, o.literal(nodeIndex), this.convertPropertyBinding(o.variable(CONTEXT_NAME), text.value));
}
visitText(text) {
this.instruction(this._creationCode, text.sourceSpan, R3.text, o.literal(this.allocateDataSlot()), o.literal(text.value));
}
// 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);
// ```
visitSingleI18nTextChild(text, i18nMeta) {
const meta = parseI18nMeta(i18nMeta);
const variable = this.constantPool.getTranslation(text.value, meta);
this.instruction(this._creationCode, text.sourceSpan, R3.text, o.literal(this.allocateDataSlot()), variable);
}
allocateDataSlot() { return this._dataIndex++; }
bindingContext() { return `${this._bindingContext++}`; }
instruction(statements, span, reference, ...params) {
statements.push(o.importExpr(reference, null, span).callFn(params, span).toStmt());
}
convertPropertyBinding(implicit, value, skipBindFn) {
const pipesConvertedValue = value.visit(this._valueConverter);
if (pipesConvertedValue instanceof Interpolation) {
const convertedPropertyBinding = convertPropertyBinding(this, implicit, pipesConvertedValue, this.bindingContext(), BindingForm.TrySimple, interpolate);
this._bindingCode.push(...convertedPropertyBinding.stmts);
return convertedPropertyBinding.currValExpr;
}
else {
const convertedPropertyBinding = convertPropertyBinding(this, implicit, pipesConvertedValue, this.bindingContext(), BindingForm.TrySimple, () => error('Unexpected interpolation'));
this._bindingCode.push(...convertedPropertyBinding.stmts);
const valExpr = convertedPropertyBinding.currValExpr;
return skipBindFn ? valExpr : o.importExpr(R3.bind).callFn([valExpr]);
}
}
}
class ValueConverter extends AstMemoryEfficientTransformer {
constructor(constantPool, allocateSlot, allocatePureFunctionSlots, definePipe) {
super();
this.constantPool = constantPool;
this.allocateSlot = allocateSlot;
this.allocatePureFunctionSlots = allocatePureFunctionSlots;
this.definePipe = definePipe;
}
// AstMemoryEfficientTransformer
visitPipe(pipe, context) {
// Allocate a slot to create the pipe
const slot = this.allocateSlot();
const slotPseudoLocal = `PIPE:${slot}`;
// Allocate one slot for the result plus one slot per pipe argument
const pureFunctionSlot = this.allocatePureFunctionSlots(2 + pipe.args.length);
const target = new PropertyRead(pipe.span, new ImplicitReceiver(pipe.span), slotPseudoLocal);
const { identifier, isVarLength } = pipeBindingCallInfo(pipe.args);
this.definePipe(pipe.name, slotPseudoLocal, slot, o.importExpr(identifier));
const args = [pipe.exp, ...pipe.args];
const convertedArgs = isVarLength ? this.visitAll([new LiteralArray(pipe.span, args)]) : this.visitAll(args);
return new FunctionCall(pipe.span, target, [
new LiteralPrimitive(pipe.span, slot),
new LiteralPrimitive(pipe.span, pureFunctionSlot),
...convertedArgs,
]);
}
visitLiteralArray(array, context) {
return new BuiltinFunctionCall(array.span, this.visitAll(array.expressions), values => {
// 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.literalArr(values);
return values.every(a => a.isConstant()) ?
this.constantPool.getConstLiteral(literal, true) :
getLiteralFactory(this.constantPool, literal, this.allocatePureFunctionSlots);
});
}
visitLiteralMap(map, context) {
return new BuiltinFunctionCall(map.span, this.visitAll(map.values), values => {
// 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((value, index) => ({ key: map.keys[index].key, value, quoted: map.keys[index].quoted })));
return values.every(a => a.isConstant()) ?
this.constantPool.getConstLiteral(literal, true) :
getLiteralFactory(this.constantPool, literal, this.allocatePureFunctionSlots);
});
}
}
// Pipes always have at least one parameter, the value they operate on
const pipeBindingIdentifiers = [R3.pipeBind1, R3.pipeBind2, R3.pipeBind3, R3.pipeBind4];
function pipeBindingCallInfo(args) {
const identifier = pipeBindingIdentifiers[args.length];
return {
identifier: identifier || R3.pipeBindV,
isVarLength: !identifier,
};
}
const pureFunctionIdentifiers = [
R3.pureFunction0, R3.pureFunction1, R3.pureFunction2, R3.pureFunction3, R3.pureFunction4,
R3.pureFunction5, R3.pureFunction6, R3.pureFunction7, R3.pureFunction8
];
function pureFunctionCallInfo(args) {
const identifier = pureFunctionIdentifiers[args.length];
return {
identifier: identifier || R3.pureFunctionV,
isVarLength: !identifier,
};
}
function getLiteralFactory(constantPool, literal, allocateSlots) {
const { literalFactory, literalFactoryArguments } = constantPool.getLiteralFactory(literal);
// Allocate 1 slot for the result plus 1 per argument
const startSlot = allocateSlots(1 + literalFactoryArguments.length);
literalFactoryArguments.length > 0 || error(`Expected arguments to a literal factory function`);
const { identifier, isVarLength } = pureFunctionCallInfo(literalFactoryArguments);
// Literal factories are pure functions that only need to be re-invoked when the parameters
// change.
const args = [
o.literal(startSlot),
literalFactory,
];
if (isVarLength) {
args.push(o.literalArr(literalFactoryArguments));
}
else {
args.push(...literalFactoryArguments);
}
return o.importExpr(identifier).callFn(args);
}
export class BindingScope {
constructor(parent = null, declareLocalVarCallback = noop) {
this.parent = parent;
this.declareLocalVarCallback = declareLocalVarCallback;
/**
* 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.
*/
this.map = new Map();
this.referenceNameIndex = 0;
}
static get ROOT_SCOPE() {
if (!BindingScope._ROOT_SCOPE) {
BindingScope._ROOT_SCOPE = new BindingScope().set('$event', o.variable('$event'));
}
return BindingScope._ROOT_SCOPE;
}
get(name) {
let current = this;
while (current) {
let value = current.map.get(name);
if (value != null) {
if (current !== this) {
// make a local copy and reset the `declared` state.
value = { lhs: value.lhs, rhs: value.rhs, declared: false };
// Cache the value locally.
this.map.set(name, value);
}
if (value.rhs && !value.declared) {
// if it is first time we are referencing the variable in the scope
// than invoke the callback to insert variable declaration.
this.declareLocalVarCallback(value.lhs, value.rhs);
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.
*/
set(name, lhs, rhs) {
!this.map.has(name) ||
error(`The name ${name} is already defined in scope to be ${this.map.get(name)}`);
this.map.set(name, { lhs: lhs, rhs: rhs, declared: false });
return this;
}
getLocal(name) { return this.get(name); }
nestedScope(declareCallback) {
return new BindingScope(this, declareCallback);
}
freshReferenceName() {
let current = 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;
}
}
/**
* Creates a `CssSelector` given a tag name and a map of attributes
*/
function createCssSelector(tag, attributes) {
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));
}
});
return cssSelector;
}
// Parse i18n metas like:
// - "@@id",
// - "description[@@id]",
// - "meaning|description[@@id]"
function parseI18nMeta(i18n) {
let meaning;
let description;
let id;
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;
[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 };
}
function interpolate(args) {
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)]);
}
/**
* 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
*/
export function parseTemplate(template, templateUrl, options = {}) {
const bindingParser = makeBindingParser();
const htmlParser = new HtmlParser();
const parseResult = htmlParser.parse(template, templateUrl);
if (parseResult.errors && parseResult.errors.length > 0) {
return { errors: parseResult.errors, nodes: [], hasNgContent: false, ngContentSelectors: [] };
}
let rootNodes = parseResult.rootNodes;
if (!options.preserveWhitespaces) {
rootNodes = html.visitAll(new WhitespaceVisitor(), rootNodes);
}
const { nodes, hasNgContent, ngContentSelectors, errors } = htmlAstToRender3Ast(rootNodes, bindingParser);
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() {
return new BindingParser(new Parser(new Lexer()), DEFAULT_INTERPOLATION_CONFIG, new DomElementSchemaRegistry(), null, []);
}
function isClassBinding(input) {
return input.name == 'className' || input.name == 'class';
}
function resolveSanitizationFn(input, context) {
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 === 1 /* 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) {
switch (prop) {
case 'background-image':
case 'background':
case 'border-image':
case 'filter':
case 'list-style':
case 'list-style-image':
return true;
}
return false;
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidGVtcGxhdGUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi8uLi8uLi8uLi9wYWNrYWdlcy9jb21waWxlci9zcmMvcmVuZGVyMy92aWV3L3RlbXBsYXRlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOzs7Ozs7R0FNRztBQUVILE9BQU8sRUFBQyxPQUFPLEVBQUUsa0JBQWtCLEVBQUMsTUFBTSx3QkFBd0IsQ0FBQztBQUVuRSxPQUFPLEVBQUMsV0FBVyxFQUFFLG1CQUFtQixFQUFpQixvQkFBb0IsRUFBRSxzQkFBc0IsRUFBQyxNQUFNLDBDQUEwQyxDQUFDO0FBRXZKLE9BQU8sS0FBSyxJQUFJLE1BQU0sWUFBWSxDQUFDO0FBQ25DLE9BQU8sRUFBTSw2QkFBNkIsRUFBNEIsWUFBWSxFQUFFLGdCQUFnQixFQUFFLGFBQWEsRUFBRSxZQUFZLEVBQWMsZ0JBQWdCLEVBQUUsWUFBWSxFQUFDLE1BQU0sNkJBQTZCLENBQUM7QUFDbE4sT0FBTyxFQUFDLEtBQUssRUFBQyxNQUFNLCtCQUErQixDQUFDO0FBQ3BELE9BQU8sRUFBQyxNQUFNLEVBQUMsTUFBTSxnQ0FBZ0MsQ0FBQztBQUN0RCxPQUFPLEtBQUssSUFBSSxNQUFNLHFCQUFxQixDQUFDO0FBQzVDLE9BQU8sRUFBQyxVQUFVLEVBQUMsTUFBTSw2QkFBNkIsQ0FBQztBQUN2RCxPQUFPLEVBQUMsaUJBQWlCLEVBQUMsTUFBTSxrQ0FBa0MsQ0FBQztBQUNuRSxPQUFPLEVBQUMsNEJBQTRCLEVBQUMsTUFBTSxzQ0FBc0MsQ0FBQztBQUNsRixPQUFPLEVBQUMsV0FBVyxFQUFDLE1BQU0sc0JBQXNCLENBQUM7QUFDakQsT0FBTyxLQUFLLENBQUMsTUFBTSx5QkFBeUIsQ0FBQztBQUU3QyxPQUFPLEVBQUMsd0JBQXdCLEVBQUMsTUFBTSwwQ0FBMEMsQ0FBQztBQUNsRixPQUFPLEVBQUMsV0FBVyxFQUFrQixNQUFNLGdCQUFnQixDQUFDO0FBQzVELE9BQU8sRUFBQyxhQUFh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