]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
feat(compiler-sfc): enable reactive props destructure by default and deprecate withDe...
authorEvan You <yyx990803@gmail.com>
Thu, 30 Mar 2023 03:58:16 +0000 (11:58 +0800)
committerGitHub <noreply@github.com>
Thu, 30 Mar 2023 03:58:16 +0000 (11:58 +0800)
packages/compiler-core/src/babelUtils.ts
packages/compiler-sfc/__tests__/__snapshots__/compileScriptPropsDestructure.spec.ts.snap [new file with mode: 0644]
packages/compiler-sfc/__tests__/compileScriptPropsDestructure.spec.ts [moved from packages/compiler-sfc/__tests__/compileScriptPropsTransform.spec.ts with 86% similarity]
packages/compiler-sfc/src/compileScript.ts
packages/compiler-sfc/src/compileScriptPropsDestructure.ts [new file with mode: 0644]
packages/runtime-core/src/apiSetupHelpers.ts

index 52175ef48b16c23ecbc7e465ca0f4faf499db81c..b58d9a0648faf9c25283a18fa83da9450aad8e13 100644 (file)
@@ -9,7 +9,8 @@ import type {
   Program,
   ImportDefaultSpecifier,
   ImportNamespaceSpecifier,
-  ImportSpecifier
+  ImportSpecifier,
+  CallExpression
 } from '@babel/types'
 import { walk } from 'estree-walker'
 
@@ -449,3 +450,18 @@ export function unwrapTSNode(node: Node): Node {
     return node
   }
 }
