From e162a6fb6cb7191518949c07d5ffd9f78c3871cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A9ry=20Debongnie?= Date: Wed, 4 Dec 2019 20:57:59 +0100 Subject: [PATCH] [IMP] qweb: add support for arrow functions in expression parser closes #542 --- src/qweb/expression_parser.ts | 45 ++++++++++++++++++++--------- tests/qweb/qweb_expressions.test.ts | 9 ++++++ 2 files changed, 41 insertions(+), 13 deletions(-) diff --git a/src/qweb/expression_parser.ts b/src/qweb/expression_parser.ts index b1374b8b..9fd8dd19 100644 --- a/src/qweb/expression_parser.ts +++ b/src/qweb/expression_parser.ts @@ -63,6 +63,7 @@ type TKind = interface Token { type: TKind; value: string; + originalValue?: string; size?: number; } @@ -79,7 +80,7 @@ const STATIC_TOKEN_MAP: { [key: string]: TKind } = { // note that the space after typeof is relevant. It makes sure that the formatted // expression has a space after typeof -const OPERATORS = ".,===,==,+,!==,!=,!,||,&&,>=,>,<=,<,?,-,*,/,%,typeof ".split(","); +const OPERATORS = ".,===,==,+,!==,!=,!,||,&&,>=,>,<=,<,?,-,*,/,%,typeof ,=>".split(","); type Tokenizer = (expr: string) => Token | false; @@ -227,35 +228,53 @@ export function tokenize(expr: string): Token[] { * - unless the previous token is a dot (in that case, this is a property: `a.b`) * - or if the previous token is a left brace or a comma, and the next token is * a colon (in that case, this is an object key: `{a: b}`) + * + * Some specific code is also required to support arrow functions. If we detect + * the arrow operator, then we add the current (or some previous tokens) token to + * the list of variables so it does not get replaced by a lookup in the context */ export function compileExpr(expr: string, vars: { [key: string]: QWebVar }): string { + vars = Object.create(vars); const tokens = tokenize(expr); - let result = ""; for (let i = 0; i < tokens.length; i++) { let token = tokens[i]; + let prevToken = tokens[i - 1]; + let nextToken = tokens[i + 1]; + let isVar = token.type === "SYMBOL" && !RESERVED_WORDS.includes(token.value); if (token.type === "SYMBOL" && !RESERVED_WORDS.includes(token.value)) { - // we need to find if it is a variable - let isVar = true; - let prevToken = tokens[i - 1]; if (prevToken) { if (prevToken.type === "OPERATOR" && prevToken.value === ".") { isVar = false; } else if (prevToken.type === "LEFT_BRACE" || prevToken.type === "COMMA") { - let nextToken = tokens[i + 1]; if (nextToken && nextToken.type === "COLON") { isVar = false; } } } - if (isVar) { - if (token.value in vars && "id" in vars[token.value]) { - token.value = vars[token.value].id!; - } else { - token.value = `context['${token.value}']`; + } + if (nextToken && nextToken.type === "OPERATOR" && nextToken.value === "=>") { + if (token.type === "RIGHT_PAREN") { + let j = i - 1; + while (j > 0 && tokens[j].type !== "LEFT_PAREN") { + if (tokens[j].type === "SYMBOL" && tokens[j].originalValue) { + tokens[j].value = tokens[j].originalValue!; + vars[tokens[j].value] = { id: tokens[j].value }; + } + j--; } + } else { + vars[token.value] = { id: token.value }; + } + } + + if (isVar) { + if (token.value in vars && "id" in vars[token.value]) { + token.value = vars[token.value].id!; + } else { + token.originalValue = token.value; + token.value = `context['${token.value}']`; } } - result += token.value; } - return result; + return tokens.map(t => t.value).join(""); } diff --git a/tests/qweb/qweb_expressions.test.ts b/tests/qweb/qweb_expressions.test.ts index 99d9bbf1..20d2f8bd 100644 --- a/tests/qweb/qweb_expressions.test.ts +++ b/tests/qweb/qweb_expressions.test.ts @@ -163,4 +163,13 @@ describe("expression evaluation", () => { "'x'.toUpperCase({b:_v5})" ); }); + + test("arrow functions", () => { + expect(compileExpr("list.map(e => e.val)", {})).toBe("context['list'].map(e=>e.val)"); + expect(compileExpr("list.map(e => a + e)", {})).toBe("context['list'].map(e=>context['a']+e)"); + expect(compileExpr("list.map((e) => e)", {})).toBe("context['list'].map((e)=>e)"); + expect(compileExpr("list.map((elem, index) => elem + index)", {})).toBe( + "context['list'].map((elem,index)=>elem+index)" + ); + }); });