Skip to content

Commit 9adfd60

Browse files
committed
refactor(compiler-vapor): add unique variable generation to avoid binding conflicts
1 parent ea397b7 commit 9adfd60

File tree

5 files changed

+141
-5
lines changed

5 files changed

+141
-5
lines changed

packages/compiler-vapor/__tests__/__snapshots__/compile.spec.ts.snap

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,42 @@ export function render(_ctx) {
280280
}"
281281
`;
282282
283+
exports[`compile > gen unique variables > should avoid binding conflicts for node vars (n*/x*) 1`] = `
284+
"
285+
const n1 = t0()
286+
const x1 = _child(n1)
287+
_renderEffect(() => _setText(x1, _toDisplayString(foo)))
288+
return n1
289+
"
290+
`;
291+
292+
exports[`compile > gen unique variables > should bump old ref var (r*) on conflict 1`] = `
293+
"
294+
const _setTemplateRef = _createTemplateRefSetter()
295+
const n1 = t0()
296+
let r1
297+
_renderEffect(() => r1 = _setTemplateRef(n1, bar, r1))
298+
return n1
299+
"
300+
`;
301+
302+
exports[`compile > gen unique variables > should bump placeholder var (p*) on conflict 1`] = `
303+
"
304+
const n1 = t0()
305+
const p1 = _child(n1)
306+
const n0 = _child(p1)
307+
_renderEffect(() => _setProp(n0, "id", foo.value))
308+
return n1
309+
"
310+
`;
311+
312+
exports[`compile > gen unique variables > should bump template var (t*) on conflict 1`] = `
313+
"
314+
const n0 = t1()
315+
return n0
316+
"
317+
`;
318+
283319
exports[`compile > setInsertionState > next, child and nthChild should be above the setInsertionState 1`] = `
284320
"import { resolveComponent as _resolveComponent, child as _child, next as _next, setInsertionState as _setInsertionState, createComponentWithFallback as _createComponentWithFallback, nthChild as _nthChild, createIf as _createIf, setProp as _setProp, renderEffect as _renderEffect, template as _template } from 'vue';
285321
const t0 = _template("<div></div>")

packages/compiler-vapor/__tests__/compile.spec.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,4 +268,59 @@ describe('compile', () => {
268268
expect(code).matchSnapshot()
269269
})
270270
})
271+
272+
describe('gen unique variables', () => {
273+
test('should avoid binding conflicts for node vars (n*/x*)', () => {
274+
const code = compile(`<div>{{ foo }}</div>`, {
275+
inline: true,
276+
bindingMetadata: {
277+
n0: BindingTypes.SETUP_REACTIVE_CONST,
278+
x0: BindingTypes.SETUP_MAYBE_REF,
279+
},
280+
})
281+
282+
expect(code).matchSnapshot()
283+
expect(code).contains('const n1 = t0()')
284+
expect(code).contains('const x1 = _child(n1)')
285+
})
286+
287+
test('should bump old ref var (r*) on conflict', () => {
288+
const code = compile(`<div :ref="bar" />`, {
289+
inline: true,
290+
bindingMetadata: {
291+
r0: BindingTypes.SETUP_REF,
292+
bar: BindingTypes.SETUP_REF,
293+
},
294+
})
295+
296+
expect(code).matchSnapshot()
297+
expect(code).contains('let r1')
298+
expect(code).contains('_setTemplateRef(n1, bar, r1)')
299+
})
300+
301+
test('should bump template var (t*) on conflict', () => {
302+
const code = compile(`<div />`, {
303+
inline: true,
304+
bindingMetadata: {
305+
t0: BindingTypes.SETUP_REF,
306+
},
307+
})
308+
309+
expect(code).matchSnapshot()
310+
expect(code).contains('const n0 = t1()')
311+
})
312+
313+
test('should bump placeholder var (p*) on conflict', () => {
314+
const code = compile(`<div><div><span :id="foo" /></div></div>`, {
315+
inline: true,
316+
bindingMetadata: {
317+
p0: BindingTypes.SETUP_REF,
318+
foo: BindingTypes.SETUP_REF,
319+
},
320+
})
321+
322+
expect(code).matchSnapshot()
323+
expect(code).contains('const p1 = ')
324+
})
325+
})
271326
})

