diff --git a/packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts b/packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts index 3e61e1b231a..5d5a424f26b 100644 --- a/packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts +++ b/packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts @@ -8,19 +8,19 @@ import { describe('resolveType', () => { test('type literal', () => { - const { elements, callSignatures } = resolve(`type Target = { + const { props, calls } = resolve(`type Target = { foo: number // property bar(): void // method 'baz': string // string literal key (e: 'foo'): void // call signature (e: 'bar'): void }`) - expect(elements).toStrictEqual({ + expect(props).toStrictEqual({ foo: ['Number'], bar: ['Function'], baz: ['String'] }) - expect(callSignatures?.length).toBe(2) + expect(calls?.length).toBe(2) }) test('reference type', () => { @@ -28,7 +28,7 @@ describe('resolveType', () => { resolve(` type Aliased = { foo: number } type Target = Aliased - `).elements + `).props ).toStrictEqual({ foo: ['Number'] }) @@ -39,7 +39,7 @@ describe('resolveType', () => { resolve(` export type Aliased = { foo: number } type Target = Aliased - `).elements + `).props ).toStrictEqual({ foo: ['Number'] }) @@ -50,7 +50,7 @@ describe('resolveType', () => { resolve(` interface Aliased { foo: number } type Target = Aliased - `).elements + `).props ).toStrictEqual({ foo: ['Number'] }) @@ -61,7 +61,7 @@ describe('resolveType', () => { resolve(` export interface Aliased { foo: number } type Target = Aliased - `).elements + `).props ).toStrictEqual({ foo: ['Number'] }) @@ -75,7 +75,7 @@ describe('resolveType', () => { interface C { c: string } interface Aliased extends B, C { foo: number } type Target = Aliased - `).elements + `).props ).toStrictEqual({ a: ['Function'], b: ['Boolean'], @@ -88,7 +88,7 @@ describe('resolveType', () => { expect( resolve(` type Target = (e: 'foo') => void - `).callSignatures?.length + `).calls?.length ).toBe(1) }) @@ -97,7 +97,7 @@ describe('resolveType', () => { resolve(` type Fn = (e: 'foo') => void type Target = Fn - `).callSignatures?.length + `).calls?.length ).toBe(1) }) @@ -108,7 +108,7 @@ describe('resolveType', () => { type Bar = { bar: string } type Baz = { bar: string | boolean } type Target = { self: any } & Foo & Bar & Baz - `).elements + `).props ).toStrictEqual({ self: ['Unknown'], foo: ['Number'], @@ -138,7 +138,7 @@ describe('resolveType', () => { } type Target = CommonProps & ConditionalProps - `).elements + `).props ).toStrictEqual({ size: ['String'], color: ['String', 'Number'], @@ -155,7 +155,7 @@ describe('resolveType', () => { type Target = { [\`_\${T}_\${S}_\`]: string } - `).elements + `).props ).toStrictEqual({ _foo_x_: ['String'], _foo_y_: ['String'], @@ -177,7 +177,7 @@ describe('resolveType', () => { } & { [K in \`x\${T}\`]: string } - `).elements + `).props ).toStrictEqual({ foo: ['String', 'Number'], bar: ['String', 'Number'], @@ -196,7 +196,7 @@ describe('resolveType', () => { type T = { foo: number, bar: string, baz: boolean } type K = 'foo' | 'bar' type Target = Pick - `).elements + `).props ).toStrictEqual({ foo: ['Number'], bar: ['String'] @@ -209,7 +209,7 @@ describe('resolveType', () => { type T = { foo: number, bar: string, baz: boolean } type K = 'foo' | 'bar' type Target = Omit - `).elements + `).props ).toStrictEqual({ baz: ['Boolean'] }) @@ -231,13 +231,13 @@ function resolve(code: string) { s => s.type === 'TSTypeAliasDeclaration' && s.id.name === 'Target' ) as TSTypeAliasDeclaration const raw = resolveTypeElements(ctx, targetDecl.typeAnnotation) - const elements: Record = {} - for (const key in raw) { - elements[key] = inferRuntimeType(ctx, raw[key]) + const props: Record = {} + for (const key in raw.props) { + props[key] = inferRuntimeType(ctx, raw.props[key]) } return { - elements, - callSignatures: raw.__callSignatures, + props, + calls: raw.calls, raw } } diff --git a/packages/compiler-sfc/src/script/defineEmits.ts b/packages/compiler-sfc/src/script/defineEmits.ts index 0e080b4fed4..e615cd71a0d 100644 --- a/packages/compiler-sfc/src/script/defineEmits.ts +++ b/packages/compiler-sfc/src/script/defineEmits.ts @@ -69,22 +69,22 @@ function extractRuntimeEmits(ctx: ScriptCompileContext): Set { return emits } - const elements = resolveTypeElements(ctx, node) + const { props, calls } = resolveTypeElements(ctx, node) let hasProperty = false - for (const key in elements) { + for (const key in props) { emits.add(key) hasProperty = true } - if (elements.__callSignatures) { + if (calls) { if (hasProperty) { ctx.error( `defineEmits() type cannot mixed call signature and property syntax.`, node ) } - for (const call of elements.__callSignatures) { + for (const call of calls) { extractEventNames(call.parameters[0], emits) } } diff --git a/packages/compiler-sfc/src/script/defineProps.ts b/packages/compiler-sfc/src/script/defineProps.ts index ee8b5e55734..16ea02fe3cf 100644 --- a/packages/compiler-sfc/src/script/defineProps.ts +++ b/packages/compiler-sfc/src/script/defineProps.ts @@ -191,8 +191,8 @@ function resolveRuntimePropsFromType( ): PropTypeData[] { const props: PropTypeData[] = [] const elements = resolveTypeElements(ctx, node) - for (const key in elements) { - const e = elements[key] + for (const key in elements.props) { + const e = elements.props[key] let type = inferRuntimeType(ctx, e) let skipCheck = false // skip check for result containing unknown types diff --git a/packages/compiler-sfc/src/script/resolveType.ts b/packages/compiler-sfc/src/script/resolveType.ts index 0f6a4eb5230..ecd3838be7b 100644 --- a/packages/compiler-sfc/src/script/resolveType.ts +++ b/packages/compiler-sfc/src/script/resolveType.ts @@ -18,7 +18,7 @@ import { UNKNOWN_TYPE } from './utils' import { ScriptCompileContext } from './context' import { ImportBinding } from '../compileScript' import { TSInterfaceDeclaration } from '@babel/types' -import { capitalize, hasOwn, isArray } from '@vue/shared' +import { capitalize, hasOwn } from '@vue/shared' import { Expression } from '@babel/types' export interface TypeScope { @@ -28,11 +28,9 @@ export interface TypeScope { types: Record } -type ResolvedElements = Record< - string, - TSPropertySignature | TSMethodSignature -> & { - __callSignatures?: (TSCallSignatureDeclaration | TSFunctionType)[] +interface ResolvedElements { + props: Record + calls?: (TSCallSignatureDeclaration | TSFunctionType)[] } /** @@ -62,9 +60,7 @@ function innerResolveTypeElements( case 'TSParenthesizedType': return resolveTypeElements(ctx, node.typeAnnotation) case 'TSFunctionType': { - const ret: ResolvedElements = {} - addCallSignature(ret, node) - return ret + return { props: {}, calls: [node] } } case 'TSExpressionWithTypeArguments': // referenced by interface extends case 'TSTypeReference': { @@ -98,32 +94,11 @@ function innerResolveTypeElements( ctx.error(`Unsupported type in SFC macro: ${node.type}`, node) } -function addCallSignature( - elements: ResolvedElements, - node: - | TSCallSignatureDeclaration - | TSFunctionType - | (TSCallSignatureDeclaration | TSFunctionType)[] -) { - if (!elements.__callSignatures) { - Object.defineProperty(elements, '__callSignatures', { - enumerable: false, - value: isArray(node) ? node : [node] - }) - } else { - if (isArray(node)) { - elements.__callSignatures.push(...node) - } else { - elements.__callSignatures.push(node) - } - } -} - function typeElementsToMap( ctx: ScriptCompileContext, elements: TSTypeElement[] ): ResolvedElements { - const ret: ResolvedElements = {} + const res: ResolvedElements = { props: {} } for (const e of elements) { if (e.type === 'TSPropertySignature' || e.type === 'TSMethodSignature') { const name = @@ -133,10 +108,10 @@ function typeElementsToMap( ? e.key.value : null if (name && !e.computed) { - ret[name] = e + res.props[name] = e } else if (e.key.type === 'TemplateLiteral') { for (const key of resolveTemplateKeys(ctx, e.key)) { - ret[key] = e + res.props[key] = e } } else { ctx.error( @@ -145,31 +120,32 @@ function typeElementsToMap( ) } } else if (e.type === 'TSCallSignatureDeclaration') { - addCallSignature(ret, e) + ;(res.calls || (res.calls = [])).push(e) } } - return ret + return res } function mergeElements( maps: ResolvedElements[], type: 'TSUnionType' | 'TSIntersectionType' ): ResolvedElements { - const res: ResolvedElements = Object.create(null) - for (const m of maps) { - for (const key in m) { - if (!(key in res)) { - res[key] = m[key] + const res: ResolvedElements = { props: {} } + const { props: baseProps } = res + for (const { props, calls } of maps) { + for (const key in props) { + if (!hasOwn(baseProps, key)) { + baseProps[key] = props[key] } else { - res[key] = createProperty(res[key].key, { + baseProps[key] = createProperty(baseProps[key].key, { type, // @ts-ignore - types: [res[key], m[key]] + types: [baseProps[key], props[key]] }) } } - if (m.__callSignatures) { - addCallSignature(res, m.__callSignatures) + if (calls) { + ;(res.calls || (res.calls = [])).push(...calls) } } return res @@ -197,10 +173,10 @@ function resolveInterfaceMembers( const base = typeElementsToMap(ctx, node.body.body) if (node.extends) { for (const ext of node.extends) { - const resolvedExt = resolveTypeElements(ctx, ext) - for (const key in resolvedExt) { - if (!hasOwn(base, key)) { - base[key] = resolvedExt[key] + const { props } = resolveTypeElements(ctx, ext) + for (const key in props) { + if (!hasOwn(base.props, key)) { + base.props[key] = props[key] } } } @@ -212,13 +188,13 @@ function resolveMappedType( ctx: ScriptCompileContext, node: TSMappedType ): ResolvedElements { - const res: ResolvedElements = {} + const res: ResolvedElements = { props: {} } if (!node.typeParameter.constraint) { ctx.error(`mapped type used in macros must have a finite constraint.`, node) } const keys = resolveStringType(ctx, node.typeParameter.constraint) for (const key of keys) { - res[key] = createProperty( + res.props[key] = createProperty( { type: 'Identifier', name: key @@ -323,20 +299,18 @@ function resolveBuiltin( return t case 'Pick': { const picked = resolveStringType(ctx, node.typeParameters!.params[1]) - const res: ResolvedElements = {} - if (t.__callSignatures) addCallSignature(res, t.__callSignatures) + const res: ResolvedElements = { props: {}, calls: t.calls } for (const key of picked) { - res[key] = t[key] + res.props[key] = t.props[key] } return res } case 'Omit': const omitted = resolveStringType(ctx, node.typeParameters!.params[1]) - const res: ResolvedElements = {} - if (t.__callSignatures) addCallSignature(res, t.__callSignatures) - for (const key in t) { + const res: ResolvedElements = { props: {}, calls: t.calls } + for (const key in t.props) { if (!omitted.includes(key)) { - res[key] = t[key] + res.props[key] = t.props[key] } } return res