From a1552117f927a6ea82a4ddba56f8e47de803bbb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A9ry=20Debongnie?= Date: Sun, 4 Jul 2021 09:27:31 +0200 Subject: [PATCH] [IMP] qweb: add support for template strings in inline expressions, such as t-esc or t-value. closes #746 --- src/qweb/expression_parser.ts | 18 +++++++- tests/qweb/__snapshots__/qweb.test.ts.snap | 49 ++++++++++++++++++++++ tests/qweb/qweb.test.ts | 15 +++++++ tests/qweb/qweb_expressions.test.ts | 6 +++ tools/playground/app.js | 2 +- 5 files changed, 88 insertions(+), 2 deletions(-) diff --git a/src/qweb/expression_parser.ts b/src/qweb/expression_parser.ts index 92e0fc3a..a1b5f779 100644 --- a/src/qweb/expression_parser.ts +++ b/src/qweb/expression_parser.ts @@ -57,6 +57,7 @@ type TKind = | "RIGHT_PAREN" | "COMMA" | "VALUE" + | "TEMPLATE_STRING" | "SYMBOL" | "OPERATOR" | "COLON"; @@ -67,6 +68,7 @@ interface Token { originalValue?: string; size?: number; varName?: string; + replace?: Function; } const STATIC_TOKEN_MAP: { [key: string]: TKind } = Object.assign(Object.create(null), { @@ -89,7 +91,7 @@ type Tokenizer = (expr: string) => Token | false; let tokenizeString: Tokenizer = function (expr) { let s = expr[0]; let start = s; - if (s !== "'" && s !== '"') { + if (s !== "'" && s !== '"' && s !== "`") { return false; } let i = 1; @@ -111,6 +113,17 @@ let tokenizeString: Tokenizer = function (expr) { throw new Error("Invalid expression"); } s += start; + if (start === "`") { + return { + type: "TEMPLATE_STRING", + value: s, + replace(replacer) { + return s.replace(/\$\{(.*?)\}/g, (match, group) => { + return "${" + replacer(group) + "}"; + }); + }, + }; + } return { type: "VALUE", value: s }; }; @@ -267,6 +280,9 @@ export function compileExprToArray(expr: string, scope: { [key: string]: QWebVar } } } + if (token.type === "TEMPLATE_STRING") { + token.value = token.replace((expr) => compileExpr(expr, scope)); + } if (nextToken && nextToken.type === "OPERATOR" && nextToken.value === "=>") { if (token.type === "RIGHT_PAREN") { let j = i - 1; diff --git a/tests/qweb/__snapshots__/qweb.test.ts.snap b/tests/qweb/__snapshots__/qweb.test.ts.snap index e061f6d8..4d1f07ee 100644 --- a/tests/qweb/__snapshots__/qweb.test.ts.snap +++ b/tests/qweb/__snapshots__/qweb.test.ts.snap @@ -1236,6 +1236,55 @@ exports[`static templates empty div 1`] = ` }" `; +exports[`static templates inline template string in t-esc 1`] = ` +"function anonymous(context, extra +) { + // Template name: \\"test\\" + let scope = Object.create(context); + let result; + let h = this.h; + let _1 = \`text\`; + if (_1 != null) { + let vn2 = {text: _1}; + result = vn2 + } + return result; +}" +`; + +exports[`static templates inline template string with content in t-esc 1`] = ` +"function anonymous(context, extra +) { + // Template name: \\"test\\" + let scope = Object.create(context); + let result; + let h = this.h; + scope.v = 1; + let _1 = \`text\${scope.v}\`; + if (_1 != null) { + let vn2 = {text: _1}; + result = vn2 + } + return result; +}" +`; + +exports[`static templates inline template string with variable in context 1`] = ` +"function anonymous(context, extra +) { + // Template name: \\"test\\" + let scope = Object.create(context); + let result; + let h = this.h; + let _1 = \`text \${scope['v']}\`; + if (_1 != null) { + let vn2 = {text: _1}; + result = vn2 + } + return result; +}" +`; + exports[`static templates properly handle comments 1`] = ` "function anonymous(context, extra ) { diff --git a/tests/qweb/qweb.test.ts b/tests/qweb/qweb.test.ts index aaac2895..2615e711 100644 --- a/tests/qweb/qweb.test.ts +++ b/tests/qweb/qweb.test.ts @@ -31,6 +31,21 @@ describe("static templates", () => { expect(renderToString(qweb, "test", { text: "hello vdom" })).toBe("hello vdom"); }); + test("inline template string in t-esc", () => { + qweb.addTemplate("test", ''); + expect(renderToString(qweb, "test")).toBe("text"); + }); + + test("inline template string with content in t-esc", () => { + qweb.addTemplate("test", ''); + expect(renderToString(qweb, "test")).toBe("text1"); + }); + + test("inline template string with variable in context", () => { + qweb.addTemplate("test", ''); + expect(renderToString(qweb, "test", { v: "from context" })).toBe("text from context"); + }); + test("simple string, with some dynamic value", () => { qweb.addTemplate("test", 'hello '); expect(renderToString(qweb, "test", { text: "vdom" })).toBe("hello vdom"); diff --git a/tests/qweb/qweb_expressions.test.ts b/tests/qweb/qweb_expressions.test.ts index 2a494e50..f47856a5 100644 --- a/tests/qweb/qweb_expressions.test.ts +++ b/tests/qweb/qweb_expressions.test.ts @@ -206,4 +206,10 @@ describe("expression evaluation", () => { expect(compileExpr("{a,b}", {})).toBe("{a:scope['a'],b:scope['b']}"); expect(compileExpr("{a,b:3,c}", {})).toBe("{a:scope['a'],b:3,c:scope['c']}"); }); + + test("template strings", () => { + expect(compileExpr("`hey`", {})).toBe("`hey`"); + expect(compileExpr("`hey ${you}`", {})).toBe("`hey ${scope['you']}`"); + expect(compileExpr("`hey ${1 + 2}`", {})).toBe("`hey ${1+2}`"); + }); }); diff --git a/tools/playground/app.js b/tools/playground/app.js index 79b60377..973eba65 100644 --- a/tools/playground/app.js +++ b/tools/playground/app.js @@ -72,7 +72,7 @@ if __name__ == "__main__": * Make an iframe, with all the js, css and xml properly injected. */ function makeCodeIframe(js, css, xml) { - const sanitizedXML = xml.replace(//g, ""); + const sanitizedXML = xml.replace(//g, "").replace(/`/g, '\\\`'); // create iframe