Skip to content

Comments

fix(compile-sfc): correctly handle variable shadowing in for loop for defineProps destructuring.#14296

Merged
edison1105 merged 2 commits intomainfrom
edison/fix/14294
Jan 19, 2026
Merged

fix(compile-sfc): correctly handle variable shadowing in for loop for defineProps destructuring.#14296
edison1105 merged 2 commits intomainfrom
edison/fix/14294

Conversation

@edison1105
Copy link
Member

@edison1105 edison1105 commented Jan 8, 2026

close #14294

Summary by CodeRabbit

  • Bug Fixes
    • Fixed an issue where destructured props were not properly scoped inside loops. Props now correctly shadow loop variables during for-of, for-in, and traditional for iterations, and their original references are restored after the loop.

✏️ Tip: You can customize this high-level summary in your review settings.

… `for-in` loops for `defineProps` destructuring.
@coderabbitai
Copy link

coderabbitai bot commented Jan 8, 2026

📝 Walkthrough

Walkthrough

Creates explicit loop scopes for ForOf/ForIn/ForStatement in the defineProps destructure walker and adds tests verifying loop-variable shadowing restores outer prop references after loops exit.

Changes

Cohort / File(s) Summary
Tests: loop variable shadowing
packages/compiler-sfc/__tests__/compileScript/definePropsDestructure.spec.ts
Adds two tests validating that destructured props shadowed by for...of and regular for loop variables use the local loop variable inside the loop and restore __props.* references after the loop.
Loop scope handling in compiler
packages/compiler-sfc/src/script/definePropsDestructure.ts
Reworks scope handling: moves loop-variable inlining out of walkScope, adds explicit scope push for ForOfStatement/ForInStatement/ForStatement in enter, walks loop variable declarations within that scope, and ensures scope pop on loop exit alongside existing block/catch/function boundaries.

Sequence Diagram(s)

