]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix(compiler-sfc): fix co-usage of defineModel transform options and props destructure
authorEvan You <yyx990803@gmail.com>
Thu, 4 Jan 2024 09:07:29 +0000 (17:07 +0800)
committerEvan You <yyx990803@gmail.com>
Thu, 4 Jan 2024 09:07:29 +0000 (17:07 +0800)
close #9972

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

index 1163a4c02bae3c51d2f83dc1fbcfdd89998822e9..b6a93541d36d4d300c99799cc60d1addb7e592d4 100644 (file)
@@ -17,8 +17,8 @@ export default {
   __expose();
 
       const modelValue = _useModel(__props, "modelValue")
-      const c = _useModel(__props, "count")
-      const toString = _useModel(__props, "toString")
+      const c = _useModel(__props, 'count')
+      const toString = _useModel(__props, 'toString')
       
 return { modelValue, c, toString }
 }
@@ -40,7 +40,10 @@ export default /*#__PURE__*/_defineComponent({
   setup(__props, { expose: __expose }) {
   __expose();
 
-      const modelValue = _useModel(__props, "modelValue", { get(v) { return v - 1 }, set: (v) => { return v + 1 },  })
+      const modelValue = _useModel(__props, "modelValue", {
+        get(v) { return v - 1 },
+        set: (v) => { return v + 1 },
+        })
       
 return { modelValue }
 }
@@ -63,7 +66,36 @@ export default /*#__PURE__*/_defineComponent({
   setup(__props, { expose: __expose }) {
   __expose();
 
-      const modelValue = _useModel(__props, "modelValue", { get(v) { return v - 1 }, set: (v) => { return v + 1 },  })
+      const modelValue = _useModel(__props, "modelValue", {
+        get(v) { return v - 1 },
+        set: (v) => { return v + 1 },
+      })
+      
+return { modelValue }
+}
+
+})"
+`;
+
+exports[`defineModel() > usage w/ props destructure 1`] = `
+"import { useModel as _useModel, mergeModels as _mergeModels, defineComponent as _defineComponent } from 'vue'
+
+export default /*#__PURE__*/_defineComponent({
+  props: /*#__PURE__*/_mergeModels({
+    x: { type: Number, required: true }
+  }, {
+    "modelValue": {
+        },
+    "modelModifiers": {},
+  }),
+  emits: ["update:modelValue"],
+  setup(__props: any, { expose: __expose }) {
+  __expose();
+
+      
+      const modelValue = _useModel(__props, "modelValue", {
+        set: (v) => { return v + __props.x }
+      })
       
 return { modelValue }
 }
@@ -84,7 +116,7 @@ export default {
   __expose();
 
       
-      const count = _useModel(__props, "count")
+      const count = _useModel(__props, 'count')
       
 return { count }
 }
@@ -132,10 +164,10 @@ export default /*#__PURE__*/_defineComponent({
   setup(__props, { expose: __expose }) {
   __expose();
 
-      const modelValue = _useModel(__props, "modelValue")
-      const count = _useModel(__props, "count")
-      const disabled = _useModel(__props, "disabled")
-      const any = _useModel(__props, "any")
+      const modelValue = _useModel<boolean | string>(__props, "modelValue")
+      const count = _useModel<number>(__props, 'count')
+      const disabled = _useModel<number>(__props, 'disabled')
+      const any = _useModel<any | boolean>(__props, 'any')
       
 return { modelValue, count, disabled, any }
 }
@@ -163,11 +195,11 @@ export default /*#__PURE__*/_defineComponent({
   setup(__props, { expose: __expose }) {
   __expose();
 
-      const modelValue = _useModel(__props, "modelValue")
-      const fn = _useModel(__props, "fn")
-      const fnWithDefault = _useModel(__props, "fnWithDefault")
-      const str = _useModel(__props, "str")
-      const optional = _useModel(__props, "optional")
+      const modelValue = _useModel<boolean>(__props, "modelValue")
+      const fn = _useModel<() => void>(__props, 'fn')
+      const fnWithDefault = _useModel<() => void>(__props, 'fnWithDefault')
+      const str = _useModel<string>(__props, 'str')
+      const optional = _useModel<string>(__props, 'optional')
       
 return { modelValue, fn, fnWithDefault, str, optional }
 }
index 6feff4500f14ee485c22fae5358c504c8affbe0e..304258615a85fbf7864a437e36ee1659cf3dd219 100644 (file)
@@ -23,7 +23,8 @@ describe('defineModel()', () => {
     expect(content).toMatch(
       `const modelValue = _useModel(__props, "modelValue")`,
     )
-    expect(content).toMatch(`const c = _useModel(__props, "count")`)
+    expect(content).toMatch(`const c = _useModel(__props, 'count')`)
+    expect(content).toMatch(`const toString = _useModel(__props, 'toString')`)
     expect(content).toMatch(`return { modelValue, c, toString }`)
     expect(content).not.toMatch('defineModel')
 
@@ -71,7 +72,7 @@ describe('defineModel()', () => {
     "count": {},
     "countModifiers": {},
   })`)
-    expect(content).toMatch(`const count = _useModel(__props, "count")`)
+    expect(content).toMatch(`const count = _useModel(__props, 'count')`)
     expect(content).not.toMatch('defineModel')
     expect(bindings).toStrictEqual({
       foo: BindingTypes.PROPS,
@@ -104,11 +105,15 @@ describe('defineModel()', () => {
     )
 
     expect(content).toMatch(
-      `const modelValue = _useModel(__props, "modelValue")`,
+      `const modelValue = _useModel<boolean | string>(__props, "modelValue")`,
+    )
+    expect(content).toMatch(`const count = _useModel<number>(__props, 'count')`)
+    expect(content).toMatch(
+      `const disabled = _useModel<number>(__props, 'disabled')`,
+    )
+    expect(content).toMatch(
+      `const any = _useModel<any | boolean>(__props, 'any')`,
     )
-    expect(content).toMatch(`const count = _useModel(__props, "count")`)
-    expect(content).toMatch(`const disabled = _useModel(__props, "disabled")`)
-    expect(content).toMatch(`const any = _useModel(__props, "any")`)
 
     expect(bindings).toStrictEqual({
       modelValue: BindingTypes.SETUP_REF,
@@ -143,10 +148,10 @@ describe('defineModel()', () => {
       'emits: ["update:modelValue", "update:fn", "update:fnWithDefault", "update:str", "update:optional"]',
     )
     expect(content).toMatch(
-      `const modelValue = _useModel(__props, "modelValue")`,
+      `const modelValue = _useModel<boolean>(__props, "modelValue")`,
     )
-    expect(content).toMatch(`const fn = _useModel(__props, "fn")`)
-    expect(content).toMatch(`const str = _useModel(__props, "str")`)
+    expect(content).toMatch(`const fn = _useModel<() => void>(__props, 'fn')`)
+    expect(content).toMatch(`const str = _useModel<string>(__props, 'str')`)
     expect(bindings).toStrictEqual({
       modelValue: BindingTypes.SETUP_REF,
       fn: BindingTypes.SETUP_REF,
@@ -171,7 +176,10 @@ describe('defineModel()', () => {
     assertCode(content)
     expect(content).toMatch(/"modelValue": {\s+required: true,?\s+}/m)
     expect(content).toMatch(
-      `_useModel(__props, "modelValue", { get(v) { return v - 1 }, set: (v) => { return v + 1 },  })`,
+      `_useModel(__props, "modelValue", {
+        get(v) { return v - 1 },
+        set: (v) => { return v + 1 },
+        })`,
     )
 
     const { content: content2 } = compile(
@@ -191,7 +199,26 @@ describe('defineModel()', () => {
       /"modelValue": {\s+default: 0,\s+required: true,?\s+}/m,
     )
     expect(content2).toMatch(
-      `_useModel(__props, "modelValue", { get(v) { return v - 1 }, set: (v) => { return v + 1 },  })`,
+      `_useModel(__props, "modelValue", {
+        get(v) { return v - 1 },
+        set: (v) => { return v + 1 },
+      })`,
     )
   })
+
+  test('usage w/ props destructure', () => {
+    const { content } = compile(
+      `
+      <script setup lang="ts">
+      const { x } = defineProps<{ x: number }>()
+      const modelValue = defineModel({
+        set: (v) => { return v + x }
+      })
+      </script>
+      `,
+      { propsDestructure: true },
+    )
+    assertCode(content)
+    expect(content).toMatch(`set: (v) => { return v + __props.x }`)
+  })
 })
index 9411fa460b93275d35d7cd14d4f6aa20a2493204..b94b7994622eb49fb8d024f2489c9c86dd5d7cfe 100644 (file)
@@ -33,7 +33,8 @@ export function processDefineModel(
   let modelName: string
   let options: Node | undefined
   const arg0 = node.arguments[0] && unwrapTSNode(node.arguments[0])
-  if (arg0 && arg0.type === 'StringLiteral') {
+  const hasName = arg0 && arg0.type === 'StringLiteral'
+  if (hasName) {
     modelName = arg0.value
     options = node.arguments[1]
   } else {
@@ -46,39 +47,42 @@ export function processDefineModel(
   }
 
   let optionsString = options && ctx.getString(options)
-  let runtimeOptions = ''
-  let transformOptions = ''
-
-  if (options) {
-    if (options.type === 'ObjectExpression') {
-      for (let i = options.properties.length - 1; i >= 0; i--) {
-        const p = options.properties[i]
-        if (p.type === 'SpreadElement' || p.computed) {
-          runtimeOptions = optionsString!
-          break
-        }
-        if (
-          (p.type === 'ObjectProperty' || p.type === 'ObjectMethod') &&
-          ((p.key.type === 'Identifier' &&
-            (p.key.name === 'get' || p.key.name === 'set')) ||
-            (p.key.type === 'StringLiteral' &&
-              (p.key.value === 'get' || p.key.value === 'set')))
-        ) {
-          transformOptions = ctx.getString(p) + ', ' + transformOptions
-
-          // remove transform option from prop options to avoid duplicates
-          const offset = p.start! - options.start!
-          const next = options.properties[i + 1]
-          const end = (next ? next.start! : options.end! - 1) - options.start!
-          optionsString =
-            optionsString.slice(0, offset) + optionsString.slice(end)
-        }
+  let optionsRemoved = !options
+
+  if (
+    options &&
+    options.type === 'ObjectExpression' &&
+    !options.properties.some(p => p.type === 'SpreadElement' || p.computed)
+  ) {
+    let removed = 0
+    for (let i = options.properties.length - 1; i >= 0; i--) {
+      const p = options.properties[i]
+      const next = options.properties[i + 1]
+      const start = p.start!
+      const end = next ? next.start! : options.end! - 1
+      if (
+        (p.type === 'ObjectProperty' || p.type === 'ObjectMethod') &&
+        ((p.key.type === 'Identifier' &&
+          (p.key.name === 'get' || p.key.name === 'set')) ||
+          (p.key.type === 'StringLiteral' &&
+            (p.key.value === 'get' || p.key.value === 'set')))
+      ) {
+        // remove runtime-only options from prop options to avoid duplicates
+        optionsString =
+          optionsString.slice(0, start - options.start!) +
+          optionsString.slice(end - options.start!)
+      } else {
+        // remove prop options from runtime options
+        removed++
+        ctx.s.remove(ctx.startOffset! + start, ctx.startOffset! + end)
       }
-      if (!runtimeOptions && transformOptions) {
-        runtimeOptions = `{ ${transformOptions} }`
-      }
-    } else {
-      runtimeOptions = optionsString!
+    }
+    if (removed === options.properties.length) {
+      optionsRemoved = true
+      ctx.s.remove(
+        ctx.startOffset! + (hasName ? arg0.end! : options.start!),
+        ctx.startOffset! + options.end!,
+      )
     }
   }
 
@@ -91,12 +95,20 @@ export function processDefineModel(
   // register binding type
   ctx.bindingMetadata[modelName] = BindingTypes.PROPS
 
+  // defineModel -> useModel
   ctx.s.overwrite(
-    ctx.startOffset! + node.start!,
-    ctx.startOffset! + node.end!,
-    `${ctx.helper('useModel')}(__props, ${JSON.stringify(modelName)}${
-      runtimeOptions ? `, ${runtimeOptions}` : ``
-    })`,
+    ctx.startOffset! + node.callee.start!,
+    ctx.startOffset! + node.callee.end!,
+    ctx.helper('useModel'),
+  )
+  // inject arguments
+  ctx.s.appendLeft(
+    ctx.startOffset! +
+      (node.arguments.length ? node.arguments[0].start! : node.end! - 1),
+    `__props, ` +
+      (hasName
+        ? ``
+        : `${JSON.stringify(modelName)}${optionsRemoved ? `` : `, `}`),
   )
 
   return true