From b71f801b7fe2ebac8aa461e7f8dc2a68a337256d Mon Sep 17 00:00:00 2001 From: tycho Date: Mon, 6 May 2024 17:43:59 +0800 Subject: [PATCH 1/2] fix(compiler-sfc): handle keyof operator --- .../compileScript/resolveType.spec.ts | 36 ++++++++++++ .../compiler-sfc/src/script/resolveType.ts | 57 +++++++++++++++---- 2 files changed, 82 insertions(+), 11 deletions(-) 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) { From 792a5e34487fae07107d33bf9b572f3de5f4456b Mon Sep 17 00:00:00 2001 From: Evan You Date: Mon, 6 May 2024 16:13:45 -0700 Subject: [PATCH 2/2] refactor: avoid options and handlers --- .../compileScript/resolveType.spec.ts | 72 +++++++++---------- .../compiler-sfc/src/script/resolveType.ts | 59 +++++++-------- 2 files changed, 63 insertions(+), 68 deletions(-) diff --git a/packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts b/packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts index 6ab56e3150a..98f5019a03d 100644 --- a/packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts +++ b/packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts @@ -265,42 +265,6 @@ 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(` @@ -483,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 21b9f73f580..bbed11baffe 100644 --- a/packages/compiler-sfc/src/script/resolveType.ts +++ b/packages/compiler-sfc/src/script/resolveType.ts @@ -1448,10 +1448,8 @@ export function inferRuntimeType( ctx: TypeResolveContext, node: Node & MaybeWithScope, scope = node._ownerScope || ctxToScope(ctx), - options: { isKeyof?: boolean } = {}, + isKeyOf = false, ): string[] { - const { isKeyof } = options - try { switch (node.type) { case 'TSStringKeyword': @@ -1471,30 +1469,24 @@ export function inferRuntimeType( 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) { - handler(m) + 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' + ) { + types.add('Function') + } else { + types.add('Object') + } } return types.size ? Array.from(types) : ['Object'] @@ -1532,11 +1524,11 @@ export function inferRuntimeType( case 'TSTypeReference': { const resolved = resolveTypeReference(ctx, node, scope) if (resolved) { - return inferRuntimeType(ctx, resolved, resolved._ownerScope, options) + return inferRuntimeType(ctx, resolved, resolved._ownerScope, isKeyOf) } if (node.typeName.type === 'Identifier') { - if (isKeyof) { + if (isKeyOf) { switch (node.typeName.name) { case 'String': case 'Array': @@ -1667,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, options) + return inferRuntimeType(ctx, matched, matched._ownerScope, isKeyOf) } } break @@ -1675,9 +1667,12 @@ export function inferRuntimeType( // e.g. readonly case 'TSTypeOperator': { - return inferRuntimeType(ctx, node.typeAnnotation, scope, { - isKeyof: node.operator === 'keyof', - }) + return inferRuntimeType( + ctx, + node.typeAnnotation, + scope, + node.operator === 'keyof', + ) } } } catch (e) {