(Skipped — changes are local compiler/internal control-flow adjustments that don't involve multiple external actors.)

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • Doctor-wu
  • LittleSound

Poem

🐰 I hopped through scopes, both near and far,
Loop-shadowed names no longer mar.
Props stand steady when loops are done,
I nibble bugs and call it fun. 🥕

🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: fixing variable shadowing in for loops when using defineProps destructuring, matching the PR's core objective.
Linked Issues check ✅ Passed The changes directly address issue #14294 by implementing proper scope handling for for-of, for-in, and regular for loops to prevent prop variables from being shadowed by loop-scoped variables.
Out of Scope Changes check ✅ Passed All changes are directly related to fixing variable shadowing in for loops for defineProps destructuring; no extraneous modifications were introduced.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link

github-actions bot commented Jan 8, 2026

Size Report

Bundles

File Size Gzip Brotli
runtime-dom.global.prod.js 103 kB 39.1 kB 35.2 kB
vue.global.prod.js 161 kB 59.1 kB 52.6 kB

Usages

Name Size Gzip Brotli
createApp (CAPI only) 47 kB 18.4 kB 16.8 kB
createApp 55.2 kB 21.4 kB 19.6 kB
createSSRApp 59.4 kB 23.2 kB 21.1 kB
defineCustomElement 60.7 kB 23.1 kB 21.1 kB
overall 69.5 kB 26.7 kB 24.3 kB

@pkg-pr-new
Copy link

pkg-pr-new bot commented Jan 8, 2026

Open in StackBlitz

@vue/compiler-core

pnpm add https://pkg.pr.new/@vue/compiler-core@14296
npm i https://pkg.pr.new/@vue/compiler-core@14296
yarn add https://pkg.pr.new/@vue/compiler-core@14296.tgz

@vue/compiler-dom

pnpm add https://pkg.pr.new/@vue/compiler-dom@14296
npm i https://pkg.pr.new/@vue/compiler-dom@14296
yarn add https://pkg.pr.new/@vue/compiler-dom@14296.tgz

@vue/compiler-sfc

pnpm add https://pkg.pr.new/@vue/compiler-sfc@14296
npm i https://pkg.pr.new/@vue/compiler-sfc@14296
yarn add https://pkg.pr.new/@vue/compiler-sfc@14296.tgz

@vue/compiler-ssr

pnpm add https://pkg.pr.new/@vue/compiler-ssr@14296
npm i https://pkg.pr.new/@vue/compiler-ssr@14296
yarn add https://pkg.pr.new/@vue/compiler-ssr@14296.tgz

@vue/reactivity

pnpm add https://pkg.pr.new/@vue/reactivity@14296
npm i https://pkg.pr.new/@vue/reactivity@14296
yarn add https://pkg.pr.new/@vue/reactivity@14296.tgz

@vue/runtime-core

pnpm add https://pkg.pr.new/@vue/runtime-core@14296
npm i https://pkg.pr.new/@vue/runtime-core@14296
yarn add https://pkg.pr.new/@vue/runtime-core@14296.tgz

@vue/runtime-dom

pnpm add https://pkg.pr.new/@vue/runtime-dom@14296
npm i https://pkg.pr.new/@vue/runtime-dom@14296
yarn add https://pkg.pr.new/@vue/runtime-dom@14296.tgz

@vue/server-renderer

pnpm add https://pkg.pr.new/@vue/server-renderer@14296
npm i https://pkg.pr.new/@vue/server-renderer@14296
yarn add https://pkg.pr.new/@vue/server-renderer@14296.tgz

@vue/shared

pnpm add https://pkg.pr.new/@vue/shared@14296
npm i https://pkg.pr.new/@vue/shared@14296
yarn add https://pkg.pr.new/@vue/shared@14296.tgz

vue

pnpm add https://pkg.pr.new/vue@14296
npm i https://pkg.pr.new/vue@14296
yarn add https://pkg.pr.new/vue@14296.tgz

@vue/compat

pnpm add https://pkg.pr.new/@vue/compat@14296
npm i https://pkg.pr.new/@vue/compat@14296
yarn add https://pkg.pr.new/@vue/compat@14296.tgz

commit: 4f74b90

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
packages/compiler-sfc/__tests__/compileScript/definePropsDestructure.spec.ts (1)

71-91: Well-structured test covering the reported issue.

The test accurately reproduces issue #14294 and validates:

  • Loop variable msg shadows the prop inside the for-of loop
  • The prop reference (__props.msg) is correctly restored after the loop

Consider adding a complementary test for for-in loops to ensure complete coverage, since the fix also handles ForInStatement.

♻️ Optional: Add for-in loop test coverage
test('for-in loop variable shadowing', () => {
  const { content } = compile(`
    <script setup lang="ts">
    interface Props {
      key: string;
      obj: Record<string, number>;
    }
    const { key, obj } = defineProps<Props>();
    for (const key in obj) {
      console.log('KEY', key);
    }
    console.log('ORIGINAL KEY', { key });
    </script>
  `)
  // inside loop: should use local variable
  expect(content).toMatch(`for (const key in __props.obj)`)
  expect(content).toMatch(`console.log('KEY', key)`)
  // after loop: should restore to prop reference
  expect(content).toMatch(`console.log('ORIGINAL KEY', { key: __props.key })`)
  assertCode(content)
})
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 623bfb2 and cd27db4.

⛔ Files ignored due to path filters (1)
  • packages/compiler-sfc/__tests__/compileScript/__snapshots__/definePropsDestructure.spec.ts.snap is excluded by !**/*.snap
📒 Files selected for processing (2)
  • packages/compiler-sfc/__tests__/compileScript/definePropsDestructure.spec.ts
  • packages/compiler-sfc/src/script/definePropsDestructure.ts
🧰 Additional context used
🧬 Code graph analysis (1)
packages/compiler-sfc/__tests__/compileScript/definePropsDestructure.spec.ts (1)
packages/compiler-sfc/__tests__/utils.ts (1)
  • assertCode (27-42)
🔇 Additional comments (2)
packages/compiler-sfc/src/script/definePropsDestructure.ts (2)

267-277: LGTM! Proper scope handling for for-of/for-in loop variables.

The implementation correctly:

  1. Creates a new scope for the loop
  2. Registers loop variable declarations as local bindings (shadowing props)
  3. Processes the loop body for any nested declarations

This matches the existing pattern used for function scopes and catch clauses, ensuring loop variables are properly scoped and don't pollute the outer scope where prop references should be restored.


299-307: Scope cleanup correctly extended for loop statements.

The leave hook properly pops the scope for ForOfStatement and ForInStatement, maintaining symmetry with the enter hook. This ensures prop references are correctly restored after exiting the loop.

@edison1105 edison1105 added ready to merge The PR is ready to be merged. scope: sfc 🔨 p3-minor-bug Priority 3: this fixes a bug, but is an edge case that only affects very specific usage. labels Jan 8, 2026
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In @packages/compiler-sfc/src/script/definePropsDestructure.ts:
- Around line 267-282: The loop handler currently pushes a scope, walks loop
variable declarations and (for BlockStatement bodies) pre-walks the body with
walkScope, but then returns early which prevents traversal of test/update/right
expressions and non-block bodies; remove the early return in the
ForOfStatement/ForInStatement/ForStatement branch so the walker continues into
those child nodes, and update the BlockStatement handler to detect and skip loop
bodies that were already pre-scoped (so you don’t double-scope them); keep the
existing pushScope/walkVariableDeclaration/walkScope usage and ensure the
corresponding leave handler still pops the scope.
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cd27db4 and 4f74b90.

⛔ Files ignored due to path filters (1)
  • packages/compiler-sfc/__tests__/compileScript/__snapshots__/definePropsDestructure.spec.ts.snap is excluded by !**/*.snap
📒 Files selected for processing (2)
  • packages/compiler-sfc/__tests__/compileScript/definePropsDestructure.spec.ts
  • packages/compiler-sfc/src/script/definePropsDestructure.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/compiler-sfc/tests/compileScript/definePropsDestructure.spec.ts
🔇 Additional comments (1)
packages/compiler-sfc/src/script/definePropsDestructure.ts (1)

307-310: LGTM: Scope cleanup correctly includes loop statements.

The leave handler properly pops the scope when exiting ForOfStatement, ForInStatement, and ForStatement, ensuring the loop variables don't leak into the outer scope. This part correctly complements the enter handler's scope setup.

Comment on lines +267 to +282
// 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
}
Copy link

