Skip to content

Commit cd27db4

Browse files
committed
fix(compile-sfc): correctly handle variable shadowing in for-of and for-in loops for defineProps destructuring.
1 parent 623bfb2 commit cd27db4

File tree

3 files changed

+63
-6
lines changed

3 files changed

+63
-6
lines changed

packages/compiler-sfc/__tests__/compileScript/__snapshots__/definePropsDestructure.spec.ts.snap

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,32 @@ return () => {}
192192
}"
193193
`;
194194

195+
exports[`sfc reactive props destructure > for-of loop variable shadowing 1`] = `
196+
"import { defineComponent as _defineComponent } from 'vue'
197+
interface Props {
198+
msg: string;
199+
input: string[];
200+
}
201+
202+
export default /*@__PURE__*/_defineComponent({
203+
props: {
204+
msg: { type: String, required: true },
205+
input: { type: Array, required: true }
206+
},
207+
setup(__props: any) {
208+
209+
210+
for (const msg of __props.input) {
211+
console.log('MESSAGE', msg);
212+
}
213+
console.log('NOT FAIL', { msg: __props.msg });
214+
215+
return () => {}
216+
}
217+
218+
})"
219+
`;
220+
195221
exports[`sfc reactive props destructure > handle function parameters with same name as destructured props 1`] = `
196222
"
197223
export default {

packages/compiler-sfc/__tests__/compileScript/definePropsDestructure.spec.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,28 @@ describe('sfc reactive props destructure', () => {
6868
})
6969
})
7070

71+
test('for-of loop variable shadowing', () => {
72+
const { content } = compile(`
73+
<script setup lang="ts">
74+
interface Props {
75+
msg: string;
76+
input: string[];
77+
}
78+
const { msg, input } = defineProps<Props>();
79+
for (const msg of input) {
80+
console.log('MESSAGE', msg);
81+
}
82+
console.log('NOT FAIL', { msg });
83+
</script>
84+
`)
85+
// inside loop: should use local variable
86+
expect(content).toMatch(`for (const msg of __props.input)`)
87+
expect(content).toMatch(`console.log('MESSAGE', msg)`)
88+
// after loop: should restore to prop reference
89+
expect(content).toMatch(`console.log('NOT FAIL', { msg: __props.msg })`)
90+
assertCode(content)
91+
})
92+
7193
test('default values w/ array runtime declaration', () => {
7294
const { content } = compile(`
7395
<script setup>

packages/compiler-sfc/src/script/definePropsDestructure.ts

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -147,11 +147,6 @@ export function transformDestructuredProps(
147147
) {
148148
if (stmt.declare || !stmt.id) continue
149149
registerLocalBinding(stmt.id)
150-
} else if (
151-
(stmt.type === 'ForOfStatement' || stmt.type === 'ForInStatement') &&
152-
stmt.left.type === 'VariableDeclaration'
153-
) {
154-
walkVariableDeclaration(stmt.left)
155150
} else if (
156151
stmt.type === 'ExportNamedDeclaration' &&
157152
stmt.declaration &&
@@ -269,6 +264,18 @@ export function transformDestructuredProps(
269264
return
270265
}
271266

267+
// for-of / for-in loops: loop variable should be scoped to the loop
268+
if (node.type === 'ForOfStatement' || node.type === 'ForInStatement') {
269+
pushScope()
270+
if (node.left.type === 'VariableDeclaration') {
271+
walkVariableDeclaration(node.left)
272+
}
273+
if (node.body.type === 'BlockStatement') {
274+
walkScope(node.body)
275+
}
276+
return
277+
}
278+
272279
// non-function block scopes
273280
if (node.type === 'BlockStatement' && !isFunctionType(parent!)) {
274281
pushScope()
@@ -292,7 +299,9 @@ export function transformDestructuredProps(
292299
if (
293300
(node.type === 'BlockStatement' && !isFunctionType(parent!)) ||
294301
isFunctionType(node) ||
295-
node.type === 'CatchClause'
302+
node.type === 'CatchClause' ||
303+
node.type === 'ForOfStatement' ||
304+
node.type === 'ForInStatement'
296305
) {
297306
popScope()
298307
}

0 commit comments

Comments
 (0)