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,
|
registerTS,
|
||||||
resolveTypeElements,
|
resolveTypeElements,
|
||||||
} from '../../src/script/resolveType'
|
} from '../../src/script/resolveType'
|
||||||
|
import { UNKNOWN_TYPE } from '../../src/script/utils'
|
||||||
import ts from 'typescript'
|
import ts from 'typescript'
|
||||||
|
|
||||||
registerTS(() => ts)
|
registerTS(() => ts)
|
||||||
|
|
||||||
describe('resolveType', () => {
|
describe('resolveType', () => {
|
||||||
|
@ -128,7 +129,7 @@ describe('resolveType', () => {
|
||||||
defineProps<{ self: any } & Foo & Bar & Baz>()
|
defineProps<{ self: any } & Foo & Bar & Baz>()
|
||||||
`).props,
|
`).props,
|
||||||
).toStrictEqual({
|
).toStrictEqual({
|
||||||
self: ['Unknown'],
|
self: [UNKNOWN_TYPE],
|
||||||
foo: ['Number'],
|
foo: ['Number'],
|
||||||
// both Bar & Baz has 'bar', but Baz['bar] is wider so it should be
|
// both Bar & Baz has 'bar', but Baz['bar] is wider so it should be
|
||||||
// preferred
|
// preferred
|
||||||
|
@ -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)', () => {
|
test('ExtractPropTypes (element-plus)', () => {
|
||||||
const { props, raw } = resolve(
|
const { props, raw } = resolve(
|
||||||
`
|
`
|
||||||
|
|
|
@ -1476,6 +1476,17 @@ export function inferRuntimeType(
|
||||||
m.key.type === 'NumericLiteral'
|
m.key.type === 'NumericLiteral'
|
||||||
) {
|
) {
|
||||||
types.add('Number')
|
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 {
|
} else {
|
||||||
types.add('String')
|
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':
|
case 'TSPropertySignature':
|
||||||
if (node.typeAnnotation) {
|
if (node.typeAnnotation) {
|
||||||
|
@ -1533,13 +1546,54 @@ export function inferRuntimeType(
|
||||||
case 'String':
|
case 'String':
|
||||||
case 'Array':
|
case 'Array':
|
||||||
case 'ArrayLike':
|
case 'ArrayLike':
|
||||||
|
case 'Parameters':
|
||||||
|
case 'ConstructorParameters':
|
||||||
case 'ReadonlyArray':
|
case 'ReadonlyArray':
|
||||||
return ['String', 'Number']
|
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']
|
return ['String']
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
|
|
||||||
switch (node.typeName.name) {
|
switch (node.typeName.name) {
|
||||||
case 'Array':
|
case 'Array':
|
||||||
case 'Function':
|
case 'Function':
|
||||||
|
@ -1610,6 +1664,7 @@ export function inferRuntimeType(
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
// cannot infer, fallback to UNKNOWN: ThisParameterType
|
// cannot infer, fallback to UNKNOWN: ThisParameterType
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
@ -1674,6 +1729,13 @@ export function inferRuntimeType(
|
||||||
node.operator === 'keyof',
|
node.operator === 'keyof',
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case 'TSAnyKeyword': {
|
||||||
|
if (isKeyOf) {
|
||||||
|
return ['String', 'Number', 'Symbol']
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// always soft fail on failed runtime type inference
|
// always soft fail on failed runtime type inference
|
||||||
|
|
Loading…
Reference in New Issue