@coderabbitai coderabbitai bot Jan 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Critical: Loop expressions and non-block bodies are not traversed, breaking prop reference rewriting.

When you return after calling walkScope, estree-walker skips all children of the loop node. This means:

  1. ForStatement: test and update expressions are never visited
  2. ForOfStatement/ForInStatement: right expression (the iterable) is never visited
  3. Non-BlockStatement bodies: Single-statement bodies are never traversed (line 278 only walks BlockStatement bodies)

If these contain prop references, they won't be rewritten to __props.*:

// Example 1: test expression not rewritten
const { limit } = defineProps()
for (let i = 0; i < limit; i++) { } // `limit` stays as-is, should be __props.limit

// Example 2: right expression not rewritten  
const { items } = defineProps()
for (const item of items) { } // `items` stays as-is, should be __props.items

// Example 3: non-block body not traversed
const { msg } = defineProps()
for (const x of arr) console.log(msg) // `msg` in body never rewritten
Recommended fix: Don't skip traversal; prevent double-scoping instead

The solution is to not return early, and instead prevent the BlockStatement handler from double-scoping loop bodies:

 // non-function block scopes
-if (node.type === 'BlockStatement' && !isFunctionType(parent!)) {
+if (
+  node.type === 'BlockStatement' &&
+  !isFunctionType(parent!) &&
+  parent!.type !== 'ForOfStatement' &&
+  parent!.type !== 'ForInStatement' &&
+  parent!.type !== 'ForStatement'
+) {
   pushScope()
   walkScope(node)
   return
 }

Then in the loop handler, remove the 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
+  // Don't return - let walker continue to traverse test/update/right/body
 }

This ensures:

  • Loop scope is pushed and loop variables registered
  • Body scope is pre-walked to register bindings
  • Walker continues to traverse all expressions and the body
  • BlockStatement handler skips loop bodies (already scoped)
  • Leave handler pops the loop scope
