]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix(compile-sfc): correctly handle variable shadowing in for loop for `defineProps...
authoredison <daiwei521@126.com>
Mon, 19 Jan 2026 00:43:16 +0000 (08:43 +0800)
committerGitHub <noreply@github.com>
Mon, 19 Jan 2026 00:43:16 +0000 (08:43 +0800)
close #14294

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

index 9306d31da97401cd3c05d696349edef858fbd559..d3d6a31c2f029067d915ce9160c152e431a89178 100644 (file)
@@ -192,6 +192,32 @@ return () => {}
 }"
 `;
 
+exports[`sfc reactive props destructure > for-of loop variable shadowing 1`] = `
+"import { defineComponent as _defineComponent } from 'vue'
+interface Props {
+        msg: string;
+        input: string[];
+      }
+      
+export default /*@__PURE__*/_defineComponent({
+  props: {
+    msg: { type: String, required: true },
+    input: { type: Array, required: true }
+  },
+  setup(__props: any) {
+
+      
+      for (const msg of __props.input) {
+        console.log('MESSAGE', msg);
+      }
+      console.log('NOT FAIL', { msg: __props.msg });
+      
+return () => {}
+}
+
+})"
+`;
+
 exports[`sfc reactive props destructure > handle function parameters with same name as destructured props 1`] = `
 "
 export default {
@@ -309,6 +335,28 @@ return (_ctx, _cache) => {
 }"
 `;
 
+exports[`sfc reactive props destructure > regular for loop variable shadowing 1`] = `
+"import { defineComponent as _defineComponent } from 'vue'
+
+export default /*@__PURE__*/_defineComponent({
+  props: {
+    i: { type: Number, required: true },
+    len: { type: Number, required: true }
+  },
+  setup(__props: any) {
+
+      
+      for (let i = 0; i < __props.len; i++) {
+        console.log('INDEX', i);
+      }
+      console.log('AFTER', { i: __props.i });
+      
+return () => {}
+}
+
+})"
+`;
+
 exports[`sfc reactive props destructure > rest spread 1`] = `
 "import { createPropsRestProxy as _createPropsRestProxy } from 'vue'
 
index 25dd817bbe5d4996808523051c1f3dd231bb7b40..cfb73a2ab26b9ba27d1dadef35d1a2e3278619b6 100644 (file)
@@ -68,6 +68,46 @@ describe('sfc reactive props destructure', () => {
     })
   })
 
+  test('for-of loop variable shadowing', () => {
+    const { content } = compile(`
+      <script setup lang="ts">
+      interface Props {
+        msg: string;
+        input: string[];
+      }
+      const { msg, input } = defineProps<Props>();
+      for (const msg of input) {
+        console.log('MESSAGE', msg);
+      }
+      console.log('NOT FAIL', { msg });
+      </script>
+    `)
+    // inside loop: should use local variable
+    expect(content).toMatch(`for (const msg of __props.input)`)
+    expect(content).toMatch(`console.log('MESSAGE', msg)`)
+    // after loop: should restore to prop reference
+    expect(content).toMatch(`console.log('NOT FAIL', { msg: __props.msg })`)
+    assertCode(content)
+  })
+
+  test('regular for loop variable shadowing', () => {
+    const { content } = compile(`
+      <script setup lang="ts">
+      const { i, len } = defineProps<{ i: number; len: number }>();
+      for (let i = 0; i < len; i++) {
+        console.log('INDEX', i);
+      }
+      console.log('AFTER', { i });
+      </script>
+    `)
+    // inside loop: should use local variable
+    expect(content).toMatch(`for (let i = 0; i < __props.len; i++)`)
+    expect(content).toMatch(`console.log('INDEX', i)`)
+    // after loop: should restore to prop reference
+    expect(content).toMatch(`console.log('AFTER', { i: __props.i })`)
+    assertCode(content)
+  })
+
   test('default values w/ array runtime declaration', () => {
     const { content } = compile(`
       <script setup>
index 81763cda53469666925a78bba5682aaa1d97fe85..0d18d01943d30746f15303ba8b776a9ee642c1d4 100644 (file)
@@ -147,11 +147,6 @@ export function transformDestructuredProps(
       ) {
         if (stmt.declare || !stmt.id) continue
         registerLocalBinding(stmt.id)
-      } else if (
-        (stmt.type === 'ForOfStatement' || stmt.type === 'ForInStatement') &&
-        stmt.left.type === 'VariableDeclaration'
-      ) {
-        walkVariableDeclaration(stmt.left)
       } else if (
         stmt.type === 'ExportNamedDeclaration' &&
         stmt.declaration &&
@@ -269,6 +264,23 @@ export function transformDestructuredProps(
         return
       }
 
+      // for loops: loop variable should be scoped to the loop
+      if (
+        node.type === 'ForOfStatement' ||
+        node.type === 'ForInStatement' ||
+        node.type === 'ForStatement'
+      ) {
+        pushScope()
+        const varDecl = node.type === 'ForStatement' ? node.init : node.left
+        if (varDecl && varDecl.type === 'VariableDeclaration') {
+          walkVariableDeclaration(varDecl)
+        }
+        if (node.body.type === 'BlockStatement') {
+          walkScope(node.body)
+        }
+        return
+      }
+
       // non-function block scopes
       if (node.type === 'BlockStatement' && !isFunctionType(parent!)) {
         pushScope()
@@ -292,7 +304,10 @@ export function transformDestructuredProps(
       if (
         (node.type === 'BlockStatement' && !isFunctionType(parent!)) ||
         isFunctionType(node) ||
-        node.type === 'CatchClause'
+        node.type === 'CatchClause' ||
+        node.type === 'ForOfStatement' ||
+        node.type === 'ForInStatement' ||
+        node.type === 'ForStatement'
       ) {
         popScope()
       }