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 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
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
class BlockDescription {
|
||||
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;
|
||||
blockName: string;
|
||||
@@ -78,7 +81,7 @@ class BlockDescription {
|
||||
}
|
||||
|
||||
insertData(str: string, prefix: string = "d"): number {
|
||||
const id = BlockDescription.generateId(prefix);
|
||||
const id = generateId(prefix);
|
||||
this.target.addLine(`let ${id} = ${str};`);
|
||||
return this.data.push(id) - 1;
|
||||
}
|
||||
@@ -209,7 +212,6 @@ const translationRE = /^(\s*)([\s\S]+?)(\s*)$/;
|
||||
|
||||
export class CodeGenerator {
|
||||
blocks: BlockDescription[] = [];
|
||||
ids: { [key: string]: number } = {};
|
||||
nextBlockId = 1;
|
||||
hasSafeContext: boolean;
|
||||
isDebug: boolean = false;
|
||||
@@ -246,7 +248,7 @@ export class CodeGenerator {
|
||||
const ast = this.ast;
|
||||
this.isDebug = ast.type === ASTType.TDebug;
|
||||
BlockDescription.nextBlockId = 1;
|
||||
BlockDescription.nextDataIds = {};
|
||||
nextDataIds = {};
|
||||
this.compileAST(ast, {
|
||||
block: null,
|
||||
index: 0,
|
||||
@@ -308,7 +310,7 @@ export class CodeGenerator {
|
||||
}
|
||||
|
||||
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 target = new CodeTarget(name, on);
|
||||
this.targets.push(target);
|
||||
@@ -326,11 +328,6 @@ export class CodeGenerator {
|
||||
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) {
|
||||
const tag = `block-child-${block.children.length}`;
|
||||
const anchor = xmlDoc.createElement(tag);
|
||||
@@ -407,7 +404,7 @@ export class CodeGenerator {
|
||||
.map((tok) => {
|
||||
if (tok.varName && !tok.isLocal) {
|
||||
if (!mapping.has(tok.varName)) {
|
||||
const varId = this.generateId("v");
|
||||
const varId = generateId("v");
|
||||
mapping.set(tok.varName, varId);
|
||||
this.define(varId, tok.value);
|
||||
}
|
||||
@@ -553,7 +550,7 @@ export class CodeGenerator {
|
||||
block = this.createBlock(block, "block", ctx);
|
||||
this.blocks.push(block);
|
||||
if (ast.dynamicTag) {
|
||||
const tagExpr = this.generateId("tag");
|
||||
const tagExpr = generateId("tag");
|
||||
this.define(tagExpr, compileExpr(ast.dynamicTag));
|
||||
block.dynamicTagName = tagExpr;
|
||||
}
|
||||
@@ -625,7 +622,7 @@ export class CodeGenerator {
|
||||
attrs["block-ref"] = String(index);
|
||||
info[1] = `multiRefSetter(refs, \`${name}\`)`;
|
||||
} else {
|
||||
let id = this.generateId("ref");
|
||||
let id = generateId("ref");
|
||||
this.target.refInfo[name] = [id, `(el) => refs[\`${name}\`] = el`];
|
||||
const index = block!.data.push(id) - 1;
|
||||
attrs["block-ref"] = String(index);
|
||||
@@ -648,11 +645,11 @@ export class CodeGenerator {
|
||||
} = ast.model;
|
||||
|
||||
const baseExpression = compileExpr(baseExpr);
|
||||
const bExprId = this.generateId("bExpr");
|
||||
const bExprId = generateId("bExpr");
|
||||
this.define(bExprId, baseExpression);
|
||||
|
||||
const expression = compileExpr(expr);
|
||||
const exprId = this.generateId("expr");
|
||||
const exprId = generateId("expr");
|
||||
this.define(exprId, expression);
|
||||
|
||||
const fullExpression = `${bExprId}[${exprId}]`;
|
||||
@@ -662,7 +659,7 @@ export class CodeGenerator {
|
||||
idx = block!.insertData(`${fullExpression} === '${attrs[targetAttr]}'`, "attr");
|
||||
attrs[`block-attribute-${idx}`] = specialInitTargetAttr;
|
||||
} else if (hasDynamicChildren) {
|
||||
const bValueId = this.generateId("bValue");
|
||||
const bValueId = generateId("bValue");
|
||||
tModelSelectedExpr = `${bValueId}`;
|
||||
this.define(tModelSelectedExpr, fullExpression);
|
||||
} else {
|
||||
@@ -869,7 +866,7 @@ export class CodeGenerator {
|
||||
let id: string;
|
||||
if (ast.memo) {
|
||||
this.target.hasCache = true;
|
||||
id = this.generateId();
|
||||
id = generateId();
|
||||
this.define(`memo${id}`, compileExpr(ast.memo));
|
||||
this.define(`vnode${id}`, `cache[key${this.target.loopLevel}];`);
|
||||
this.addLine(`if (vnode${id}) {`);
|
||||
@@ -904,7 +901,7 @@ export class CodeGenerator {
|
||||
}
|
||||
|
||||
compileTKey(ast: ASTTKey, ctx: Context) {
|
||||
const tKeyExpr = this.generateId("tKey_");
|
||||
const tKeyExpr = generateId("tKey_");
|
||||
this.define(tKeyExpr, compileExpr(ast.expr));
|
||||
ctx = createContext(ctx, {
|
||||
tKeyExpr,
|
||||
@@ -989,7 +986,7 @@ export class CodeGenerator {
|
||||
}
|
||||
const key = `key + \`${this.generateComponentKey()}\``;
|
||||
if (isDynamic) {
|
||||
const templateVar = this.generateId("template");
|
||||
const templateVar = generateId("template");
|
||||
this.define(templateVar, subTemplate);
|
||||
block = this.createBlock(block, "multi", ctx);
|
||||
this.helpers.add("call");
|
||||
@@ -998,7 +995,7 @@ export class CodeGenerator {
|
||||
forceNewBlock: !block,
|
||||
});
|
||||
} else {
|
||||
const id = this.generateId(`callTemplate_`);
|
||||
const id = generateId(`callTemplate_`);
|
||||
this.helpers.add("getTemplate");
|
||||
this.staticDefs.push({ id, expr: `getTemplate(${subTemplate})` });
|
||||
block = this.createBlock(block, "multi", ctx);
|
||||
@@ -1051,7 +1048,7 @@ export class CodeGenerator {
|
||||
}
|
||||
|
||||
generateComponentKey() {
|
||||
const parts = [this.generateId("__")];
|
||||
const parts = [generateId("__")];
|
||||
for (let i = 0; i < this.target.loopLevel; i++) {
|
||||
parts.push(`\${key${i + 1}}`);
|
||||
}
|
||||
@@ -1108,7 +1105,7 @@ export class CodeGenerator {
|
||||
if (ast.slots) {
|
||||
let ctxStr = "ctx";
|
||||
if (this.target.loopLevel || !this.hasSafeContext) {
|
||||
ctxStr = this.generateId("ctx");
|
||||
ctxStr = generateId("ctx");
|
||||
this.helpers.add("capture");
|
||||
this.define(ctxStr, `capture(ctx)`);
|
||||
}
|
||||
@@ -1149,7 +1146,7 @@ export class CodeGenerator {
|
||||
|
||||
let propVar: string;
|
||||
if ((slotDef && (ast.dynamicProps || hasSlotsProp)) || this.dev) {
|
||||
propVar = this.generateId("props");
|
||||
propVar = generateId("props");
|
||||
this.define(propVar!, propString);
|
||||
propString = propVar!;
|
||||
}
|
||||
@@ -1163,7 +1160,7 @@ export class CodeGenerator {
|
||||
const key = this.generateComponentKey();
|
||||
let expr: string;
|
||||
if (ast.isDynamic) {
|
||||
expr = this.generateId("Comp");
|
||||
expr = generateId("Comp");
|
||||
this.define(expr, compileExpr(ast.name));
|
||||
} else {
|
||||
expr = `\`${ast.name}\``;
|
||||
@@ -1199,11 +1196,11 @@ export class CodeGenerator {
|
||||
|
||||
wrapWithEventCatcher(expr: string, on: EventHandlers): string {
|
||||
this.helpers.add("createCatcher");
|
||||
let name = this.generateId("catcher");
|
||||
let name = generateId("catcher");
|
||||
let spec: any = {};
|
||||
let handlers: any[] = [];
|
||||
for (let ev in on) {
|
||||
let handlerId = this.generateId("hdlr");
|
||||
let handlerId = generateId("hdlr");
|
||||
let idx = handlers.push(handlerId) - 1;
|
||||
spec[ev] = idx;
|
||||
const handler = this.generateHandlerCode(ev, on[ev]);
|
||||
@@ -1232,7 +1229,7 @@ export class CodeGenerator {
|
||||
blockString = `callSlot(ctx, node, key, ${slotName}, ${dynamic}, ${scope}, ${name})`;
|
||||
} else {
|
||||
if (dynamic) {
|
||||
let name = this.generateId("slot");
|
||||
let name = generateId("slot");
|
||||
this.define(name, slotName);
|
||||
blockString = `toggler(${name}, callSlot(ctx, node, key, ${name}, ${dynamic}, ${scope}))`;
|
||||
} else {
|
||||
@@ -1264,7 +1261,7 @@ export class CodeGenerator {
|
||||
const key = this.generateComponentKey();
|
||||
let ctxStr = "ctx";
|
||||
if (this.target.loopLevel || !this.hasSafeContext) {
|
||||
ctxStr = this.generateId("ctx");
|
||||
ctxStr = generateId("ctx");
|
||||
this.helpers.add("capture");
|
||||
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`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
|
||||
@@ -197,6 +197,36 @@ describe("t-on", () => {
|
||||
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 () => {
|
||||
class Child extends Component {
|
||||
static template = xml`
|
||||
|
||||
Reference in New Issue
Block a user