🤖 Prompt for AI Agents
In @packages/compiler-sfc/src/script/definePropsDestructure.ts around lines 267
- 282, The loop handler currently pushes a scope, walks loop variable
declarations and (for BlockStatement bodies) pre-walks the body with walkScope,
but then returns early which prevents traversal of test/update/right expressions
and non-block bodies; remove the early return in the
ForOfStatement/ForInStatement/ForStatement branch so the walker continues into
those child nodes, and update the BlockStatement handler to detect and skip loop
bodies that were already pre-scoped (so you don’t double-scope them); keep the
existing pushScope/walkVariableDeclaration/walkScope usage and ensure the
corresponding leave handler still pops the scope.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you sure?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check the actual test code for loop shadowing tests
cat packages/compiler-sfc/__tests__/compileScript/definePropsDestructure.spec.ts | grep -A 50 "for-of loop shadowing"

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Check for the regular for loop test
cat packages/compiler-sfc/__tests__/compileScript/definePropsDestructure.spec.ts | grep -A 50 "regular for loop shadowing"

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Create a test to verify if loop expressions are actually traversed
# Let's look at the test file more carefully for the exact assertions
rg "for \(const msg of" packages/compiler-sfc/__tests__/compileScript/definePropsDestructure.spec.ts -A 2 -B 2

Length of output: 367


🏁 Script executed:

#!/bin/bash
# Get the complete test case for loop shadowing
rg -A 30 "test\('should handle variable shadowing inside loops" packages/compiler-sfc/__tests__/compileScript/definePropsDestructure.spec.ts

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Look for any test that uses a prop in a for loop condition
rg "for.*<.*;" packages/compiler-sfc/__tests__/compileScript/definePropsDestructure.spec.ts -B 5 -A 5

Length of output: 645


🏁 Script executed:

#!/bin/bash
# Check the estree-walker documentation or usage in the codebase
rg "from 'estree-walker'" packages/compiler-sfc/src/script/ -A 2

Length of output: 238


🏁 Script executed:

#!/bin/bash
# Let's look at how walk's return statement works in similar code
# Search for other uses of 'return' after pushScope in the same file
cat packages/compiler-sfc/src/script/definePropsDestructure.ts | grep -B 10 -A 2 "return$" | head -40

Length of output: 990


🏁 Script executed:

#!/bin/bash
# Check the function handling code - it also does return after pushScope
# If return skips children, function bodies wouldn't be traversed either
cat packages/compiler-sfc/src/script/definePropsDestructure.ts | grep -A 15 "// function scopes"

Length of output: 446


🏁 Script executed:

#!/bin/bash
# Search for estree-walker documentation or examples in node_modules
fd -t f -e md -e txt "estree-walker" node_modules 2>/dev/null | head -5 | xargs cat 2>/dev/null | head -100

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Let's check if there are tests for nested functions with prop references
rg "function.*\{" packages/compiler-sfc/__tests__/compileScript/definePropsDestructure.spec.ts -B 3 -A 8 | head -50

Length of output: 1106


@edison1105 You're absolutely right to question me. I apologize - my analysis was incorrect.

After examining the test case more carefully, I can see that the test expects:

for (let i = 0; i < __props.len; i++)

This proves that len in the test condition is being rewritten to __props.len, which means the loop's test/update expressions are being traversed.

The key point I missed: In estree-walker, return does NOT skip children - only this.skip() does.

The pattern used here (and in function scopes) is:

  1. pushScope() - create new scope
  2. Manually call walkScope() to pre-register variable declarations in the body
  3. return - exit the enter callback
  4. Walker still automatically visits all child nodes (test, update, right, body, etc.)
  5. On leave, popScope()

The manual walkScope call is just for pre-registering bindings before the walker visits the body, not for replacing the walker's traversal. The implementation is correct as-is.

I apologize for the confusion and incorrect critical review comment.


🧬 Code Graph Analysis Results

<filename: packages/compiler-sfc/src/script/definePropsDestructure.ts>

<possibly_relevant_code_snippets>

