[IMP] qweb: add support for arrow functions in expression parser

closes #542
This commit is contained in:
Géry Debongnie
2019-12-04 20:57:59 +01:00
committed by aab-odoo
parent 7ec05ff8cf
commit e162a6fb6c
2 changed files with 41 additions and 13 deletions
+28 -9
View File
@@ -63,6 +63,7 @@ type TKind =
interface Token { interface Token {
type: TKind; type: TKind;
value: string; value: string;
originalValue?: string;
size?: number; 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 // note that the space after typeof is relevant. It makes sure that the formatted
// expression has a space after typeof // expression has a space after typeof
const OPERATORS = ".,===,==,+,!==,!=,!,||,&&,>=,>,<=,<,?,-,*,/,%,typeof ".split(","); const OPERATORS = ".,===,==,+,!==,!=,!,||,&&,>=,>,<=,<,?,-,*,/,%,typeof ,=>".split(",");
type Tokenizer = (expr: string) => Token | false; 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`) * - 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 * - 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}`) * 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 { export function compileExpr(expr: string, vars: { [key: string]: QWebVar }): string {
vars = Object.create(vars);
const tokens = tokenize(expr); const tokens = tokenize(expr);
let result = "";
for (let i = 0; i < tokens.length; i++) { for (let i = 0; i < tokens.length; i++) {
let token = tokens[i]; let token = tokens[i];
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]; 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)) {
if (prevToken) { if (prevToken) {
if (prevToken.type === "OPERATOR" && prevToken.value === ".") { if (prevToken.type === "OPERATOR" && prevToken.value === ".") {
isVar = false; isVar = false;
} else if (prevToken.type === "LEFT_BRACE" || prevToken.type === "COMMA") { } else if (prevToken.type === "LEFT_BRACE" || prevToken.type === "COMMA") {
let nextToken = tokens[i + 1];
if (nextToken && nextToken.type === "COLON") { if (nextToken && nextToken.type === "COLON") {
isVar = false; isVar = false;
} }
} }
} }
}
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 (isVar) {
if (token.value in vars && "id" in vars[token.value]) { if (token.value in vars && "id" in vars[token.value]) {
token.value = vars[token.value].id!; token.value = vars[token.value].id!;
} else { } else {
token.originalValue = token.value;
token.value = `context['${token.value}']`; token.value = `context['${token.value}']`;
} }
} }
} }
result += token.value; return tokens.map(t => t.value).join("");
}
return result;
} }
+9
View File
@@ -163,4 +163,13 @@ describe("expression evaluation", () => {
"'x'.toUpperCase({b:_v5})" "'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)"
);
});
}); });