mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
[FIX] compiler: fix issue with identifiers with same name
Before this commit, there were 2 different ways of generating variable identifiers. And it was possible to have a situation with two different variable in a template with the same identifier, which caused a crash. This commit ensures that we go through a unique helper method, so this cannot occur anymore. Also, the code is slightly simpler.
This commit is contained in:
committed by
Sam Degueldre
parent
7137130c38
commit
d09771b04b
@@ -42,17 +42,20 @@ export interface CodeGenOptions extends Config {
|
|||||||
const xmlDoc = document.implementation.createDocument(null, null, null);
|
const xmlDoc = document.implementation.createDocument(null, null, null);
|
||||||
|
|
||||||
const MODS = new Set(["stop", "capture", "prevent", "self", "synthetic"]);
|
const MODS = new Set(["stop", "capture", "prevent", "self", "synthetic"]);
|
||||||
|
|
||||||
|
let nextDataIds: { [key: string]: number } = {};
|
||||||
|
|
||||||
|
function generateId(prefix: string = "") {
|
||||||
|
nextDataIds[prefix] = (nextDataIds[prefix] || 0) + 1;
|
||||||
|
return prefix + nextDataIds[prefix];
|
||||||
|
}
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
// BlockDescription
|
// BlockDescription
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
class BlockDescription {
|
class BlockDescription {
|
||||||
static nextBlockId = 1;
|
static nextBlockId = 1;
|
||||||
static nextDataIds: { [key: string]: number } = {};
|
|
||||||
static generateId(prefix: string) {
|
|
||||||
this.nextDataIds[prefix] = (this.nextDataIds[prefix] || 0) + 1;
|
|
||||||
return prefix + this.nextDataIds[prefix];
|
|
||||||
}
|
|
||||||
|
|
||||||
varName: string;
|
varName: string;
|
||||||
blockName: string;
|
blockName: string;
|
||||||
@@ -78,7 +81,7 @@ class BlockDescription {
|
|||||||
}
|
}
|
||||||
|
|
||||||
insertData(str: string, prefix: string = "d"): number {
|
insertData(str: string, prefix: string = "d"): number {
|
||||||
const id = BlockDescription.generateId(prefix);
|
const id = generateId(prefix);
|
||||||
this.target.addLine(`let ${id} = ${str};`);
|
this.target.addLine(`let ${id} = ${str};`);
|
||||||
return this.data.push(id) - 1;
|
return this.data.push(id) - 1;
|
||||||
}
|
}
|
||||||
@@ -209,7 +212,6 @@ const translationRE = /^(\s*)([\s\S]+?)(\s*)$/;
|
|||||||
|
|
||||||
export class CodeGenerator {
|
export class CodeGenerator {
|
||||||
blocks: BlockDescription[] = [];
|
blocks: BlockDescription[] = [];
|
||||||
ids: { [key: string]: number } = {};
|
|
||||||
nextBlockId = 1;
|
nextBlockId = 1;
|
||||||
hasSafeContext: boolean;
|
hasSafeContext: boolean;
|
||||||
isDebug: boolean = false;
|
isDebug: boolean = false;
|
||||||
@@ -246,7 +248,7 @@ export class CodeGenerator {
|
|||||||
const ast = this.ast;
|
const ast = this.ast;
|
||||||
this.isDebug = ast.type === ASTType.TDebug;
|
this.isDebug = ast.type === ASTType.TDebug;
|
||||||
BlockDescription.nextBlockId = 1;
|
BlockDescription.nextBlockId = 1;
|
||||||
BlockDescription.nextDataIds = {};
|
nextDataIds = {};
|
||||||
this.compileAST(ast, {
|
this.compileAST(ast, {
|
||||||
block: null,
|
block: null,
|
||||||
index: 0,
|
index: 0,
|
||||||
@@ -308,7 +310,7 @@ export class CodeGenerator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
compileInNewTarget(prefix: string, ast: AST, ctx: Context, on?: EventHandlers | null): string {
|
compileInNewTarget(prefix: string, ast: AST, ctx: Context, on?: EventHandlers | null): string {
|
||||||
const name = this.generateId(prefix);
|
const name = generateId(prefix);
|
||||||
const initialTarget = this.target;
|
const initialTarget = this.target;
|
||||||
const target = new CodeTarget(name, on);
|
const target = new CodeTarget(name, on);
|
||||||
this.targets.push(target);
|
this.targets.push(target);
|
||||||
@@ -326,11 +328,6 @@ export class CodeGenerator {
|
|||||||
this.addLine(`const ${varName} = ${expr};`);
|
this.addLine(`const ${varName} = ${expr};`);
|
||||||
}
|
}
|
||||||
|
|
||||||
generateId(prefix: string = ""): string {
|
|
||||||
this.ids[prefix] = (this.ids[prefix] || 0) + 1;
|
|
||||||
return prefix + this.ids[prefix];
|
|
||||||
}
|
|
||||||
|
|
||||||
insertAnchor(block: BlockDescription) {
|
insertAnchor(block: BlockDescription) {
|
||||||
const tag = `block-child-${block.children.length}`;
|
const tag = `block-child-${block.children.length}`;
|
||||||
const anchor = xmlDoc.createElement(tag);
|
const anchor = xmlDoc.createElement(tag);
|
||||||
@@ -407,7 +404,7 @@ export class CodeGenerator {
|
|||||||
.map((tok) => {
|
.map((tok) => {
|
||||||
if (tok.varName && !tok.isLocal) {
|
if (tok.varName && !tok.isLocal) {
|
||||||
if (!mapping.has(tok.varName)) {
|
if (!mapping.has(tok.varName)) {
|
||||||
const varId = this.generateId("v");
|
const varId = generateId("v");
|
||||||
mapping.set(tok.varName, varId);
|
mapping.set(tok.varName, varId);
|
||||||
this.define(varId, tok.value);
|
this.define(varId, tok.value);
|
||||||
}
|
}
|
||||||
@@ -553,7 +550,7 @@ export class CodeGenerator {
|
|||||||
block = this.createBlock(block, "block", ctx);
|
block = this.createBlock(block, "block", ctx);
|
||||||
this.blocks.push(block);
|
this.blocks.push(block);
|
||||||
if (ast.dynamicTag) {
|
if (ast.dynamicTag) {
|
||||||
const tagExpr = this.generateId("tag");
|
const tagExpr = generateId("tag");
|
||||||
this.define(tagExpr, compileExpr(ast.dynamicTag));
|
this.define(tagExpr, compileExpr(ast.dynamicTag));
|
||||||
block.dynamicTagName = tagExpr;
|
block.dynamicTagName = tagExpr;
|
||||||
}
|
}
|
||||||
@@ -625,7 +622,7 @@ export class CodeGenerator {
|
|||||||
attrs["block-ref"] = String(index);
|
attrs["block-ref"] = String(index);
|
||||||
info[1] = `multiRefSetter(refs, \`${name}\`)`;
|
info[1] = `multiRefSetter(refs, \`${name}\`)`;
|
||||||
} else {
|
} else {
|
||||||
let id = this.generateId("ref");
|
let id = generateId("ref");
|
||||||
this.target.refInfo[name] = [id, `(el) => refs[\`${name}\`] = el`];
|
this.target.refInfo[name] = [id, `(el) => refs[\`${name}\`] = el`];
|
||||||
const index = block!.data.push(id) - 1;
|
const index = block!.data.push(id) - 1;
|
||||||
attrs["block-ref"] = String(index);
|
attrs["block-ref"] = String(index);
|
||||||
@@ -648,11 +645,11 @@ export class CodeGenerator {
|
|||||||
} = ast.model;
|
} = ast.model;
|
||||||
|
|
||||||
const baseExpression = compileExpr(baseExpr);
|
const baseExpression = compileExpr(baseExpr);
|
||||||
const bExprId = this.generateId("bExpr");
|
const bExprId = generateId("bExpr");
|
||||||
this.define(bExprId, baseExpression);
|
this.define(bExprId, baseExpression);
|
||||||
|
|
||||||
const expression = compileExpr(expr);
|
const expression = compileExpr(expr);
|
||||||
const exprId = this.generateId("expr");
|
const exprId = generateId("expr");
|
||||||
this.define(exprId, expression);
|
this.define(exprId, expression);
|
||||||
|
|
||||||
const fullExpression = `${bExprId}[${exprId}]`;
|
const fullExpression = `${bExprId}[${exprId}]`;
|
||||||
@@ -662,7 +659,7 @@ export class CodeGenerator {
|
|||||||
idx = block!.insertData(`${fullExpression} === '${attrs[targetAttr]}'`, "attr");
|
idx = block!.insertData(`${fullExpression} === '${attrs[targetAttr]}'`, "attr");
|
||||||
attrs[`block-attribute-${idx}`] = specialInitTargetAttr;
|
attrs[`block-attribute-${idx}`] = specialInitTargetAttr;
|
||||||
} else if (hasDynamicChildren) {
|
} else if (hasDynamicChildren) {
|
||||||
const bValueId = this.generateId("bValue");
|
const bValueId = generateId("bValue");
|
||||||
tModelSelectedExpr = `${bValueId}`;
|
tModelSelectedExpr = `${bValueId}`;
|
||||||
this.define(tModelSelectedExpr, fullExpression);
|
this.define(tModelSelectedExpr, fullExpression);
|
||||||
} else {
|
} else {
|
||||||
@@ -869,7 +866,7 @@ export class CodeGenerator {
|
|||||||
let id: string;
|
let id: string;
|
||||||
if (ast.memo) {
|
if (ast.memo) {
|
||||||
this.target.hasCache = true;
|
this.target.hasCache = true;
|
||||||
id = this.generateId();
|
id = generateId();
|
||||||
this.define(`memo${id}`, compileExpr(ast.memo));
|
this.define(`memo${id}`, compileExpr(ast.memo));
|
||||||
this.define(`vnode${id}`, `cache[key${this.target.loopLevel}];`);
|
this.define(`vnode${id}`, `cache[key${this.target.loopLevel}];`);
|
||||||
this.addLine(`if (vnode${id}) {`);
|
this.addLine(`if (vnode${id}) {`);
|
||||||
@@ -904,7 +901,7 @@ export class CodeGenerator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
compileTKey(ast: ASTTKey, ctx: Context) {
|
compileTKey(ast: ASTTKey, ctx: Context) {
|
||||||
const tKeyExpr = this.generateId("tKey_");
|
const tKeyExpr = generateId("tKey_");
|
||||||
this.define(tKeyExpr, compileExpr(ast.expr));
|
this.define(tKeyExpr, compileExpr(ast.expr));
|
||||||
ctx = createContext(ctx, {
|
ctx = createContext(ctx, {
|
||||||
tKeyExpr,
|
tKeyExpr,
|
||||||
@@ -989,7 +986,7 @@ export class CodeGenerator {
|
|||||||
}
|
}
|
||||||
const key = `key + \`${this.generateComponentKey()}\``;
|
const key = `key + \`${this.generateComponentKey()}\``;
|
||||||
if (isDynamic) {
|
if (isDynamic) {
|
||||||
const templateVar = this.generateId("template");
|
const templateVar = generateId("template");
|
||||||
this.define(templateVar, subTemplate);
|
this.define(templateVar, subTemplate);
|
||||||
block = this.createBlock(block, "multi", ctx);
|
block = this.createBlock(block, "multi", ctx);
|
||||||
this.helpers.add("call");
|
this.helpers.add("call");
|
||||||
@@ -998,7 +995,7 @@ export class CodeGenerator {
|
|||||||
forceNewBlock: !block,
|
forceNewBlock: !block,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
const id = this.generateId(`callTemplate_`);
|
const id = generateId(`callTemplate_`);
|
||||||
this.helpers.add("getTemplate");
|
this.helpers.add("getTemplate");
|
||||||
this.staticDefs.push({ id, expr: `getTemplate(${subTemplate})` });
|
this.staticDefs.push({ id, expr: `getTemplate(${subTemplate})` });
|
||||||
block = this.createBlock(block, "multi", ctx);
|
block = this.createBlock(block, "multi", ctx);
|
||||||
@@ -1051,7 +1048,7 @@ export class CodeGenerator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
generateComponentKey() {
|
generateComponentKey() {
|
||||||
const parts = [this.generateId("__")];
|
const parts = [generateId("__")];
|
||||||
for (let i = 0; i < this.target.loopLevel; i++) {
|
for (let i = 0; i < this.target.loopLevel; i++) {
|
||||||
parts.push(`\${key${i + 1}}`);
|
parts.push(`\${key${i + 1}}`);
|
||||||
}
|
}
|
||||||
@@ -1108,7 +1105,7 @@ export class CodeGenerator {
|
|||||||
if (ast.slots) {
|
if (ast.slots) {
|
||||||
let ctxStr = "ctx";
|
let ctxStr = "ctx";
|
||||||
if (this.target.loopLevel || !this.hasSafeContext) {
|
if (this.target.loopLevel || !this.hasSafeContext) {
|
||||||
ctxStr = this.generateId("ctx");
|
ctxStr = generateId("ctx");
|
||||||
this.helpers.add("capture");
|
this.helpers.add("capture");
|
||||||
this.define(ctxStr, `capture(ctx)`);
|
this.define(ctxStr, `capture(ctx)`);
|
||||||
}
|
}
|
||||||
@@ -1149,7 +1146,7 @@ export class CodeGenerator {
|
|||||||
|
|
||||||
let propVar: string;
|
let propVar: string;
|
||||||
if ((slotDef && (ast.dynamicProps || hasSlotsProp)) || this.dev) {
|
if ((slotDef && (ast.dynamicProps || hasSlotsProp)) || this.dev) {
|
||||||
propVar = this.generateId("props");
|
propVar = generateId("props");
|
||||||
this.define(propVar!, propString);
|
this.define(propVar!, propString);
|
||||||
propString = propVar!;
|
propString = propVar!;
|
||||||
}
|
}
|
||||||
@@ -1163,7 +1160,7 @@ export class CodeGenerator {
|
|||||||
const key = this.generateComponentKey();
|
const key = this.generateComponentKey();
|
||||||
let expr: string;
|
let expr: string;
|
||||||
if (ast.isDynamic) {
|
if (ast.isDynamic) {
|
||||||
expr = this.generateId("Comp");
|
expr = generateId("Comp");
|
||||||
this.define(expr, compileExpr(ast.name));
|
this.define(expr, compileExpr(ast.name));
|
||||||
} else {
|
} else {
|
||||||
expr = `\`${ast.name}\``;
|
expr = `\`${ast.name}\``;
|
||||||
@@ -1199,11 +1196,11 @@ export class CodeGenerator {
|
|||||||
|
|
||||||
wrapWithEventCatcher(expr: string, on: EventHandlers): string {
|
wrapWithEventCatcher(expr: string, on: EventHandlers): string {
|
||||||
this.helpers.add("createCatcher");
|
this.helpers.add("createCatcher");
|
||||||
let name = this.generateId("catcher");
|
let name = generateId("catcher");
|
||||||
let spec: any = {};
|
let spec: any = {};
|
||||||
let handlers: any[] = [];
|
let handlers: any[] = [];
|
||||||
for (let ev in on) {
|
for (let ev in on) {
|
||||||
let handlerId = this.generateId("hdlr");
|
let handlerId = generateId("hdlr");
|
||||||
let idx = handlers.push(handlerId) - 1;
|
let idx = handlers.push(handlerId) - 1;
|
||||||
spec[ev] = idx;
|
spec[ev] = idx;
|
||||||
const handler = this.generateHandlerCode(ev, on[ev]);
|
const handler = this.generateHandlerCode(ev, on[ev]);
|
||||||
@@ -1232,7 +1229,7 @@ export class CodeGenerator {
|
|||||||
blockString = `callSlot(ctx, node, key, ${slotName}, ${dynamic}, ${scope}, ${name})`;
|
blockString = `callSlot(ctx, node, key, ${slotName}, ${dynamic}, ${scope}, ${name})`;
|
||||||
} else {
|
} else {
|
||||||
if (dynamic) {
|
if (dynamic) {
|
||||||
let name = this.generateId("slot");
|
let name = generateId("slot");
|
||||||
this.define(name, slotName);
|
this.define(name, slotName);
|
||||||
blockString = `toggler(${name}, callSlot(ctx, node, key, ${name}, ${dynamic}, ${scope}))`;
|
blockString = `toggler(${name}, callSlot(ctx, node, key, ${name}, ${dynamic}, ${scope}))`;
|
||||||
} else {
|
} else {
|
||||||
@@ -1264,7 +1261,7 @@ export class CodeGenerator {
|
|||||||
const key = this.generateComponentKey();
|
const key = this.generateComponentKey();
|
||||||
let ctxStr = "ctx";
|
let ctxStr = "ctx";
|
||||||
if (this.target.loopLevel || !this.hasSafeContext) {
|
if (this.target.loopLevel || !this.hasSafeContext) {
|
||||||
ctxStr = this.generateId("ctx");
|
ctxStr = generateId("ctx");
|
||||||
this.helpers.add("capture");
|
this.helpers.add("capture");
|
||||||
this.define(ctxStr, `capture(ctx);`);
|
this.define(ctxStr, `capture(ctx);`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -121,6 +121,38 @@ exports[`t-on t-on method call in t-foreach 1`] = `
|
|||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`t-on t-on on component next to t-on on div 1`] = `
|
||||||
|
"function anonymous(bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
let { createCatcher } = helpers;
|
||||||
|
const catcher1 = createCatcher({\\"click\\":0});
|
||||||
|
|
||||||
|
let block1 = createBlock(\`<div><block-child-0/><p block-handler-0=\\"click\\">dec</p></div>\`);
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
const hdlr1 = [ctx['increment'], ctx];
|
||||||
|
const b2 = catcher1(component(\`Child\`, {value: ctx['state'].value}, key + \`__1\`, node, ctx), [hdlr1]);
|
||||||
|
let hdlr2 = [ctx['decrement'], ctx];
|
||||||
|
return block1([hdlr2], [b2]);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`t-on t-on on component next to t-on on div 2`] = `
|
||||||
|
"function anonymous(bdom, helpers
|
||||||
|
) {
|
||||||
|
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||||
|
|
||||||
|
let block1 = createBlock(\`<button><block-text-0/></button>\`);
|
||||||
|
|
||||||
|
return function template(ctx, node, key = \\"\\") {
|
||||||
|
let txt1 = ctx['props'].value;
|
||||||
|
return block1([txt1]);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`t-on t-on on components 1`] = `
|
exports[`t-on t-on on components 1`] = `
|
||||||
"function anonymous(bdom, helpers
|
"function anonymous(bdom, helpers
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -197,6 +197,36 @@ describe("t-on", () => {
|
|||||||
expect(fixture.innerHTML).toBe("<div><span></span><button>2</button><p></p></div>");
|
expect(fixture.innerHTML).toBe("<div><span></span><button>2</button><p></p></div>");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("t-on on component next to t-on on div", async () => {
|
||||||
|
class Child extends Component {
|
||||||
|
static template = xml`<button t-esc="props.value"/>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
class Parent extends Component {
|
||||||
|
static template = xml`
|
||||||
|
<div>
|
||||||
|
<Child t-on-click="increment" value="state.value"/>
|
||||||
|
<p t-on-click="decrement">dec</p>
|
||||||
|
</div>`;
|
||||||
|
static components = { Child };
|
||||||
|
state = useState({ value: 1 });
|
||||||
|
increment() {
|
||||||
|
this.state.value++;
|
||||||
|
}
|
||||||
|
decrement() {
|
||||||
|
this.state.value--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await mount(Parent, fixture);
|
||||||
|
expect(fixture.innerHTML).toBe("<div><button>1</button><p>dec</p></div>");
|
||||||
|
fixture.querySelector("button")!.click();
|
||||||
|
await nextTick();
|
||||||
|
expect(fixture.innerHTML).toBe("<div><button>2</button><p>dec</p></div>");
|
||||||
|
fixture.querySelector("p")!.click();
|
||||||
|
await nextTick();
|
||||||
|
expect(fixture.innerHTML).toBe("<div><button>1</button><p>dec</p></div>");
|
||||||
|
});
|
||||||
|
|
||||||
test("t-on on t-slots", async () => {
|
test("t-on on t-slots", async () => {
|
||||||
class Child extends Component {
|
class Child extends Component {
|
||||||
static template = xml`
|
static template = xml`
|
||||||
|
|||||||
Reference in New Issue
Block a user