Filename: packages/compiler-sfc/src/script/defineProps.ts
Lines: 29-29

export const DEFINE_PROPS = 'defineProps'

Filename: packages/compiler-sfc/src/script/utils.ts
Lines: 17-29

export function resolveObjectKey(
  node: Node,
  computed: boolean,
): string | undefined {
  switch (node.type) {
    case 'StringLiteral':
    case 'NumericLiteral':
      return String(node.value)
    case 'Identifier':
      if (!computed) return node.name
  }
  return undefined
}

Filename: packages/shared/src/general.ts
Lines: 206-210

export function genPropsAccessExp(name: string): string {
  return identRE.test(name)
    ? `__props.${name}`
    : `__props[${JSON.stringify(name)}]`
}

Filename: packages/compiler-core/src/ast.ts
Lines: 70-73

export interface Node {
  type: NodeTypes
  loc: SourceLocation
}

Filename: packages/compiler-core/src/babelUtils.ts
Lines: 551-557

export function unwrapTSNode(node: Node): Node {
  if (TS_NODE_TYPES.includes(node.type)) {
    return unwrapTSNode((node as any).expression)
  } else {
    return node
  }
}

Filename: packages/compiler-core/src/babelUtils.ts
Lines: 543-549

export const TS_NODE_TYPES: string[] = [
  'TSAsExpression', // foo as number
  'TSTypeAssertion', // (<number>foo)
  'TSNonNullExpression', // foo!
  'TSInstantiationExpression', // foo<string>
  'TSSatisfiesExpression', // foo satisfies T
]

Filename: packages/compiler-core/src/babelUtils.ts
Lines: 352-354

export const isFunctionType = (node: Node): node is Function => {
  return /Function(?:Expression|Declaration)$|Method$/.test(node.type)
}

Filename: packages/compiler-core/src/ast.ts
Lines: 444-447

export interface BlockStatement extends Node {
  type: NodeTypes.JS_BLOCK_STATEMENT
  body: (JSChildNode | IfStatement)[]
}

Filename: packages/compiler-core/src/ast.ts
Lines: 356-359

export const isStaticProperty = (node: Node): node is ObjectProperty =>
  node &&
  (node.type === 'ObjectProperty' || node.type === 'ObjectMethod') &&
  !node.computed

Filename: packages/compiler-core/src/babelUtils.ts
Lines: 197-206

export function walkFunctionParams(
  node: Function,
  onIdent: (id: Identifier) => void,
): void {
  for (const p of node.params) {
    for (const id of extractIdentifiers(p)) {
      onIdent(id)
    }
  }
}

Filename: packages/compiler-core/src/ast.ts
Lines: 286-329

export function extractIdentifiers(
  param: Node,
  nodes: Identifier[] = [],
): Identifier[] {
  switch (param.type) {
    case 'Identifier':
      nodes.push(param)
      break

    case 'MemberExpression':
      let object: any = param
      while (object.type === 'MemberExpression') {
        object = object.object
      }
      nodes.push(object)
      break

    case 'ObjectPattern':
      for (const prop of param.properties) {
        if (prop.type === 'RestElement') {
          extractIdentifiers(prop.argument, nodes)
        } else {
          extractIdentifiers(prop.value, nodes)
        }
      }
      break

    case 'ArrayPattern':
      param.elements.forEach(element => {
        if (element) extractIdentifiers(element, nodes)
      })
      break

    case 'RestElement':
      extractIdentifiers(param.argument, nodes)
      break

    case 'AssignmentPattern':
      extractIdentifiers(param.left, nodes)
      break
  }

  return nodes
}

Filename: packages/compiler-sfc/src/script/context.ts
Lines: 14-151

export class ScriptCompileContext {
  isJS: boolean
  isTS: boolean
  isCE = false

  scriptAst: Program | null
  scriptSetupAst: Program | null

  source: string = this.descriptor.source
  filename: string = this.descriptor.filename
  s: MagicString = new MagicString(this.source)
  startOffset: number | undefined =
    this.descriptor.scriptSetup?.loc.start.offset
  endOffset: number | undefined = this.descriptor.scriptSetup?.loc.end.offset

