diff --git a/packages/compiler-sfc/__tests__/__snapshots__/compileScript.spec.ts.snap b/packages/compiler-sfc/__tests__/__snapshots__/compileScript.spec.ts.snap index 9655e7385c4..6dcac98204e 100644 --- a/packages/compiler-sfc/__tests__/__snapshots__/compileScript.spec.ts.snap +++ b/packages/compiler-sfc/__tests__/__snapshots__/compileScript.spec.ts.snap @@ -1715,7 +1715,9 @@ export default /*#__PURE__*/_defineComponent({ foo: { type: [Function, null], required: true }, unknown: { type: null, required: true }, unknownUnion: { type: null, required: true }, - unknownIntersection: { type: Object, required: true } + unknownIntersection: { type: Object, required: true }, + unknownUnionWithBoolean: { type: Boolean, required: true, skipCheck: true }, + unknownUnionWithFunction: { type: Function, required: true, skipCheck: true } }, setup(__props: any, { expose: __expose }) { __expose(); diff --git a/packages/compiler-sfc/__tests__/compileScript.spec.ts b/packages/compiler-sfc/__tests__/compileScript.spec.ts index 8aa5951104a..8e3ef4e63ea 100644 --- a/packages/compiler-sfc/__tests__/compileScript.spec.ts +++ b/packages/compiler-sfc/__tests__/compileScript.spec.ts @@ -1042,6 +1042,8 @@ const emit = defineEmits(['a', 'b']) unknown: UnknownType unknownUnion: UnknownType | string unknownIntersection: UnknownType & Object + unknownUnionWithBoolean: UnknownType | boolean + unknownUnionWithFunction: UnknownType | (() => any) }>() `) assertCode(content) @@ -1093,7 +1095,13 @@ const emit = defineEmits(['a', 'b']) expect(content).toMatch(`unknownUnion: { type: null, required: true }`) // intersection containing unknown type: narrow to the known types expect(content).toMatch( - `unknownIntersection: { type: Object, required: true }` + `unknownIntersection: { type: Object, required: true },` + ) + expect(content).toMatch( + `unknownUnionWithBoolean: { type: Boolean, required: true, skipCheck: true },` + ) + expect(content).toMatch( + `unknownUnionWithFunction: { type: Function, required: true, skipCheck: true }` ) expect(bindings).toStrictEqual({ string: BindingTypes.PROPS, @@ -1131,7 +1139,9 @@ const emit = defineEmits(['a', 'b']) nonNull: BindingTypes.PROPS, unknown: BindingTypes.PROPS, unknownUnion: BindingTypes.PROPS, - unknownIntersection: BindingTypes.PROPS + unknownIntersection: BindingTypes.PROPS, + unknownUnionWithBoolean: BindingTypes.PROPS, + unknownUnionWithFunction: BindingTypes.PROPS }) }) diff --git a/packages/compiler-sfc/src/compileScript.ts b/packages/compiler-sfc/src/compileScript.ts index 6a4ac52b1ea..3ec54e8010a 100644 --- a/packages/compiler-sfc/src/compileScript.ts +++ b/packages/compiler-sfc/src/compileScript.ts @@ -888,11 +888,11 @@ export function compileScript( } } - const { type, required } = props[key] + const { type, required, skipCheck } = props[key] if (!isProd) { return `${key}: { type: ${toRuntimeTypeString( type - )}, required: ${required}${ + )}, required: ${required}${skipCheck ? ', skipCheck: true' : ''}${ defaultString ? `, ${defaultString}` : `` } }` } else if ( @@ -1964,6 +1964,7 @@ interface PropTypeData { key: string type: string[] required: boolean + skipCheck: boolean } function recordType(node: Node, declaredTypes: Record) { @@ -1993,19 +1994,26 @@ function extractRuntimeProps( m.key.type === 'Identifier' ) { let type: string[] | undefined + let skipCheck = false if (m.type === 'TSMethodSignature') { type = ['Function'] } else if (m.typeAnnotation) { type = inferRuntimeType(m.typeAnnotation.typeAnnotation, declaredTypes) // skip check for result containing unknown types if (type.includes(UNKNOWN_TYPE)) { - type = [`null`] + if (type.includes('Boolean') || type.includes('Function')) { + type = type.filter(t => t !== UNKNOWN_TYPE) + skipCheck = true + } else { + type = ['null'] + } } } props[m.key.name] = { key: m.key.name, required: !m.optional, - type: type || [`null`] + type: type || [`null`], + skipCheck } } } diff --git a/packages/runtime-core/__tests__/componentProps.spec.ts b/packages/runtime-core/__tests__/componentProps.spec.ts index df46da6807a..071a24f138d 100644 --- a/packages/runtime-core/__tests__/componentProps.spec.ts +++ b/packages/runtime-core/__tests__/componentProps.spec.ts @@ -335,7 +335,8 @@ describe('component props', () => { arr: { type: Array }, obj: { type: Object }, cls: { type: MyClass }, - fn: { type: Function } + fn: { type: Function }, + skipCheck: { type: [Boolean, Function], skipCheck: true } }, setup() { return () => null @@ -349,7 +350,8 @@ describe('component props', () => { arr: {}, obj: 'false', cls: {}, - fn: true + fn: true, + skipCheck: 'foo' }), nodeOps.createElement('div') ) @@ -374,6 +376,9 @@ describe('component props', () => { expect( `Invalid prop: type check failed for prop "cls". Expected MyClass, got Object` ).toHaveBeenWarned() + expect( + `Invalid prop: type check failed for prop "skipCheck". Expected Boolean | Function, got String with value "foo".` + ).not.toHaveBeenWarned() }) // #3495 diff --git a/packages/runtime-core/src/componentProps.ts b/packages/runtime-core/src/componentProps.ts index db8bf73a9a1..03c83990c57 100644 --- a/packages/runtime-core/src/componentProps.ts +++ b/packages/runtime-core/src/componentProps.ts @@ -58,6 +58,7 @@ export interface PropOptions { required?: boolean default?: D | DefaultFactory | null | undefined | object validator?(value: unknown): boolean + skipCheck?: boolean } export type PropType = PropConstructor | PropConstructor[] @@ -608,7 +609,7 @@ function validateProp( prop: PropOptions, isAbsent: boolean ) { - const { type, required, validator } = prop + const { type, required, validator, skipCheck } = prop // required! if (required && isAbsent) { warn('Missing required prop: "' + name + '"') @@ -619,7 +620,7 @@ function validateProp( return } // type check - if (type != null && type !== true) { + if (type != null && type !== true && !skipCheck) { let isValid = false const types = isArray(type) ? type : [type] const expectedTypes = []