From 10d34a5624775f20437ccad074a97270ef74c3fb Mon Sep 17 00:00:00 2001 From: Tycho Date: Tue, 7 May 2024 07:16:14 +0800 Subject: [PATCH] fix(compiler-sfc): handle keyof operator (#10874) close #10871 --- .../compileScript/resolveType.spec.ts | 36 ++++++++++++++++++ .../compiler-sfc/src/script/resolveType.ts | 38 +++++++++++++++++-- 2 files changed, 70 insertions(+), 4 deletions(-) diff --git a/packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts b/packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts index dc95a9dc643..98f5019a03d 100644 --- a/packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts +++ b/packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts @@ -447,6 +447,42 @@ describe('resolveType', () => { }) }) + test('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('ExtractPropTypes (element-plus)', () => { const { props, raw } = resolve( ` diff --git a/packages/compiler-sfc/src/script/resolveType.ts b/packages/compiler-sfc/src/script/resolveType.ts index 54b207e7e91..bbed11baffe 100644 --- a/packages/compiler-sfc/src/script/resolveType.ts +++ b/packages/compiler-sfc/src/script/resolveType.ts @@ -1448,6 +1448,7 @@ export function inferRuntimeType( ctx: TypeResolveContext, node: Node & MaybeWithScope, scope = node._ownerScope || ctxToScope(ctx), + isKeyOf = false, ): string[] { try { switch (node.type) { @@ -1467,8 +1468,18 @@ export function inferRuntimeType( const types = new Set() const members = node.type === 'TSTypeLiteral' ? node.members : node.body.body + for (const m of members) { - if ( + if (isKeyOf) { + if ( + m.type === 'TSPropertySignature' && + m.key.type === 'NumericLiteral' + ) { + types.add('Number') + } else { + types.add('String') + } + } else if ( m.type === 'TSCallSignatureDeclaration' || m.type === 'TSConstructSignatureDeclaration' ) { @@ -1477,6 +1488,7 @@ export function inferRuntimeType( types.add('Object') } } + return types.size ? Array.from(types) : ['Object'] } case 'TSPropertySignature': @@ -1512,9 +1524,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, isKeyOf) } + 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 +1659,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, isKeyOf) } } break @@ -1642,7 +1667,12 @@ export function inferRuntimeType( // e.g. readonly case 'TSTypeOperator': { - return inferRuntimeType(ctx, node.typeAnnotation, scope) + return inferRuntimeType( + ctx, + node.typeAnnotation, + scope, + node.operator === 'keyof', + ) } } } catch (e) {