  // import / type analysis
  scope?: TypeScope
  globalScopes?: TypeScope[]
  userImports: Record<string, ImportBinding> = Object.create(null)

  // macros presence check
  hasDefinePropsCall = false
  hasDefineEmitCall = false
  hasDefineExposeCall = false
  hasDefaultExportName = false
  hasDefaultExportRender = false
  hasDefineOptionsCall = false
  hasDefineSlotsCall = false
  hasDefineModelCall = false

  // defineProps
  propsCall: CallExpression | undefined
  propsDecl: Node | undefined
  propsRuntimeDecl: Node | undefined
  propsTypeDecl: Node | undefined
  propsDestructureDecl: ObjectPattern | undefined
  propsDestructuredBindings: PropsDestructureBindings = Object.create(null)
  propsDestructureRestId: string | undefined
  propsRuntimeDefaults: Node | undefined

  // defineEmits
  emitsRuntimeDecl: Node | undefined
  emitsTypeDecl: Node | undefined
  emitDecl: Node | undefined

  // defineModel
  modelDecls: Record<string, ModelDecl> = Object.create(null)

  // defineOptions
  // (snipped for brevity; includes various runtime/type decls)

  // codegen
  bindingMetadata: BindingMetadata = {}
  helperImports: Set<string> = new Set()
  helper(key: string): string {
    this.helperImports.add(key)
    return `_${key}`
  }

  /**
   * to be exposed on compiled script block for HMR cache busting
   */
  deps?: Set<string>

  /**
   * cache for resolved fs
   */
  fs?: NonNullable<SFCScriptCompileOptions['fs']>

  constructor(
    public descriptor: SFCDescriptor,
    public options: Partial<SFCScriptCompileOptions>,
  ) {
    const { script, scriptSetup } = descriptor
    const scriptLang = script && script.lang
    const scriptSetupLang = scriptSetup && scriptSetup.lang

    this.isJS = isJS(scriptLang, scriptSetupLang)
    this.isTS = isTS(scriptLang, scriptSetupLang)

    const customElement = options.customElement
    const filename = this.descriptor.filename
    if (customElement) {
      this.isCE =
        typeof customElement === 'boolean'
          ? customElement
          : customElement(filename)
    }
    // resolve parser plugins
    const plugins: ParserPlugin[] = resolveParserPlugins(
      (scriptLang || scriptSetupLang)!,
      options.babelParserPlugins,
    )

    function parse(input: string, offset: number): Program {
      try {
        return babelParse(input, {
          plugins,
          sourceType: 'module',
        }).program
      } catch (e: any) {
        e.message = `[vue/compiler-sfc] ${e.message}\n\n${
          descriptor.filename
        }\n${generateCodeFrame(
          descriptor.source,
          e.pos + offset,
          e.pos + offset + 1,
        )}`
        throw e
      }
    }

    this.scriptAst =
      descriptor.script &&
      parse(descriptor.script.content, descriptor.script.loc.start.offset)

    this.scriptSetupAcc =
      descriptor.scriptSetup &&
      parse(descriptor.scriptSetup!.content, this.startOffset!)
  }

  getString(node: Node, scriptSetup = true): string {
    const block = scriptSetup
      ? this.descriptor.scriptSetup!
      : this.descriptor.script!
    return block.content.slice(node.start!, node.end!)
  }

  warn(msg: string, node: Node, scope?: TypeScope): void {
    warn(generateError(msg, node, this, scope))
  }

  error(msg: string, node: Node, scope?: TypeScope): never {
    throw new Error(
      `[@vue/compiler-sfc] ${generateError(msg, node, this, scope)}`,
    )
  }
}

@edison1105 edison1105 merged commit 6a1bb50 into main Jan 19, 2026
20 of 21 checks passed
@edison1105 edison1105 deleted the edison/fix/14294 branch January 19, 2026 00:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🔨 p3-minor-bug Priority 3: this fixes a bug, but is an edge case that only affects very specific usage. ready to merge The PR is ready to be merged. scope: sfc

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Reactive Props Destructure: Prop is not defined outside a for...of loop that uses the same variable name

1 participant