[FIX] compiler: add support for #{...} in string interpolation

This commit is contained in:
Géry Debongnie
2022-06-07 17:25:23 +02:00
committed by Sam Degueldre
parent a3111eb9ca
commit 385e118e58
7 changed files with 68 additions and 14 deletions
+3 -2
View File
@@ -290,10 +290,11 @@ If an expression evaluates to a falsy value, it will not be set at all:
It is sometimes convenient to format an attribute with string interpolation. In It is sometimes convenient to format an attribute with string interpolation. In
that case, the `t-attf-` directive can be used. It is useful when we need to mix that case, the `t-attf-` directive can be used. It is useful when we need to mix
literal and dynamic elements, such as css classes. literal and dynamic elements, such as css classes. The dynamic elements can be
specified with either `{{...}}` or `#{...}`:
```xml ```xml
<div t-attf-foo="a {{value1}} is {{value2}} of {{value3}} ]"/> <div t-attf-foo="a {{value1}} is #{value2} of {{value3}} ]"/>
<!-- result if values are set to 1,2 and 3: <div foo="a 0 is 1 of 2 ]"></div> --> <!-- result if values are set to 1,2 and 3: <div foo="a 0 is 1 of 2 ]"></div> -->
``` ```
+9 -6
View File
@@ -1,4 +1,10 @@
import { compileExpr, compileExprToArray, interpolate, INTERP_REGEXP } from "./inline_expressions"; import {
compileExpr,
compileExprToArray,
interpolate,
INTERP_REGEXP,
replaceDynamicParts,
} from "./inline_expressions";
import { import {
AST, AST,
ASTComment, ASTComment,
@@ -606,11 +612,8 @@ export class CodeGenerator {
this.target.hasRef = true; this.target.hasRef = true;
const isDynamic = INTERP_REGEXP.test(ast.ref); const isDynamic = INTERP_REGEXP.test(ast.ref);
if (isDynamic) { if (isDynamic) {
const str = ast.ref.replace( const str = replaceDynamicParts(ast.ref, (expr) => this.captureExpression(expr, true));
INTERP_REGEXP, const idx = block!.insertData(`(el) => refs[${str}] = el`, "ref");
(expr) => "${" + this.captureExpression(expr.slice(2, -2), true) + "}"
);
const idx = block!.insertData(`(el) => refs[\`${str}\`] = el`, "ref");
attrs["block-ref"] = String(idx); attrs["block-ref"] = String(idx);
} else { } else {
let name = ast.ref; let name = ast.ref;
+10 -5
View File
@@ -355,15 +355,20 @@ export function compileExpr(expr: string): string {
.join(""); .join("");
} }
export const INTERP_REGEXP = /\{\{.*?\}\}/g; export const INTERP_REGEXP = /\{\{.*?\}\}|\#\{.*?\}/g;
const INTERP_GROUP_REGEXP = /\{\{.*?\}\}/g;
export function interpolate(s: string): string { export function replaceDynamicParts(s: string, replacer: (s: string) => string) {
let matches = s.match(INTERP_REGEXP); let matches = s.match(INTERP_REGEXP);
if (matches && matches[0].length === s.length) { if (matches && matches[0].length === s.length) {
return `(${compileExpr(s.slice(2, -2))})`; return `(${replacer(s.slice(2, matches[0][0] === "{" ? -2 : -1))})`;
} }
let r = s.replace(INTERP_GROUP_REGEXP, (s) => "${" + compileExpr(s.slice(2, -2)) + "}"); let r = s.replace(
INTERP_REGEXP,
(s) => "${" + replacer(s.slice(2, s[0] === "{" ? -2 : -1)) + "}"
);
return "`" + r + "`"; return "`" + r + "`";
} }
export function interpolate(s: string): string {
return replaceDynamicParts(s, compileExpr);
}
@@ -387,6 +387,20 @@ exports[`attributes static attributes with dashes 1`] = `
}" }"
`; `;
exports[`attributes string interpolation, alternate syntax 1`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let block1 = createBlock(\`<div block-attribute-0=\\"foo\\"/>\`);
return function template(ctx, node, key = \\"\\") {
let attr1 = \`b\${ctx['value']}r\`;
return block1([attr1]);
}
}"
`;
exports[`attributes t-att-class and class should combine together 1`] = ` exports[`attributes t-att-class and class should combine together 1`] = `
"function anonymous(app, bdom, helpers "function anonymous(app, bdom, helpers
) { ) {
@@ -16,6 +16,22 @@ exports[`t-ref can get a dynamic ref on a node 1`] = `
}" }"
`; `;
exports[`t-ref can get a dynamic ref on a node, alternate syntax 1`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let block1 = createBlock(\`<div><span block-ref=\\"0\\"/></div>\`);
return function template(ctx, node, key = \\"\\") {
const refs = ctx.__owl__.refs;
const v1 = ctx['id'];
let ref1 = (el) => refs[\`myspan\${v1}\`] = el;
return block1([ref1]);
}
}"
`;
exports[`t-ref can get a ref on a node 1`] = ` exports[`t-ref can get a ref on a node 1`] = `
"function anonymous(app, bdom, helpers "function anonymous(app, bdom, helpers
) { ) {
@@ -99,7 +115,7 @@ exports[`t-ref refs in a loop 1`] = `
const key1 = ctx['item']; const key1 = ctx['item'];
const tKey_1 = ctx['item']; const tKey_1 = ctx['item'];
const v1 = ctx['item']; const v1 = ctx['item'];
let ref1 = (el) => refs[\`\${v1}\`] = el; let ref1 = (el) => refs[(v1)] = el;
let txt1 = ctx['item']; let txt1 = ctx['item'];
c_block2[i1] = withKey(block3([ref1, txt1]), tKey_1 + key1); c_block2[i1] = withKey(block3([ref1, txt1]), tKey_1 + key1);
} }
+6
View File
@@ -131,6 +131,12 @@ describe("attributes", () => {
expect(result).toBe(`<div foo="bar"></div>`); expect(result).toBe(`<div foo="bar"></div>`);
}); });
test("string interpolation, alternate syntax", () => {
const template = `<div t-attf-foo="b#{value}r"/>`;
const result = renderToString(template, { value: "a" });
expect(result).toBe(`<div foo="bar"></div>`);
});
test("t-attf-class", () => { test("t-attf-class", () => {
const template = `<div t-attf-class="hello"/>`; const template = `<div t-attf-class="hello"/>`;
const result = renderToString(template); const result = renderToString(template);
+9
View File
@@ -27,6 +27,15 @@ describe("t-ref", () => {
expect(refs.myspan3.tagName).toBe("SPAN"); expect(refs.myspan3.tagName).toBe("SPAN");
}); });
test("can get a dynamic ref on a node, alternate syntax", () => {
const template = `<div><span t-ref="myspan#{id}"/></div>`;
const refs: any = {};
const bdom = renderToBdom(template, { id: 3, __owl__: { refs } });
expect(refs).toEqual({});
mount(bdom, document.createElement("div"));
expect(refs.myspan3.tagName).toBe("SPAN");
});
test("refs in a loop", () => { test("refs in a loop", () => {
const template = `<div> const template = `<div>
<t t-foreach="items" t-as="item" t-key="item"> <t t-foreach="items" t-as="item" t-key="item">