refactor(ivy): move classMap interpolation logic internally (#31211)

Adds the new `classMapInterpolate1` through `classMapInterpolate8` instructions which handle interpolations inside the `class` attribute and moves the interpolation logic internally. This allows us to remove the `interpolationX` instructions in a follow-up PR.

These changes also add an error if an interpolation is encountered inside a `style` tag (e.g. `style="width: {{value}}"`). Up until now this would actually generate valid instructions, because `styleMap` goes through the same code path as `classMap` which does support interpolation. At runtime, however, `styleMap` would set invalid styles that look like `<div style="0:w;1:i;2:d;3:t;4:h;5::;7:1;">`. In `ViewEngine` interpolations inside `style` weren't supported either, however there we'd output invalid styles like `<div style="unsafe">`, even if the content was trusted.

PR Close #31211
This commit is contained in:
crisbeto
2019-06-27 18:57:09 +02:00
committed by Alex Rickabaugh
parent dca713c087
commit 02491a6ce8
15 changed files with 1469 additions and 89 deletions

View File

@ -36,8 +36,8 @@ import {I18nContext} from './i18n/context';
import {I18nMetaVisitor} from './i18n/meta';
import {getSerializedI18nContent} from './i18n/serializer';
import {I18N_ICU_MAPPING_PREFIX, TRANSLATION_PREFIX, assembleBoundTextPlaceholders, assembleI18nBoundString, formatI18nPlaceholderName, getTranslationConstPrefix, getTranslationDeclStmts, icuFromI18nMessage, isI18nRootNode, isSingleI18nIcu, metaFromI18nMessage, placeholdersToParams, wrapI18nPlaceholder} from './i18n/util';
import {Instruction, StylingBuilder} from './styling_builder';
import {CONTEXT_NAME, IMPLICIT_REFERENCE, NON_BINDABLE_ATTR, REFERENCE_PREFIX, RENDER_FLAGS, asLiteral, chainedInstruction, getAttrsForDirectiveMatching, invalid, trimTrailingNulls, unsupported} from './util';
import {StylingBuilder, StylingInstruction} from './styling_builder';
import {CONTEXT_NAME, IMPLICIT_REFERENCE, NON_BINDABLE_ATTR, REFERENCE_PREFIX, RENDER_FLAGS, asLiteral, chainedInstruction, getAttrsForDirectiveMatching, getInterpolationArgsLength, invalid, trimTrailingNulls, unsupported} from './util';
@ -1067,14 +1067,21 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
}
private processStylingInstruction(
elementIndex: number, instruction: Instruction|null, createMode: boolean) {
elementIndex: number, instruction: StylingInstruction|null, createMode: boolean) {
if (instruction) {
const paramsFn = () => instruction.buildParams(value => this.convertPropertyBinding(value));
if (createMode) {
this.creationInstruction(instruction.sourceSpan, instruction.reference, paramsFn);
this.creationInstruction(instruction.sourceSpan, instruction.reference, () => {
return instruction.params(value => this.convertPropertyBinding(value)) as o.Expression[];
});
} else {
this.updateInstruction(
elementIndex, instruction.sourceSpan, instruction.reference, paramsFn);
this.updateInstruction(elementIndex, instruction.sourceSpan, instruction.reference, () => {
return instruction
.params(value => {
return (instruction.supportsInterpolation && value instanceof Interpolation) ?
this.getUpdateInstructionArguments(value) :
this.convertPropertyBinding(value);
}) as o.Expression[];
});
}
}
}
@ -1150,12 +1157,9 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
}
private convertPropertyBinding(value: AST): o.Expression {
const interpolationFn =
value instanceof Interpolation ? interpolate : () => error('Unexpected interpolation');
const convertedPropertyBinding = convertPropertyBinding(
this, this.getImplicitReceiverExpr(), value, this.bindingContext(), BindingForm.TrySimple,
interpolationFn);
() => error('Unexpected interpolation'));
const valExpr = convertedPropertyBinding.currValExpr;
this._tempVariables.push(...convertedPropertyBinding.stmts);
@ -1751,31 +1755,6 @@ function getNgProjectAsLiteral(attribute: t.TextAttribute): o.Expression[] {
return [o.literal(core.AttributeMarker.ProjectAs), asLiteral(parsedR3Selector)];
}
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)]);
}
/**
* Gets the instruction to generate for an interpolated property
* @param interpolation An Interpolation AST
@ -1861,22 +1840,6 @@ function getTextInterpolationExpression(interpolation: Interpolation): o.Externa
}
}
/**
* Gets the number of arguments expected to be passed to a generated instruction in the case of
* interpolation instructions.
* @param interpolation An interpolation ast
*/
function getInterpolationArgsLength(interpolation: Interpolation) {
const {expressions, strings} = interpolation;
if (expressions.length === 1 && strings.length === 2 && strings[0] === '' && strings[1] === '') {
// If the interpolation has one interpolated value, but the prefix and suffix are both empty
// strings, we only pass one argument, to a special instruction like `propertyInterpolate` or
// `textInterpolate`.
return 1;
} else {
return expressions.length + strings.length;
}
}
/**
* Options that can be used to modify how a template is parsed by `parseTemplate()`.
*/