+
+export function isCallOf(
+  node: Node | null | undefined,
+  test: string | ((id: string) => boolean) | null | undefined
+): node is CallExpression {
+  return !!(
+    node &&
+    test &&
+    node.type === 'CallExpression' &&
+    node.callee.type === 'Identifier' &&
+    (typeof test === 'string'
+      ? node.callee.name === test
+      : test(node.callee.name))
+  )
+}
diff --git a/packages/compiler-sfc/__tests__/__snapshots__/compileScriptPropsDestructure.spec.ts.snap b/packages/compiler-sfc/__tests__/__snapshots__/compileScriptPropsDestructure.spec.ts.snap
new file mode 100644 (file)
index 0000000..525f4b6
--- /dev/null
@@ -0,0 +1,210 @@
+// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
+
+exports[`sfc props transform > aliasing 1`] = `
+"import { toDisplayString as _toDisplayString } from \\"vue\\"
+
+
+export default {
+  props: ['foo'],
+  setup(__props) {
+
+      
+      let x = foo
+      let y = __props.foo
+      
+return (_ctx, _cache) => {
+  return _toDisplayString(__props.foo + __props.foo)
+}
+}
+
+}"
+`;
+
+exports[`sfc props transform > basic usage 1`] = `
+"import { toDisplayString as _toDisplayString } from \\"vue\\"
+
+
+export default {
+  props: ['foo'],
+  setup(__props) {
+
+      
+      console.log(__props.foo)
+      
+return (_ctx, _cache) => {
+  return _toDisplayString(__props.foo)
+}
+}
+
+}"
+`;
+
+exports[`sfc props transform > computed static key 1`] = `
+"import { toDisplayString as _toDisplayString } from \\"vue\\"
+
+
+export default {
+  props: ['foo'],
+  setup(__props) {
+
+    
+    console.log(__props.foo)
+    
+return (_ctx, _cache) => {
+  return _toDisplayString(__props.foo)
+}
+}
+
+}"
+`;
+
+exports[`sfc props transform > default values w/ array runtime declaration 1`] = `
+"import { mergeDefaults as _mergeDefaults } from 'vue'
+
+export default {
+  props: _mergeDefaults(['foo', 'bar', 'baz'], {
+  foo: 1,
+  bar: () => ({}),
+  func: () => {}, __skip_func: true
+}),
+  setup(__props) {
+
+      
+      
+return () => {}
+}
+
+}"
+`;
+
+exports[`sfc props transform > default values w/ object runtime declaration 1`] = `
+"import { mergeDefaults as _mergeDefaults } from 'vue'
+
+export default {
+  props: _mergeDefaults({ foo: Number, bar: Object, func: Function, ext: null }, {
+  foo: 1,
+  bar: () => ({}),
+  func: () => {}, __skip_func: true,
+  ext: x, __skip_ext: true
+}),
+  setup(__props) {
+
+      
+      
+return () => {}
+}
+
+}"
+`;
+
+exports[`sfc props transform > default values w/ type declaration 1`] = `
+"import { defineComponent as _defineComponent } from 'vue'
+
+export default /*#__PURE__*/_defineComponent({
+  props: {
+    foo: { type: Number, required: false, default: 1 },
+    bar: { type: Object, required: false, default: () => ({}) },
+    func: { type: Function, required: false, default: () => {} }
+  },
+  setup(__props: any) {
+
+      
+      
+return () => {}
+}
+
+})"
+`;
+
+exports[`sfc props transform > default values w/ type declaration, prod mode 1`] = `
+"import { defineComponent as _defineComponent } from 'vue'
+
+export default /*#__PURE__*/_defineComponent({
+  props: {
+    foo: { default: 1 },
+    bar: { default: () => ({}) },
+    baz: null,
+    boola: { type: Boolean },
+    boolb: { type: [Boolean, Number] },
+    func: { type: Function, default: () => {} }
+  },
+  setup(__props: any) {
+
+      
+      
+return () => {}
+}
+
+})"
+`;
+
+exports[`sfc props transform > multiple variable declarations 1`] = `
+"import { toDisplayString as _toDisplayString, openBlock as _openBlock, createElementBlock as _createElementBlock } from \\"vue\\"
+
+
+export default {
+  props: ['foo'],
+  setup(__props) {
+
+      const bar = 'fish', hello = 'world'
+      
+return (_ctx, _cache) => {
+  return (_openBlock(), _createElementBlock(\\"div\\", null, _toDisplayString(__props.foo) + \\" \\" + _toDisplayString(hello) + \\" \\" + _toDisplayString(bar), 1 /* TEXT */))
+}
+}
+
+}"
+`;
+
+exports[`sfc props transform > nested scope 1`] = `
+"export default {
+  props: ['foo', 'bar'],
+  setup(__props) {
+
+      
+      function test(foo) {
+        console.log(foo)
+        console.log(__props.bar)
+      }
+      
+return () => {}
+}
+
+}"
+`;
+
+exports[`sfc props transform > non-identifier prop names 1`] = `
+"import { toDisplayString as _toDisplayString } from \\"vue\\"
+
+
+export default {
+  props: { 'foo.bar': Function },
+  setup(__props) {
+
+      
+      let x = __props[\\"foo.bar\\"]
+      
+return (_ctx, _cache) => {
+  return _toDisplayString(__props[\\"foo.bar\\"])
+}
+}
+
+}"
+`;
+
+exports[`sfc props transform > rest spread 1`] = `
+"import { createPropsRestProxy as _createPropsRestProxy } from 'vue'
+
+export default {
+  props: ['foo', 'bar', 'baz'],
+  setup(__props) {
+
+const rest = _createPropsRestProxy(__props, [\\"foo\\",\\"bar\\"]);
+
+      
+      
+return () => {}
+}
+
+}"
+`;
similarity index 86%
rename from packages/compiler-sfc/__tests__/compileScriptPropsTransform.spec.ts
rename to packages/compiler-sfc/__tests__/compileScriptPropsDestructure.spec.ts
index 9fe711fbc3da90396952ec7a02dc2d64de757cf6..346f95a5c7a8babb6f17e33366e7f637f4ef5503 100644 (file)
@@ -6,7 +6,6 @@ describe('sfc props transform', () => {
   function compile(src: string, options?: Partial<SFCScriptCompileOptions>) {
     return compileSFCScript(src, {
       inlineTemplate: true,
-      reactivityTransform: true,
       ...options
     })
   }
@@ -211,23 +210,6 @@ describe('sfc props transform', () => {
     })
   })
 
