Skip to content

Commit e77c0e3

Browse files
committed
fix(compiler-sfc): handle keyof operator
1 parent c0c9432 commit e77c0e3

File tree

2 files changed

+63
-9
lines changed

2 files changed

+63
-9
lines changed

packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,42 @@ describe('resolveType', () => {
265265
})
266266
})
267267

268+
test('utility type: keyof', () => {
269+
const files = {
270+
'/foo.ts': `export type IMP = { ${1}: 1 };`,
271+
}
272+
273+
const { props } = resolve(
274+
`
275+
import { IMP } from './foo'
276+
interface Foo { foo: 1, ${1}: 1 }
277+
type Bar = { bar: 1 }
278+
type SubBar = Bar
279+
declare const obj: Bar
280+
declare const set: Set<any>
281+
282+
defineProps<{
283+
imp: keyof IMP,
284+
foo: keyof Foo,
285+
bar: keyof Bar,
286+
subBar: keyof SubBar,
287+
obj: keyof typeof obj,
288+
set: keyof typeof set,
289+
}>()
290+
`,
291+
files,
292+
)
293+
294+
expect(props).toStrictEqual({
295+
imp: ['Number'],
296+
foo: ['String', 'Number'],
297+
bar: ['String'],
298+
subBar: ['String'],
299+
obj: ['String'],
300+
set: ['String'],
301+
})
302+
})
303+
268304
test('utility type: ReadonlyArray', () => {
269305
expect(
270306
resolve(`

packages/compiler-sfc/src/script/resolveType.ts

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1448,7 +1448,10 @@ export function inferRuntimeType(
14481448
ctx: TypeResolveContext,
14491449
node: Node & MaybeWithScope,
14501450
scope = node._ownerScope || ctxToScope(ctx),
1451+
options: { isKeyof?: boolean } = {},
14511452
): string[] {
1453+
const { isKeyof } = options
1454+
14521455
try {
14531456
switch (node.type) {
14541457
case 'TSStringKeyword':
@@ -1467,16 +1470,26 @@ export function inferRuntimeType(
14671470
const types = new Set<string>()
14681471
const members =
14691472
node.type === 'TSTypeLiteral' ? node.members : node.body.body
1473+
14701474
for (const m of members) {
1471-
if (
1472-
m.type === 'TSCallSignatureDeclaration' ||
1473-
m.type === 'TSConstructSignatureDeclaration'
1474-
) {
1475-
types.add('Function')
1475+
if (isKeyof) {
1476+
if ((m as any).key.type === 'NumericLiteral') {
1477+
types.add('Number')
1478+
} else {
1479+
types.add('String')
1480+
}
14761481
} else {
1477-
types.add('Object')
1482+
if (
1483+
m.type === 'TSCallSignatureDeclaration' ||
1484+
m.type === 'TSConstructSignatureDeclaration'
1485+
) {
1486+
types.add('Function')
1487+
} else {
1488+
types.add('Object')
1489+
}
14781490
}
14791491
}
1492+
14801493
return types.size ? Array.from(types) : ['Object']
14811494
}
14821495
case 'TSPropertySignature':
@@ -1512,8 +1525,11 @@ export function inferRuntimeType(
15121525
case 'TSTypeReference': {
15131526
const resolved = resolveTypeReference(ctx, node, scope)
15141527
if (resolved) {
1515-
return inferRuntimeType(ctx, resolved, resolved._ownerScope)
1528+
return inferRuntimeType(ctx, resolved, resolved._ownerScope, options)
15161529
}
1530+
1531+
if (isKeyof) return ['String']
1532+
15171533
if (node.typeName.type === 'Identifier') {
15181534
switch (node.typeName.name) {
15191535
case 'Array':
@@ -1634,15 +1650,17 @@ export function inferRuntimeType(
16341650
// typeof only support identifier in local scope
16351651
const matched = scope.declares[id.name]
16361652
if (matched) {
1637-
return inferRuntimeType(ctx, matched, matched._ownerScope)
1653+
return inferRuntimeType(ctx, matched, matched._ownerScope, options)
16381654
}
16391655
}
16401656
break
16411657
}
16421658

16431659
// e.g. readonly
16441660
case 'TSTypeOperator': {
1645-
return inferRuntimeType(ctx, node.typeAnnotation, scope)
1661+
return inferRuntimeType(ctx, node.typeAnnotation, scope, {
1662+
isKeyof: node.operator === 'keyof',
1663+
})
16461664
}
16471665
}
16481666
} catch (e) {

0 commit comments

Comments
 (0)