Skip to content

Commit 6a1bb50

Browse files
authored
fix(compile-sfc): correctly handle variable shadowing in for loop for defineProps destructuring. (#14296)
close #14294
1 parent f0f0a21 commit 6a1bb50

File tree

3 files changed

+109
-6
lines changed

3 files changed

+109
-6
lines changed

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

Lines changed: 48 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 {
@@ -309,6 +335,28 @@ return (_ctx, _cache) => {
309335
}"
310336
`;
311337

338+
exports[`sfc reactive props destructure > regular for loop variable shadowing 1`] = `
339+
"import { defineComponent as _defineComponent } from 'vue'
340+
341+
export default /*@__PURE__*/_defineComponent({
342+
props: {
343+
i: { type: Number, required: true },
344+
len: { type: Number, required: true }
345+
},
346+
setup(__props: any) {
347+
348+
349+
for (let i = 0; i < __props.len; i++) {
350+
console.log('INDEX', i);
351+
}
352+
console.log('AFTER', { i: __props.i });
353+
354+
return () => {}
355+
}
356+
357+
})"
358+
`;
359+
312360
exports[`sfc reactive props destructure > rest spread 1`] = `
313361
"import { createPropsRestProxy as _createPropsRestProxy } from 'vue'
314362

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

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,46 @@ 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+
93+
test('regular for loop variable shadowing', () => {
94+
const { content } = compile(`
95+
<script setup lang="ts">
96+
const { i, len } = defineProps<{ i: number; len: number }>();
97+
for (let i = 0; i < len; i++) {
98+
console.log('INDEX', i);
99+
}
100+
console.log('AFTER', { i });
101+
</script>
102+
`)
103+
// inside loop: should use local variable
104+
expect(content).toMatch(`for (let i = 0; i < __props.len; i++)`)
105+
expect(content).toMatch(`console.log('INDEX', i)`)
106+
// after loop: should restore to prop reference
107+
expect(content).toMatch(`console.log('AFTER', { i: __props.i })`)
108+
assertCode(content)
109+
})
110+
71111
test('default values w/ array runtime declaration', () => {
72112
const { content } = compile(`
73113
<script setup>

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

Lines changed: 21 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,23 @@ export function transformDestructuredProps(
269264
return
270265
}
271266

267+
// for loops: loop variable should be scoped to the loop
268+
if (
269+
node.type === 'ForOfStatement' ||
270+
node.type === 'ForInStatement' ||
271+
node.type === 'ForStatement'
272+
) {
273+
pushScope()
274+
const varDecl = node.type === 'ForStatement' ? node.init : node.left
275+
if (varDecl && varDecl.type === 'VariableDeclaration') {
276+
walkVariableDeclaration(varDecl)
277+
}
278+
if (node.body.type === 'BlockStatement') {
279+
walkScope(node.body)
280+
}
281+
return
282+
}
283+
272284
// non-function block scopes
273285
if (node.type === 'BlockStatement' && !isFunctionType(parent!)) {
274286
pushScope()
@@ -292,7 +304,10 @@ export function transformDestructuredProps(
292304
if (
293305
(node.type === 'BlockStatement' && !isFunctionType(parent!)) ||
294306
isFunctionType(node) ||
295-
node.type === 'CatchClause'
307+
node.type === 'CatchClause' ||
308+
node.type === 'ForOfStatement' ||
309+
node.type === 'ForInStatement' ||
310+
node.type === 'ForStatement'
296311
) {
297312
popScope()
298313
}

0 commit comments

Comments
 (0)