-  test('$$() escape', () => {
-    const { content } = compile(`
-      <script setup>
-      const { foo, bar: baz } = defineProps(['foo'])
-      console.log($$(foo))
-      console.log($$(baz))
-      $$({ foo, baz })
-      </script>
-    `)
-    expect(content).toMatch(`const __props_foo = _toRef(__props, 'foo')`)
-    expect(content).toMatch(`const __props_bar = _toRef(__props, 'bar')`)
-    expect(content).toMatch(`console.log((__props_foo))`)
-    expect(content).toMatch(`console.log((__props_bar))`)
-    expect(content).toMatch(`({ foo: __props_foo, baz: __props_bar })`)
-    assertCode(content)
-  })
-
   // #6960
   test('computed static key', () => {
     const { content, bindings } = compile(`
@@ -292,7 +274,7 @@ describe('sfc props transform', () => {
       ).toThrow(`cannot reference locally declared variables`)
     })
 
-    test('should error if assignment to constant variable', () => {
+    test('should error if assignment to destructured prop binding', () => {
       expect(() =>
         compile(
           `<script setup>
@@ -300,7 +282,49 @@ describe('sfc props transform', () => {
           foo = 'bar'
           </script>`
         )
-      ).toThrow(`Assignment to constant variable.`)
+      ).toThrow(`Cannot assign to destructured props`)
+
+      expect(() =>
+        compile(
+          `<script setup>
+          let { foo } = defineProps(['foo'])
+          foo = 'bar'
+          </script>`
+        )
+      ).toThrow(`Cannot assign to destructured props`)
+    })
+
+    test('should error when watching destructured prop', () => {
+      expect(() =>
+        compile(
+          `<script setup>
+        import { watch } from 'vue'
+        const { foo } = defineProps(['foo'])
+        watch(foo, () => {})
+        </script>`
+        )
+      ).toThrow(`"foo" is a destructured prop and cannot be directly watched.`)
+
+      expect(() =>
+        compile(
+          `<script setup>
+        import { watch as w } from 'vue'
+        const { foo } = defineProps(['foo'])
+        w(foo, () => {})
+        </script>`
+        )
+      ).toThrow(`"foo" is a destructured prop and cannot be directly watched.`)
+    })
+
+    // not comprehensive, but should help for most common cases
+    test('should error if default value type does not match declared type', () => {
+      expect(() =>
+        compile(
+          `<script setup lang="ts">
+        const { foo = 'hello' } = defineProps<{ foo?: number }>()
+        </script>`
+        )
+      ).toThrow(`Default value of prop "foo" does not match declared type.`)
     })
   })
 })
index d9c59ab665ef632d7f506524eea630940ec506fe..96ea7df18bc063862cda0a5d15a356dc5851a031 100644 (file)
@@ -11,7 +11,8 @@ import {
   isFunctionType,
   walkIdentifiers,
   getImportedName,
-  unwrapTSNode
+  unwrapTSNode,
+  isCallOf
 } from '@vue/compiler-dom'
 import { DEFAULT_FILENAME, SFCDescriptor, SFCScriptBlock } from './parse'
 import {
@@ -59,6 +60,7 @@ import { warnOnce } from './warn'
 import { rewriteDefaultAST } from './rewriteDefault'
 import { createCache } from './cache'
 import { shouldTransform, transformAST } from '@vue/reactivity-transform'
+import { transformDestructuredProps } from './compileScriptPropsDestructure'
 
 // Special compiler macros
 const DEFINE_PROPS = 'defineProps'
@@ -132,6 +134,14 @@ export interface ImportBinding {
   isUsedInTemplate: boolean
 }
 
+export type PropsDestructureBindings = Record<
+  string, // public prop key
+  {
+    local: string // local identifier, may be different
+    default?: Expression
+  }
+>
+
 type FromNormalScript<T> = T & { __fromNormalScript?: boolean | null }
 type PropsDeclType = FromNormalScript<TSTypeLiteral | TSInterfaceBody>
 type EmitsDeclType = FromNormalScript<
@@ -151,7 +161,6 @@ export function compileScript(
   // feature flags
   // TODO remove support for deprecated options when out of experimental
   const enableReactivityTransform = !!options.reactivityTransform
-  const enablePropsTransform = !!options.reactivityTransform
   const isProd = !!options.isProd
   const genSourceMap = options.sourceMap !== false
   const hoistStatic = options.hoistStatic !== false && !script
@@ -310,14 +319,8 @@ export function compileScript(
   // record declared types for runtime props type generation
   const declaredTypes: Record<string, string[]> = {}
   // props destructure data
-  const propsDestructuredBindings: Record<
-    string, // public prop key
-    {
-      local: string // local identifier, may be different
-      default?: Expression
-      isConst: boolean
-    }
-  > = Object.create(null)
+  const propsDestructuredBindings: PropsDestructureBindings =
+    Object.create(null)
 
   // magic-string state
   const s = new MagicString(source)
@@ -410,11 +413,7 @@ export function compileScript(
     }
   }
 
-  function processDefineProps(
-    node: Node,
-    declId?: LVal,
-    declKind?: VariableDeclaration['kind']
-  ): boolean {
+  function processDefineProps(node: Node, declId?: LVal): boolean {
     if (!isCallOf(node, DEFINE_PROPS)) {
       return false
     }
@@ -452,10 +451,9 @@ export function compileScript(
     }
 
     if (declId) {
-      const isConst = declKind === 'const'
-      if (enablePropsTransform && declId.type === 'ObjectPattern') {
+      // handle props destructure
+      if (declId.type === 'ObjectPattern') {
         propsDestructureDecl = declId
-        // props destructure - handle compilation sugar
         for (const prop of declId.properties) {
           if (prop.type === 'ObjectProperty') {
             const propKey = resolveObjectKey(prop.key, prop.computed)
@@ -479,14 +477,12 @@ export function compileScript(
               // store default value
               propsDestructuredBindings[propKey] = {
                 local: left.name,
-                default: right,
-                isConst
+                default: right
               }
             } else if (prop.value.type === 'Identifier') {
               // simple destructure
               propsDestructuredBindings[propKey] = {
-                local: prop.value.name,
-                isConst
+                local: prop.value.name
               }
             } else {
               error(
@@ -515,7 +511,12 @@ export function compileScript(
     if (!isCallOf(node, WITH_DEFAULTS)) {
       return false
     }
-    if (processDefineProps(node.arguments[0], declId, declKind)) {
+    warnOnce(
+      `withDefaults() has been deprecated. ` +
+        `Props destructure is now reactive by default - ` +
+        `use destructure with default values instead.`
+    )
+    if (processDefineProps(node.arguments[0], declId)) {
       if (propsRuntimeDecl) {
         error(
           `${WITH_DEFAULTS} can only be used with type-based ` +
@@ -943,7 +944,23 @@ export function compileScript(
         defaultVal.start!,
         defaultVal.end!
       )
+
       const unwrapped = unwrapTSNode(defaultVal)
+
+      if (
+        inferredType &&
+        inferredType.length &&
+        !inferredType.includes(UNKNOWN_TYPE)
+      ) {
+        const valueType = inferValueType(unwrapped)
+        if (valueType && !inferredType.includes(valueType)) {
+          error(
+            `Default value of prop "${key}" does not match declared type.`,
+            unwrapped
+          )
+        }
+      }
+
       // If the default value is a function or is an identifier referencing
       // external value, skip factory wrap. This is needed when using
       // destructure w/ runtime declaration since we cannot safely infer
@@ -951,10 +968,12 @@ export function compileScript(
       const needSkipFactory =
         !inferredType &&
         (isFunctionType(unwrapped) || unwrapped.type === 'Identifier')
+
       const needFactoryWrap =
         !needSkipFactory &&
         !isLiteralNode(unwrapped) &&
         !inferredType?.includes('Function')
+
       return {
         valueString: needFactoryWrap ? `() => (${value})` : value,
         needSkipFactory
@@ -1220,6 +1239,7 @@ export function compileScript(
     }
 
     // apply reactivity transform
+    // TODO remove in 3.4
     if (enableReactivityTransform && shouldTransform(script.content)) {
       const { rootRefs, importedHelpers } = transformAST(
         scriptAst,
@@ -1300,7 +1320,7 @@ export function compileScript(
 
           // defineProps / defineEmits
           const isDefineProps =
-            processDefineProps(init, decl.id, node.kind) ||
+            processDefineProps(init, decl.id) ||
             processWithDefaults(init, decl.id, node.kind)
           const isDefineEmits = processDefineEmits(init, decl.id)
           if (isDefineProps || isDefineEmits) {
@@ -1416,19 +1436,30 @@ export function compileScript(
     }
   }
 
-  // 3. Apply reactivity transform
+  // 3.1 props destructure transform
+  if (propsDestructureDecl) {
+    transformDestructuredProps(
+      scriptSetupAst,
+      s,
+      startOffset,
+      propsDestructuredBindings,
+      error,
+      vueImportAliases.watch
+    )
+  }
+
+  // 3.2 Apply reactivity transform
+  // TODO remove in 3.4
   if (
-    (enableReactivityTransform &&
-      // normal <script> had ref bindings that maybe used in <script setup>
-      (refBindings || shouldTransform(scriptSetup.content))) ||
-    propsDestructureDecl
+    enableReactivityTransform &&
+    // normal <script> had ref bindings that maybe used in <script setup>
+    (refBindings || shouldTransform(scriptSetup.content))
   ) {
     const { rootRefs, importedHelpers } = transformAST(
       scriptSetupAst,
       s,
       startOffset,
-      refBindings,
-      propsDestructuredBindings
+      refBindings
     )
     refBindings = refBindings ? [...refBindings, ...rootRefs] : rootRefs
     for (const h of importedHelpers) {
@@ -1444,7 +1475,7 @@ export function compileScript(
     extractRuntimeEmits(emitsTypeDecl, typeDeclaredEmits)
   }
 
-  // 5. check useOptions args to make sure it doesn't reference setup scope
+  // 5. check macro args to make sure it doesn't reference setup scope
   // variables
   checkInvalidScopeReference(propsRuntimeDecl, DEFINE_PROPS)
   checkInvalidScopeReference(propsRuntimeDefaults, DEFINE_PROPS)
@@ -2219,6 +2250,27 @@ function inferEnumType(node: TSEnumDeclaration): string[] {
   return types.size ? [...types] : ['Number']
 }
 
+// non-comprehensive, best-effort type infernece for a runtime value
+// this is used to catch default value / type declaration mismatches
+// when using props destructure.
+function inferValueType(node: Node): string | undefined {
+  switch (node.type) {
+    case 'StringLiteral':
+      return 'String'
+    case 'NumericLiteral':
+      return 'Number'
+    case 'BooleanLiteral':
+      return 'Boolean'
+    case 'ObjectExpression':
+      return 'Object'
+    case 'ArrayExpression':
+      return 'Array'
+    case 'FunctionExpression':
+    case 'ArrowFunctionExpression':
+      return 'Function'
+  }
+}
+
 function extractRuntimeEmits(
   node: TSFunctionType | TSTypeLiteral | TSInterfaceBody,
   emits: Set<string>
@@ -2275,21 +2327,6 @@ function genRuntimeEmits(emits: Set<string>) {
     : ``
 }
 
-function isCallOf(
-  node: Node | null | undefined,
-  test: string | ((id: string) => boolean) | null | undefined
-): node is CallExpression {
-  return !!(
-    node &&
-    test &&
-    node.type === 'CallExpression' &&
-    node.callee.type === 'Identifier' &&
-    (typeof test === 'string'
-      ? node.callee.name === test
-      : test(node.callee.name))
-  )
-}
-
 function canNeverBeRef(node: Node, userReactiveImport?: string): boolean {
   if (isCallOf(node, userReactiveImport)) {
     return true
diff --git a/packages/compiler-sfc/src/compileScriptPropsDestructure.ts b/packages/compiler-sfc/src/compileScriptPropsDestructure.ts
new file mode 100644 (file)
index 0000000..bc38912
--- /dev/null
@@ -0,0 +1,236 @@
+import {
+  Node,
+  Identifier,
+  BlockStatement,
+  Program,
+  VariableDeclaration
+} from '@babel/types'
+import MagicString from 'magic-string'
+import { walk } from 'estree-walker'
+import {
+  extractIdentifiers,
+  isFunctionType,
+  isInDestructureAssignment,
+  isReferencedIdentifier,
+  isStaticProperty,
+  walkFunctionParams,
+  isCallOf,
+  unwrapTSNode
+} from '@vue/compiler-core'
+import { hasOwn, genPropsAccessExp } from '@vue/shared'
+import { PropsDestructureBindings } from './compileScript'
+
+/**
+ * true -> prop binding
+ * false -> local binding
+ */
+type Scope = Record<string, boolean>
+
+export function transformDestructuredProps(
+  ast: Program,
+  s: MagicString,
+  offset = 0,
+  knownProps: PropsDestructureBindings,
+  error: (msg: string, node: Node, end?: number) => never,
+  watchMethodName = 'watch'
+) {
+  const rootScope: Scope = {}
+  const scopeStack: Scope[] = [rootScope]
+  let currentScope: Scope = rootScope
+  const excludedIds = new WeakSet<Identifier>()
+  const parentStack: Node[] = []
+  const propsLocalToPublicMap: Record<string, string> = Object.create(null)
+
+  for (const key in knownProps) {
+    const { local } = knownProps[key]
+    rootScope[local] = true
+    propsLocalToPublicMap[local] = key
+  }
+
+  function registerLocalBinding(id: Identifier) {
+    excludedIds.add(id)
+    if (currentScope) {
+      currentScope[id.name] = false
+    } else {
+      error(
+        'registerBinding called without active scope, something is wrong.',
+        id
+      )
+    }
+  }
+
+  function walkScope(node: Program | BlockStatement, isRoot = false) {
+    for (const stmt of node.body) {
+      if (stmt.type === 'VariableDeclaration') {
+        walkVariableDeclaration(stmt, isRoot)
+      } else if (
+        stmt.type === 'FunctionDeclaration' ||
+        stmt.type === 'ClassDeclaration'
+      ) {
+        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 &&
+        stmt.declaration.type === 'VariableDeclaration'
+      ) {
+        walkVariableDeclaration(stmt.declaration, isRoot)
+      } else if (
+        stmt.type === 'LabeledStatement' &&
+        stmt.body.type === 'VariableDeclaration'
+      ) {
+        walkVariableDeclaration(stmt.body, isRoot)
+      }
+    }
+  }
+
+  function walkVariableDeclaration(stmt: VariableDeclaration, isRoot = false) {
+    if (stmt.declare) {
+      return
+    }
+    for (const decl of stmt.declarations) {
+      const isDefineProps =
+        isRoot && decl.init && isCallOf(unwrapTSNode(decl.init), 'defineProps')
+      for (const id of extractIdentifiers(decl.id)) {
+        if (isDefineProps) {
+          // for defineProps destructure, only exclude them since they
+          // are already passed in as knownProps
+          excludedIds.add(id)
+        } else {
+          registerLocalBinding(id)
+        }
+      }
+    }
+  }
+
+  function rewriteId(
+    scope: Scope,
+    id: Identifier,
+    parent: Node,
+    parentStack: Node[]
+  ): boolean {
+    if (hasOwn(scope, id.name)) {
+      const binding = scope[id.name]
+
+      if (binding) {
+        if (
+          (parent.type === 'AssignmentExpression' && id === parent.left) ||
+          parent.type === 'UpdateExpression'
+        ) {
+          error(`Cannot assign to destructured props as they are readonly.`, id)
+        }
+
+        if (isStaticProperty(parent) && parent.shorthand) {
+          // let binding used in a property shorthand
+          // skip for destructure patterns
+          if (
+            !(parent as any).inPattern ||
+            isInDestructureAssignment(parent, parentStack)
+          ) {
+            // { prop } -> { prop: __props.prop }
+            s.appendLeft(
+              id.end! + offset,
+              `: ${genPropsAccessExp(propsLocalToPublicMap[id.name])}`
+            )
+          }
+        } else {
+          // x --> __props.x
+          s.overwrite(
+            id.start! + offset,
+            id.end! + offset,
+            genPropsAccessExp(propsLocalToPublicMap[id.name])
+          )
+        }
+      }
+      return true
+    }
+    return false
+  }
+
+  // check root scope first
+  walkScope(ast, true)
+  ;(walk as any)(ast, {
+    enter(node: Node, parent?: Node) {
+      parent && parentStack.push(parent)
+
+      // skip type nodes
+      if (
+        parent &&
+        parent.type.startsWith('TS') &&
+        parent.type !== 'TSAsExpression' &&
+        parent.type !== 'TSNonNullExpression' &&
+        parent.type !== 'TSTypeAssertion'
+      ) {
+        return this.skip()
+      }
+
+      if (isCallOf(node, watchMethodName)) {
+        const arg = unwrapTSNode(node.arguments[0])
+        if (arg.type === 'Identifier') {
+          error(
+            `"${arg.name}" is a destructured prop and cannot be directly watched. ` +
+              `Use a getter () => ${arg.name} instead.`,
+            arg
+          )
+        }
+      }
+
+      // function scopes
+      if (isFunctionType(node)) {
+        scopeStack.push((currentScope = {}))
+        walkFunctionParams(node, registerLocalBinding)
+        if (node.body.type === 'BlockStatement') {
+          walkScope(node.body)
+        }
+        return
+      }
+
+      // catch param
+      if (node.type === 'CatchClause') {
+        scopeStack.push((currentScope = {}))
+        if (node.param && node.param.type === 'Identifier') {
+          registerLocalBinding(node.param)
+        }
+        walkScope(node.body)
+        return
+      }
+
+      // non-function block scopes
+      if (node.type === 'BlockStatement' && !isFunctionType(parent!)) {
+        scopeStack.push((currentScope = {}))
+        walkScope(node)
+        return
+      }
+
+      if (node.type === 'Identifier') {
+        if (
+          isReferencedIdentifier(node, parent!, parentStack) &&
+          !excludedIds.has(node)
+        ) {
+          // walk up the scope chain to check if id should be appended .value
+          let i = scopeStack.length
+          while (i--) {
+            if (rewriteId(scopeStack[i], node, parent!, parentStack)) {
+              return
+            }
+          }
+        }
+      }
+    },
+    leave(node: Node, parent?: Node) {
+      parent && parentStack.pop()
+      if (
+        (node.type === 'BlockStatement' && !isFunctionType(parent!)) ||
+        isFunctionType(node)
+      ) {
+        scopeStack.pop()
+        currentScope = scopeStack[scopeStack.length - 1] || null
+      }
+    }
+  })
+}
index 072bc98e4a3bd3f84d56fb6600ae9ef7a4c9f06e..5ce6fbbd6d127c69bdadec0814920aa0e1de9f43 100644 (file)
@@ -216,6 +216,8 @@ type PropsWithDefaults<Base, Defaults> = Base & {
  *
  * This is only usable inside `<script setup>`, is compiled away in the output
  * and should **not** be actually called at runtime.
+ *
+ * @deprecated use reactive props destructure instead.
  */
 export function withDefaults<Props, Defaults extends InferDefaults<Props>>(
   props: Props,