fix(compiler-core): properly handle for loop variable declarations in expression transforms

ref https://github.com/vuejs/core/pull/11467#issuecomment-2263069794
This commit is contained in:
Evan You 2024-08-05 14:07:30 +08:00
parent f3efff1909
commit 67bb820904
No known key found for this signature in database
GPG Key ID: 00E9AB7A6704CE0A
3 changed files with 78 additions and 15 deletions

View File

@ -14,6 +14,21 @@ return function render(_ctx, _cache, $props, $setup, $data, $options) {
}" }"
`; `;
exports[`compiler: expression transform > should allow leak of var declarations in for loop 1`] = `
"const { openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
return function render(_ctx, _cache) {
return (_openBlock(), _createElementBlock("div", {
onClick: () => {
for (var i = 0; i < _ctx.list.length; i++) {
_ctx.log(i)
}
_ctx.error(i)
}
}, null, 8 /* PROPS */, ["onClick"]))
}"
`;
exports[`compiler: expression transform > should not prefix catch block param 1`] = ` exports[`compiler: expression transform > should not prefix catch block param 1`] = `
"const { openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue "const { openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
@ -53,6 +68,7 @@ return function render(_ctx, _cache) {
for (let i = 0; i < _ctx.list.length; i++) { for (let i = 0; i < _ctx.list.length; i++) {
_ctx.log(i) _ctx.log(i)
} }
_ctx.error(_ctx.i)
} }
}, null, 8 /* PROPS */, ["onClick"])) }, null, 8 /* PROPS */, ["onClick"]))
}" }"
@ -67,6 +83,7 @@ return function render(_ctx, _cache) {
for (const x in _ctx.list) { for (const x in _ctx.list) {
_ctx.log(x) _ctx.log(x)
} }
_ctx.error(_ctx.x)
} }
}, null, 8 /* PROPS */, ["onClick"])) }, null, 8 /* PROPS */, ["onClick"]))
}" }"
@ -81,6 +98,7 @@ return function render(_ctx, _cache) {
for (const x of _ctx.list) { for (const x of _ctx.list) {
_ctx.log(x) _ctx.log(x)
} }
_ctx.error(_ctx.x)
} }
}, null, 8 /* PROPS */, ["onClick"])) }, null, 8 /* PROPS */, ["onClick"]))
}" }"

View File

@ -468,9 +468,11 @@ describe('compiler: expression transform', () => {
for (const x in list) { for (const x in list) {
log(x) log(x)
} }
error(x)
}"/>`, }"/>`,
) )
expect(code).not.toMatch(`_ctx.x`) expect(code).not.toMatch(`log(_ctx.x)`)
expect(code).toMatch(`error(_ctx.x)`)
expect(code).toMatchSnapshot() expect(code).toMatchSnapshot()
}) })
@ -480,9 +482,11 @@ describe('compiler: expression transform', () => {
for (const x of list) { for (const x of list) {
log(x) log(x)
} }
error(x)
}"/>`, }"/>`,
) )
expect(code).not.toMatch(`_ctx.x`) expect(code).not.toMatch(`log(_ctx.x)`)
expect(code).toMatch(`error(_ctx.x)`)
expect(code).toMatchSnapshot() expect(code).toMatchSnapshot()
}) })
@ -492,9 +496,25 @@ describe('compiler: expression transform', () => {
for (let i = 0; i < list.length; i++) { for (let i = 0; i < list.length; i++) {
log(i) log(i)
} }
error(i)
}"/>`, }"/>`,
) )
expect(code).not.toMatch(`_ctx.i`) expect(code).not.toMatch(`log(_ctx.i)`)
expect(code).toMatch(`error(_ctx.i)`)
expect(code).toMatchSnapshot()
})
test('should allow leak of var declarations in for loop', () => {
const { code } = compile(
`<div @click="() => {
for (var i = 0; i < list.length; i++) {
log(i)
}
error(i)
}"/>`,
)
expect(code).not.toMatch(`log(_ctx.i)`)
expect(code).not.toMatch(`error(_ctx.i)`)
expect(code).toMatchSnapshot() expect(code).toMatchSnapshot()
}) })

View File

@ -2,6 +2,9 @@
// do not import runtime methods // do not import runtime methods
import type { import type {
BlockStatement, BlockStatement,
ForInStatement,
ForOfStatement,
ForStatement,
Function, Function,
Identifier, Identifier,
Node, Node,
@ -81,6 +84,10 @@ export function walkIdentifiers(
for (const id of extractIdentifiers(node.param)) { for (const id of extractIdentifiers(node.param)) {
markScopeIdentifier(node, id, knownIds) markScopeIdentifier(node, id, knownIds)
} }
} else if (isForStatement(node)) {
walkForStatement(node, false, id =>
markScopeIdentifier(node, id, knownIds),
)
} }
}, },
leave(node: Node & { scopeIds?: Set<string> }, parent: Node | null) { leave(node: Node & { scopeIds?: Set<string> }, parent: Node | null) {
@ -196,18 +203,36 @@ export function walkBlockDeclarations(
) { ) {
if (stmt.declare || !stmt.id) continue if (stmt.declare || !stmt.id) continue
onIdent(stmt.id) onIdent(stmt.id)
} else if ( } else if (isForStatement(stmt)) {
stmt.type === 'ForOfStatement' || walkForStatement(stmt, true, onIdent)
stmt.type === 'ForInStatement' || }
stmt.type === 'ForStatement' }
) { }
const variable = stmt.type === 'ForStatement' ? stmt.init : stmt.left
if (variable && variable.type === 'VariableDeclaration') { function isForStatement(
for (const decl of variable.declarations) { stmt: Node,
for (const id of extractIdentifiers(decl.id)) { ): stmt is ForStatement | ForOfStatement | ForInStatement {
onIdent(id) return (
} stmt.type === 'ForOfStatement' ||
} stmt.type === 'ForInStatement' ||
stmt.type === 'ForStatement'
)
}
function walkForStatement(
stmt: ForStatement | ForOfStatement | ForInStatement,
isVar: boolean,
onIdent: (id: Identifier) => void,
) {
const variable = stmt.type === 'ForStatement' ? stmt.init : stmt.left
if (
variable &&
variable.type === 'VariableDeclaration' &&
(variable.kind === 'var' ? isVar : !isVar)
) {
for (const decl of variable.declarations) {
for (const id of extractIdentifiers(decl.id)) {
onIdent(id)
} }
} }
} }