}"
`;
-exports[`sfc props transform > default values w/ runtime declaration 1`] = `
+exports[`sfc props transform > default values w/ array runtime declaration 1`] = `
"import { mergeDefaults as _mergeDefaults } from 'vue'
export default {
- props: _mergeDefaults(['foo', 'bar'], {
+ props: _mergeDefaults(['foo', 'bar', 'baz'], {
foo: 1,
- bar: () => ({})
+ 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) {
export default /*#__PURE__*/_defineComponent({
props: {
foo: { type: Number, required: false, default: 1 },
- bar: { type: Object, required: false, default: () => ({}) }
+ bar: { type: Object, required: false, default: () => ({}) },
+ func: { type: Function, required: false, default: () => {} }
},
setup(__props: any) {
baz: null,
boola: { type: Boolean },
boolb: { type: [Boolean, Number] },
- func: { type: Function, default: () => (() => {}) }
+ func: { type: Function, default: () => {} }
},
setup(__props: any) {
})
})
- test('default values w/ runtime declaration', () => {
+ test('default values w/ array runtime declaration', () => {
const { content } = compile(`
<script setup>
- const { foo = 1, bar = {} } = defineProps(['foo', 'bar'])
+ const { foo = 1, bar = {}, func = () => {} } = defineProps(['foo', 'bar', 'baz'])
</script>
`)
// literals can be used as-is, non-literals are always returned from a
// function
- expect(content).toMatch(`props: _mergeDefaults(['foo', 'bar'], {
+ // functions need to be marked with a skip marker
+ expect(content).toMatch(`props: _mergeDefaults(['foo', 'bar', 'baz'], {
foo: 1,
- bar: () => ({})
+ bar: () => ({}),
+ func: () => {}, __skip_func: true
+})`)
+ assertCode(content)
+ })
+
+ test('default values w/ object runtime declaration', () => {
+ const { content } = compile(`
+ <script setup>
+ const { foo = 1, bar = {}, func = () => {}, ext = x } = defineProps({ foo: Number, bar: Object, func: Function, ext: null })
+ </script>
+ `)
+ // literals can be used as-is, non-literals are always returned from a
+ // function
+ // functions need to be marked with a skip marker since we cannot always
+ // safely infer whether runtime type is Function (e.g. if the runtime decl
+ // is imported, or spreads another object)
+ expect(content)
+ .toMatch(`props: _mergeDefaults({ foo: Number, bar: Object, func: Function, ext: null }, {
+ foo: 1,
+ bar: () => ({}),
+ func: () => {}, __skip_func: true,
+ ext: x, __skip_ext: true
})`)
assertCode(content)
})
test('default values w/ type declaration', () => {
const { content } = compile(`
<script setup lang="ts">
- const { foo = 1, bar = {} } = defineProps<{ foo?: number, bar?: object }>()
+ const { foo = 1, bar = {}, func = () => {} } = defineProps<{ foo?: number, bar?: object, func?: () => any }>()
</script>
`)
// literals can be used as-is, non-literals are always returned from a
// function
expect(content).toMatch(`props: {
foo: { type: Number, required: false, default: 1 },
- bar: { type: Object, required: false, default: () => ({}) }
+ bar: { type: Object, required: false, default: () => ({}) },
+ func: { type: Function, required: false, default: () => {} }
}`)
assertCode(content)
})
baz: null,
boola: { type: Boolean },
boolb: { type: [Boolean, Number] },
- func: { type: Function, default: () => (() => {}) }
+ func: { type: Function, default: () => {} }
}`)
assertCode(content)
})
${keys
.map(key => {
let defaultString: string | undefined
- const destructured = genDestructuredDefaultValue(key)
+ const destructured = genDestructuredDefaultValue(key, props[key].type)
if (destructured) {
- defaultString = `default: ${destructured}`
+ defaultString = `default: ${destructured.valueString}${
+ destructured.needSkipFactory ? `, skipFactory: true` : ``
+ }`
} else if (hasStaticDefaults) {
const prop = propsRuntimeDefaults!.properties.find(node => {
if (node.type === 'SpreadElement') return false
return `\n props: ${propsDecls},`
}
- function genDestructuredDefaultValue(key: string): string | undefined {
+ function genDestructuredDefaultValue(
+ key: string,
+ inferredType?: string[]
+ ):
+ | {
+ valueString: string
+ needSkipFactory: boolean
+ }
+ | undefined {
const destructured = propsDestructuredBindings[key]
- if (destructured && destructured.default) {
+ const defaultVal = destructured && destructured.default
+ if (defaultVal) {
const value = scriptSetup!.content.slice(
- destructured.default.start!,
- destructured.default.end!
+ defaultVal.start!,
+ defaultVal.end!
)
- const isLiteral = isLiteralNode(destructured.default)
- return isLiteral ? value : `() => (${value})`
+ const unwrapped = unwrapTSNode(defaultVal)
+ // 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
+ // whether tje expected runtime prop type is `Function`.
+ const needSkipFactory =
+ !inferredType &&
+ (isFunctionType(unwrapped) || unwrapped.type === 'Identifier')
+ const needFactoryWrap =
+ !needSkipFactory &&
+ !isLiteralNode(unwrapped) &&
+ !inferredType?.includes('Function')
+ return {
+ valueString: needFactoryWrap ? `() => (${value})` : value,
+ needSkipFactory
+ }
}
}
const defaults: string[] = []
for (const key in propsDestructuredBindings) {
const d = genDestructuredDefaultValue(key)
- if (d) defaults.push(`${key}: ${d}`)
+ if (d)
+ defaults.push(
+ `${key}: ${d.valueString}${
+ d.needSkipFactory ? `, __skip_${key}: true` : ``
+ }`
+ )
}
if (defaults.length) {
declCode = `${helper(
})
})
+ test('merging with skipFactory', () => {
+ const fn = () => {}
+ const merged = mergeDefaults(['foo', 'bar', 'baz'], {
+ foo: fn,
+ __skip_foo: true
+ })
+ expect(merged).toMatchObject({
+ foo: { default: fn, skipFactory: true }
+ })
+ })
+
test('should warn missing', () => {
mergeDefaults({}, { foo: 1 })
expect(
)
: raw
for (const key in defaults) {
- const opt = props[key]
+ if (key.startsWith('__skip')) continue
+ let opt = props[key]
if (opt) {
if (isArray(opt) || isFunction(opt)) {
- props[key] = { type: opt, default: defaults[key] }
+ opt = props[key] = { type: opt, default: defaults[key] }
} else {
opt.default = defaults[key]
}
} else if (opt === null) {
- props[key] = { default: defaults[key] }
+ opt = props[key] = { default: defaults[key] }
} else if (__DEV__) {
warn(`props default key "${key}" has no corresponding declaration.`)
}
+ if (opt && defaults[`__skip_${key}`]) {
+ opt.skipFactory = true
+ }
}
return props
}
required?: boolean
default?: D | DefaultFactory<D> | null | undefined | object
validator?(value: unknown): boolean
+ /**
+ * @internal
+ */
skipCheck?: boolean
+ /**
+ * @internal
+ */
+ skipFactory?: boolean
}
export type PropType<T> = PropConstructor<T> | PropConstructor<T>[]
// default values
if (hasDefault && value === undefined) {
const defaultValue = opt.default
- if (opt.type !== Function && isFunction(defaultValue)) {
+ if (
+ opt.type !== Function &&
+ !opt.skipFactory &&
+ isFunction(defaultValue)
+ ) {
const { propsDefaults } = instance
if (key in propsDefaults) {
value = propsDefaults[key]