diff --git a/packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts b/packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts index dc95a9dc643..6ab56e3150a 100644 --- a/packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts +++ b/packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts @@ -265,6 +265,42 @@ describe('resolveType', () => { }) }) + test('utility type: keyof', () => { + const files = { + '/foo.ts': `export type IMP = { ${1}: 1 };`, + } + + const { props } = resolve( + ` + import { IMP } from './foo' + interface Foo { foo: 1, ${1}: 1 } + type Bar = { bar: 1 } + declare const obj: Bar + declare const set: Set + declare const arr: Array + + defineProps<{ + imp: keyof IMP, + foo: keyof Foo, + bar: keyof Bar, + obj: keyof typeof obj, + set: keyof typeof set, + arr: keyof typeof arr + }>() + `, + files, + ) + + expect(props).toStrictEqual({ + imp: ['Number'], + foo: ['String', 'Number'], + bar: ['String'], + obj: ['String'], + set: ['String'], + arr: ['String', 'Number'], + }) + }) + test('utility type: ReadonlyArray', () => { expect( resolve(` diff --git a/packages/compiler-sfc/src/script/resolveType.ts b/packages/compiler-sfc/src/script/resolveType.ts index 54b207e7e91..21b9f73f580 100644 --- a/packages/compiler-sfc/src/script/resolveType.ts +++ b/packages/compiler-sfc/src/script/resolveType.ts @@ -1448,7 +1448,10 @@ export function inferRuntimeType( ctx: TypeResolveContext, node: Node & MaybeWithScope, scope = node._ownerScope || ctxToScope(ctx), + options: { isKeyof?: boolean } = {}, ): string[] { + const { isKeyof } = options + try { switch (node.type) { case 'TSStringKeyword': @@ -1467,16 +1470,33 @@ export function inferRuntimeType( const types = new Set() const members = node.type === 'TSTypeLiteral' ? node.members : node.body.body + + const handler = isKeyof + ? (m: TSTypeElement) => { + if ( + m.type === 'TSPropertySignature' && + m.key.type === 'NumericLiteral' + ) { + types.add('Number') + } else { + types.add('String') + } + } + : (m: TSTypeElement) => { + if ( + m.type === 'TSCallSignatureDeclaration' || + m.type === 'TSConstructSignatureDeclaration' + ) { + types.add('Function') + } else { + types.add('Object') + } + } + for (const m of members) { - if ( - m.type === 'TSCallSignatureDeclaration' || - m.type === 'TSConstructSignatureDeclaration' - ) { - types.add('Function') - } else { - types.add('Object') - } + handler(m) } + return types.size ? Array.from(types) : ['Object'] } case 'TSPropertySignature': @@ -1512,9 +1532,22 @@ export function inferRuntimeType( case 'TSTypeReference': { const resolved = resolveTypeReference(ctx, node, scope) if (resolved) { - return inferRuntimeType(ctx, resolved, resolved._ownerScope) + return inferRuntimeType(ctx, resolved, resolved._ownerScope, options) } + if (node.typeName.type === 'Identifier') { + if (isKeyof) { + switch (node.typeName.name) { + case 'String': + case 'Array': + case 'ArrayLike': + case 'ReadonlyArray': + return ['String', 'Number'] + default: + return ['String'] + } + } + switch (node.typeName.name) { case 'Array': case 'Function': @@ -1634,7 +1667,7 @@ export function inferRuntimeType( // typeof only support identifier in local scope const matched = scope.declares[id.name] if (matched) { - return inferRuntimeType(ctx, matched, matched._ownerScope) + return inferRuntimeType(ctx, matched, matched._ownerScope, options) } } break @@ -1642,7 +1675,9 @@ export function inferRuntimeType( // e.g. readonly case 'TSTypeOperator': { - return inferRuntimeType(ctx, node.typeAnnotation, scope) + return inferRuntimeType(ctx, node.typeAnnotation, scope, { + isKeyof: node.operator === 'keyof', + }) } } } catch (e) {