[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:
Géry Debongnie
2022-05-04 14:13:26 +02:00
committed by Sam Degueldre
parent 7137130c38
commit d09771b04b
3 changed files with 91 additions and 32 deletions
+29 -32
View File
@@ -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
) {
+30
View File
@@ -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`