mirror of https://github.com/vuejs/core.git
fix(compiler-sfc): improve type resolving for the keyof operator (#10921)
close #10920 close #11002
This commit is contained in:
parent
5afc76c229
commit
293cf4e131
|
@ -9,8 +9,9 @@ import {
|
|||
registerTS,
|
||||
resolveTypeElements,
|
||||
} from '../../src/script/resolveType'
|
||||
|
||||
import { UNKNOWN_TYPE } from '../../src/script/utils'
|
||||
import ts from 'typescript'
|
||||
|
||||
registerTS(() => ts)
|
||||
|
||||
describe('resolveType', () => {
|
||||
|
@ -128,7 +129,7 @@ describe('resolveType', () => {
|
|||
defineProps<{ self: any } & Foo & Bar & Baz>()
|
||||
`).props,
|
||||
).toStrictEqual({
|
||||
self: ['Unknown'],
|
||||
self: [UNKNOWN_TYPE],
|
||||
foo: ['Number'],
|
||||
// both Bar & Baz has 'bar', but Baz['bar] is wider so it should be
|
||||
// preferred
|
||||
|
@ -455,13 +456,13 @@ describe('resolveType', () => {
|
|||
const { props } = resolve(
|
||||
`
|
||||
import { IMP } from './foo'
|
||||
interface Foo { foo: 1, ${1}: 1 }
|
||||
interface Foo { foo: 1, ${1}: 1 }
|
||||
type Bar = { bar: 1 }
|
||||
declare const obj: Bar
|
||||
declare const set: Set<any>
|
||||
declare const arr: Array<any>
|
||||
|
||||
defineProps<{
|
||||
defineProps<{
|
||||
imp: keyof IMP,
|
||||
foo: keyof Foo,
|
||||
bar: keyof Bar,
|
||||
|
@ -483,6 +484,81 @@ describe('resolveType', () => {
|
|||
})
|
||||
})
|
||||
|
||||
test('keyof: index signature', () => {
|
||||
const { props } = resolve(
|
||||
`
|
||||
declare const num: number;
|
||||
interface Foo {
|
||||
[key: symbol]: 1
|
||||
[key: string]: 1
|
||||
[key: typeof num]: 1,
|
||||
}
|
||||
|
||||
type Test<T> = T
|
||||
type Bar = {
|
||||
[key: string]: 1
|
||||
[key: Test<number>]: 1
|
||||
}
|
||||
|
||||
defineProps<{
|
||||
foo: keyof Foo
|
||||
bar: keyof Bar
|
||||
}>()
|
||||
`,
|
||||
)
|
||||
|
||||
expect(props).toStrictEqual({
|
||||
foo: ['Symbol', 'String', 'Number'],
|
||||
bar: [UNKNOWN_TYPE],
|
||||
})
|
||||
})
|
||||
|
||||
test('keyof: utility type', () => {
|
||||
const { props } = resolve(
|
||||
`
|
||||
type Foo = Record<symbol | string, any>
|
||||
type Bar = { [key: string]: any }
|
||||
type AnyRecord = Record<keyof any, any>
|
||||
type Baz = { a: 1, ${1}: 2, b: 3}
|
||||
|
||||
defineProps<{
|
||||
record: keyof Foo,
|
||||
anyRecord: keyof AnyRecord
|
||||
partial: keyof Partial<Bar>,
|
||||
required: keyof Required<Bar>,
|
||||
readonly: keyof Readonly<Bar>,
|
||||
pick: keyof Pick<Baz, 'a' | 1>
|
||||
extract: keyof Extract<keyof Baz, 'a' | 1>
|
||||
}>()
|
||||
`,
|
||||
)
|
||||
|
||||
expect(props).toStrictEqual({
|
||||
record: ['Symbol', 'String'],
|
||||
anyRecord: ['String', 'Number', 'Symbol'],
|
||||
partial: ['String'],
|
||||
required: ['String'],
|
||||
readonly: ['String'],
|
||||
pick: ['String', 'Number'],
|
||||
extract: ['String', 'Number'],
|
||||
})
|
||||
})
|
||||
|
||||
test('keyof: fallback to Unknown', () => {
|
||||
const { props } = resolve(
|
||||
`
|
||||
interface Barr {}
|
||||
interface Bar extends Barr {}
|
||||
type Foo = keyof Bar
|
||||
defineProps<{ foo: Foo }>()
|
||||
`,
|
||||
)
|
||||
|
||||
expect(props).toStrictEqual({
|
||||
foo: [UNKNOWN_TYPE],
|
||||
})
|
||||
})
|
||||
|
||||
test('ExtractPropTypes (element-plus)', () => {
|
||||
const { props, raw } = resolve(
|
||||
`
|
||||
|
|
|
@ -1476,6 +1476,17 @@ export function inferRuntimeType(
|
|||
m.key.type === 'NumericLiteral'
|
||||
) {
|
||||
types.add('Number')
|
||||
} else if (m.type === 'TSIndexSignature') {
|
||||
const annotation = m.parameters[0].typeAnnotation
|
||||
if (annotation && annotation.type !== 'Noop') {
|
||||
const type = inferRuntimeType(
|
||||
ctx,
|
||||
annotation.typeAnnotation,
|
||||
scope,
|
||||
)[0]
|
||||
if (type === UNKNOWN_TYPE) return [UNKNOWN_TYPE]
|
||||
types.add(type)
|
||||
}
|
||||
} else {
|
||||
types.add('String')
|
||||
}
|
||||
|
@ -1489,7 +1500,9 @@ export function inferRuntimeType(
|
|||
}
|
||||
}
|
||||
|
||||
return types.size ? Array.from(types) : ['Object']
|
||||
return types.size
|
||||
? Array.from(types)
|
||||
: [isKeyOf ? UNKNOWN_TYPE : 'Object']
|
||||
}
|
||||
case 'TSPropertySignature':
|
||||
if (node.typeAnnotation) {
|
||||
|
@ -1533,81 +1546,123 @@ export function inferRuntimeType(
|
|||
case 'String':
|
||||
case 'Array':
|
||||
case 'ArrayLike':
|
||||
case 'Parameters':
|
||||
case 'ConstructorParameters':
|
||||
case 'ReadonlyArray':
|
||||
return ['String', 'Number']
|
||||
default:
|
||||
|
||||
// TS built-in utility types
|
||||
case 'Record':
|
||||
case 'Partial':
|
||||
case 'Required':
|
||||
case 'Readonly':
|
||||
if (node.typeParameters && node.typeParameters.params[0]) {
|
||||
return inferRuntimeType(
|
||||
ctx,
|
||||
node.typeParameters.params[0],
|
||||
scope,
|
||||
true,
|
||||
)
|
||||
}
|
||||
break
|
||||
case 'Pick':
|
||||
case 'Extract':
|
||||
if (node.typeParameters && node.typeParameters.params[1]) {
|
||||
return inferRuntimeType(
|
||||
ctx,
|
||||
node.typeParameters.params[1],
|
||||
scope,
|
||||
)
|
||||
}
|
||||
break
|
||||
|
||||
case 'Function':
|
||||
case 'Object':
|
||||
case 'Set':
|
||||
case 'Map':
|
||||
case 'WeakSet':
|
||||
case 'WeakMap':
|
||||
case 'Date':
|
||||
case 'Promise':
|
||||
case 'Error':
|
||||
case 'Uppercase':
|
||||
case 'Lowercase':
|
||||
case 'Capitalize':
|
||||
case 'Uncapitalize':
|
||||
case 'ReadonlyMap':
|
||||
case 'ReadonlySet':
|
||||
return ['String']
|
||||
}
|
||||
}
|
||||
} else {
|
||||
switch (node.typeName.name) {
|
||||
case 'Array':
|
||||
case 'Function':
|
||||
case 'Object':
|
||||
case 'Set':
|
||||
case 'Map':
|
||||
case 'WeakSet':
|
||||
case 'WeakMap':
|
||||
case 'Date':
|
||||
case 'Promise':
|
||||
case 'Error':
|
||||
return [node.typeName.name]
|
||||
|
||||
switch (node.typeName.name) {
|
||||
case 'Array':
|
||||
case 'Function':
|
||||
case 'Object':
|
||||
case 'Set':
|
||||
case 'Map':
|
||||
case 'WeakSet':
|
||||
case 'WeakMap':
|
||||
case 'Date':
|
||||
case 'Promise':
|
||||
case 'Error':
|
||||
return [node.typeName.name]
|
||||
// TS built-in utility types
|
||||
// https://www.typescriptlang.org/docs/handbook/utility-types.html
|
||||
case 'Partial':
|
||||
case 'Required':
|
||||
case 'Readonly':
|
||||
case 'Record':
|
||||
case 'Pick':
|
||||
case 'Omit':
|
||||
case 'InstanceType':
|
||||
return ['Object']
|
||||
|
||||
// TS built-in utility types
|
||||
// https://www.typescriptlang.org/docs/handbook/utility-types.html
|
||||
case 'Partial':
|
||||
case 'Required':
|
||||
case 'Readonly':
|
||||
case 'Record':
|
||||
case 'Pick':
|
||||
case 'Omit':
|
||||
case 'InstanceType':
|
||||
return ['Object']
|
||||
case 'Uppercase':
|
||||
case 'Lowercase':
|
||||
case 'Capitalize':
|
||||
case 'Uncapitalize':
|
||||
return ['String']
|
||||
|
||||
case 'Uppercase':
|
||||
case 'Lowercase':
|
||||
case 'Capitalize':
|
||||
case 'Uncapitalize':
|
||||
return ['String']
|
||||
case 'Parameters':
|
||||
case 'ConstructorParameters':
|
||||
case 'ReadonlyArray':
|
||||
return ['Array']
|
||||
|
||||
case 'Parameters':
|
||||
case 'ConstructorParameters':
|
||||
case 'ReadonlyArray':
|
||||
return ['Array']
|
||||
case 'ReadonlyMap':
|
||||
return ['Map']
|
||||
case 'ReadonlySet':
|
||||
return ['Set']
|
||||
|
||||
case 'ReadonlyMap':
|
||||
return ['Map']
|
||||
case 'ReadonlySet':
|
||||
return ['Set']
|
||||
|
||||
case 'NonNullable':
|
||||
if (node.typeParameters && node.typeParameters.params[0]) {
|
||||
return inferRuntimeType(
|
||||
ctx,
|
||||
node.typeParameters.params[0],
|
||||
scope,
|
||||
).filter(t => t !== 'null')
|
||||
}
|
||||
break
|
||||
case 'Extract':
|
||||
if (node.typeParameters && node.typeParameters.params[1]) {
|
||||
return inferRuntimeType(
|
||||
ctx,
|
||||
node.typeParameters.params[1],
|
||||
scope,
|
||||
)
|
||||
}
|
||||
break
|
||||
case 'Exclude':
|
||||
case 'OmitThisParameter':
|
||||
if (node.typeParameters && node.typeParameters.params[0]) {
|
||||
return inferRuntimeType(
|
||||
ctx,
|
||||
node.typeParameters.params[0],
|
||||
scope,
|
||||
)
|
||||
}
|
||||
break
|
||||
case 'NonNullable':
|
||||
if (node.typeParameters && node.typeParameters.params[0]) {
|
||||
return inferRuntimeType(
|
||||
ctx,
|
||||
node.typeParameters.params[0],
|
||||
scope,
|
||||
).filter(t => t !== 'null')
|
||||
}
|
||||
break
|
||||
case 'Extract':
|
||||
if (node.typeParameters && node.typeParameters.params[1]) {
|
||||
return inferRuntimeType(
|
||||
ctx,
|
||||
node.typeParameters.params[1],
|
||||
scope,
|
||||
)
|
||||
}
|
||||
break
|
||||
case 'Exclude':
|
||||
case 'OmitThisParameter':
|
||||
if (node.typeParameters && node.typeParameters.params[0]) {
|
||||
return inferRuntimeType(
|
||||
ctx,
|
||||
node.typeParameters.params[0],
|
||||
scope,
|
||||
)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
// cannot infer, fallback to UNKNOWN: ThisParameterType
|
||||
|
@ -1674,6 +1729,13 @@ export function inferRuntimeType(
|
|||
node.operator === 'keyof',
|
||||
)
|
||||
}
|
||||
|
||||
case 'TSAnyKeyword': {
|
||||
if (isKeyOf) {
|
||||
return ['String', 'Number', 'Symbol']
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// always soft fail on failed runtime type inference
|
||||
|
|
Loading…
Reference in New Issue