packages/compiler-vapor/src/generate.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ export class CodegenContext {
2626

2727
helpers: Set<string> = new Set<string>([])
2828

29+
bindingNames: Set<string> = new Set<string>()
30+
2931
helper = (name: CoreHelper | VaporHelper) => {
3032
this.helpers.add(name)
3133
return `_${name}`
@@ -68,6 +70,30 @@ export class CodegenContext {
6870
return [this.scopeLevel++, () => this.scopeLevel--] as const
6971
}
7072

73+
seenVarNames: Set<string> = new Set()
74+
templateVars: Map<number, string> = new Map()
75+
genVarName(prefix: string, baseNum: number): string {
76+
let num = baseNum
77+
let name = `${prefix}${num}`
78+
while (this.bindingNames.has(name) || this.seenVarNames.has(name)) {
79+
name = `${prefix}${++num}`
80+
}
81+
this.seenVarNames.add(name)
82+
return name
83+
}
84+
85+
tName(i: number): string {
86+
let name = this.templateVars.get(i)
87+
if (!name) {
88+
this.templateVars.set(i, (name = this.genVarName('t', i)))
89+
}
90+
return name
91+
}
92+
93+
pName(i: number): string {
94+
return this.genVarName('p', i)
95+
}
96+
7197
constructor(
7298
public ir: RootIRNode,
7399
options: CodegenOptions,
@@ -90,6 +116,11 @@ export class CodegenContext {
90116
}
91117
this.options = extend(defaultOptions, options)
92118
this.block = ir.block
119+
this.bindingNames = new Set<string>(
120+
this.options.bindingMetadata
121+
? Object.keys(this.options.bindingMetadata)
122+
: [],
123+
)
93124
}
94125
}
95126

packages/compiler-vapor/src/generators/template.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,12 @@ import { type CodeFragment, NEWLINE, buildCodeFragment, genCall } from './utils'
77
export function genTemplates(
88
templates: string[],
99
rootIndex: number | undefined,
10-
{ helper }: CodegenContext,
10+
context: CodegenContext,
1111
): string {
1212
return templates
1313
.map(
1414
(template, i) =>
15-
`const t${i} = ${helper('template')}(${JSON.stringify(
15+
`const ${context.tName(i)} = ${context.helper('template')}(${JSON.stringify(
1616
template,
1717
)}${i === rootIndex ? ', true' : ''})\n`,
1818
)
@@ -27,7 +27,7 @@ export function genSelf(
2727
const { id, template, operation, hasDynamicChild } = dynamic
2828

2929
if (id !== undefined && template !== undefined) {
30-
push(NEWLINE, `const n${id} = t${template}()`)
30+
push(NEWLINE, `const n${id} = ${context.tName(template)}()`)
3131
push(...genDirectivesForElement(id, context))
3232
}
3333

@@ -75,7 +75,8 @@ export function genChildren(
7575
const elementIndex = Number(index) + offset
7676
// p for "placeholder" variables that are meant for possible reuse by
7777
// other access paths
78-
const variable = id === undefined ? `p${context.block.tempId++}` : `n${id}`
78+
const variable =
79+
id === undefined ? context.pName(context.block.tempId++) : `n${id}`
7980
pushBlock(NEWLINE, `const ${variable} = `)
8081

8182
if (prev) {

packages/compiler-vapor/src/transform.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,20 @@ export class TransformContext<T extends AllNode = AllNode> {
117117
}
118118
}
119119

120-
increaseId = (): number => this.globalId++
120+
increaseId = (): number => {
121+
// allocate an id that won't conflict with user-defined bindings when used
122+
// as generated identifiers with n/x/r prefixes (e.g., n1, x1, r1)
123+
const binding = this.root.options.bindingMetadata
124+
let id = this.globalId
125+
while (
126+
binding &&
127+
('n' + id in binding || 'x' + id in binding || 'r' + id in binding)
128+
) {
129+
id++
130+
}
131+
this.globalId = id + 1
132+
return id
133+
}
121134
reference(): number {
122135
if (this.dynamic.id !== undefined) return this.dynamic.id
123136
this.dynamic.flags |= DynamicFlag.REFERENCED

0 commit comments

Comments
 (0)