parent
634519720a
commit
eb5e307c0b
|
@ -455,6 +455,88 @@ describe('resolveType', () => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('generics', () => {
|
||||||
|
test('generic with type literal', () => {
|
||||||
|
expect(
|
||||||
|
resolve(`
|
||||||
|
type Props<T> = T
|
||||||
|
defineProps<Props<{ foo: string }>>()
|
||||||
|
`).props
|
||||||
|
).toStrictEqual({
|
||||||
|
foo: ['String']
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('generic used in intersection', () => {
|
||||||
|
expect(
|
||||||
|
resolve(`
|
||||||
|
type Foo = { foo: string; }
|
||||||
|
type Bar = { bar: number; }
|
||||||
|
type Props<T,U> = T & U & { baz: boolean }
|
||||||
|
defineProps<Props<Foo, Bar>>()
|
||||||
|
`).props
|
||||||
|
).toStrictEqual({
|
||||||
|
foo: ['String'],
|
||||||
|
bar: ['Number'],
|
||||||
|
baz: ['Boolean']
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('generic type /w generic type alias', () => {
|
||||||
|
expect(
|
||||||
|
resolve(`
|
||||||
|
type Aliased<T> = Readonly<Partial<T>>
|
||||||
|
type Props<T> = Aliased<T>
|
||||||
|
type Foo = { foo: string; }
|
||||||
|
defineProps<Props<Foo>>()
|
||||||
|
`).props
|
||||||
|
).toStrictEqual({
|
||||||
|
foo: ['String']
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('generic type /w aliased type literal', () => {
|
||||||
|
expect(
|
||||||
|
resolve(`
|
||||||
|
type Aliased<T> = { foo: T }
|
||||||
|
defineProps<Aliased<string>>()
|
||||||
|
`).props
|
||||||
|
).toStrictEqual({
|
||||||
|
foo: ['String']
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('generic type /w interface', () => {
|
||||||
|
expect(
|
||||||
|
resolve(`
|
||||||
|
interface Props<T> {
|
||||||
|
foo: T
|
||||||
|
}
|
||||||
|
type Foo = string
|
||||||
|
defineProps<Props<Foo>>()
|
||||||
|
`).props
|
||||||
|
).toStrictEqual({
|
||||||
|
foo: ['String']
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('generic from external-file', () => {
|
||||||
|
const files = {
|
||||||
|
'/foo.ts': 'export type P<T> = { foo: T }'
|
||||||
|
}
|
||||||
|
const { props } = resolve(
|
||||||
|
`
|
||||||
|
import { P } from './foo'
|
||||||
|
defineProps<P<string>>()
|
||||||
|
`,
|
||||||
|
files
|
||||||
|
)
|
||||||
|
expect(props).toStrictEqual({
|
||||||
|
foo: ['String']
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe('external type imports', () => {
|
describe('external type imports', () => {
|
||||||
test('relative ts', () => {
|
test('relative ts', () => {
|
||||||
const files = {
|
const files = {
|
||||||
|
|
|
@ -118,7 +118,8 @@ interface ResolvedElements {
|
||||||
export function resolveTypeElements(
|
export function resolveTypeElements(
|
||||||
ctx: TypeResolveContext,
|
ctx: TypeResolveContext,
|
||||||
node: Node & MaybeWithScope & { _resolvedElements?: ResolvedElements },
|
node: Node & MaybeWithScope & { _resolvedElements?: ResolvedElements },
|
||||||
scope?: TypeScope
|
scope?: TypeScope,
|
||||||
|
typeParameters?: Record<string, Node>
|
||||||
): ResolvedElements {
|
): ResolvedElements {
|
||||||
if (node._resolvedElements) {
|
if (node._resolvedElements) {
|
||||||
return node._resolvedElements
|
return node._resolvedElements
|
||||||
|
@ -126,30 +127,37 @@ export function resolveTypeElements(
|
||||||
return (node._resolvedElements = innerResolveTypeElements(
|
return (node._resolvedElements = innerResolveTypeElements(
|
||||||
ctx,
|
ctx,
|
||||||
node,
|
node,
|
||||||
node._ownerScope || scope || ctxToScope(ctx)
|
node._ownerScope || scope || ctxToScope(ctx),
|
||||||
|
typeParameters
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
function innerResolveTypeElements(
|
function innerResolveTypeElements(
|
||||||
ctx: TypeResolveContext,
|
ctx: TypeResolveContext,
|
||||||
node: Node,
|
node: Node,
|
||||||
scope: TypeScope
|
scope: TypeScope,
|
||||||
|
typeParameters?: Record<string, Node>
|
||||||
): ResolvedElements {
|
): ResolvedElements {
|
||||||
switch (node.type) {
|
switch (node.type) {
|
||||||
case 'TSTypeLiteral':
|
case 'TSTypeLiteral':
|
||||||
return typeElementsToMap(ctx, node.members, scope)
|
return typeElementsToMap(ctx, node.members, scope, typeParameters)
|
||||||
case 'TSInterfaceDeclaration':
|
case 'TSInterfaceDeclaration':
|
||||||
return resolveInterfaceMembers(ctx, node, scope)
|
return resolveInterfaceMembers(ctx, node, scope, typeParameters)
|
||||||
case 'TSTypeAliasDeclaration':
|
case 'TSTypeAliasDeclaration':
|
||||||
case 'TSParenthesizedType':
|
case 'TSParenthesizedType':
|
||||||
return resolveTypeElements(ctx, node.typeAnnotation, scope)
|
return resolveTypeElements(
|
||||||
|
ctx,
|
||||||
|
node.typeAnnotation,
|
||||||
|
scope,
|
||||||
|
typeParameters
|
||||||
|
)
|
||||||
case 'TSFunctionType': {
|
case 'TSFunctionType': {
|
||||||
return { props: {}, calls: [node] }
|
return { props: {}, calls: [node] }
|
||||||
}
|
}
|
||||||
case 'TSUnionType':
|
case 'TSUnionType':
|
||||||
case 'TSIntersectionType':
|
case 'TSIntersectionType':
|
||||||
return mergeElements(
|
return mergeElements(
|
||||||
node.types.map(t => resolveTypeElements(ctx, t, scope)),
|
node.types.map(t => resolveTypeElements(ctx, t, scope, typeParameters)),
|
||||||
node.type
|
node.type
|
||||||
)
|
)
|
||||||
case 'TSMappedType':
|
case 'TSMappedType':
|
||||||
|
@ -171,20 +179,57 @@ function innerResolveTypeElements(
|
||||||
scope.imports[typeName]?.source === 'vue'
|
scope.imports[typeName]?.source === 'vue'
|
||||||
) {
|
) {
|
||||||
return resolveExtractPropTypes(
|
return resolveExtractPropTypes(
|
||||||
resolveTypeElements(ctx, node.typeParameters.params[0], scope),
|
resolveTypeElements(
|
||||||
|
ctx,
|
||||||
|
node.typeParameters.params[0],
|
||||||
|
scope,
|
||||||
|
typeParameters
|
||||||
|
),
|
||||||
scope
|
scope
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
const resolved = resolveTypeReference(ctx, node, scope)
|
const resolved = resolveTypeReference(ctx, node, scope)
|
||||||
if (resolved) {
|
if (resolved) {
|
||||||
return resolveTypeElements(ctx, resolved, resolved._ownerScope)
|
const typeParams: Record<string, Node> = Object.create(null)
|
||||||
|
if (
|
||||||
|
(resolved.type === 'TSTypeAliasDeclaration' ||
|
||||||
|
resolved.type === 'TSInterfaceDeclaration') &&
|
||||||
|
resolved.typeParameters &&
|
||||||
|
node.typeParameters
|
||||||
|
) {
|
||||||
|
resolved.typeParameters.params.forEach((p, i) => {
|
||||||
|
let param = typeParameters && typeParameters[p.name]
|
||||||
|
if (!param) param = node.typeParameters!.params[i]
|
||||||
|
typeParams[p.name] = param
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return resolveTypeElements(
|
||||||
|
ctx,
|
||||||
|
resolved,
|
||||||
|
resolved._ownerScope,
|
||||||
|
typeParams
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
if (typeof typeName === 'string') {
|
if (typeof typeName === 'string') {
|
||||||
|
if (typeParameters && typeParameters[typeName]) {
|
||||||
|
return resolveTypeElements(
|
||||||
|
ctx,
|
||||||
|
typeParameters[typeName],
|
||||||
|
scope,
|
||||||
|
typeParameters
|
||||||
|
)
|
||||||
|
}
|
||||||
if (
|
if (
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
SupportedBuiltinsSet.has(typeName)
|
SupportedBuiltinsSet.has(typeName)
|
||||||
) {
|
) {
|
||||||
return resolveBuiltin(ctx, node, typeName as any, scope)
|
return resolveBuiltin(
|
||||||
|
ctx,
|
||||||
|
node,
|
||||||
|
typeName as any,
|
||||||
|
scope,
|
||||||
|
typeParameters
|
||||||
|
)
|
||||||
} else if (typeName === 'ReturnType' && node.typeParameters) {
|
} else if (typeName === 'ReturnType' && node.typeParameters) {
|
||||||
// limited support, only reference types
|
// limited support, only reference types
|
||||||
const ret = resolveReturnType(
|
const ret = resolveReturnType(
|
||||||
|
@ -243,11 +288,17 @@ function innerResolveTypeElements(
|
||||||
function typeElementsToMap(
|
function typeElementsToMap(
|
||||||
ctx: TypeResolveContext,
|
ctx: TypeResolveContext,
|
||||||
elements: TSTypeElement[],
|
elements: TSTypeElement[],
|
||||||
scope = ctxToScope(ctx)
|
scope = ctxToScope(ctx),
|
||||||
|
typeParameters?: Record<string, Node>
|
||||||
): ResolvedElements {
|
): ResolvedElements {
|
||||||
const res: ResolvedElements = { props: {} }
|
const res: ResolvedElements = { props: {} }
|
||||||
for (const e of elements) {
|
for (const e of elements) {
|
||||||
if (e.type === 'TSPropertySignature' || e.type === 'TSMethodSignature') {
|
if (e.type === 'TSPropertySignature' || e.type === 'TSMethodSignature') {
|
||||||
|
// capture generic parameters on node's scope
|
||||||
|
if (typeParameters) {
|
||||||
|
scope = createChildScope(scope)
|
||||||
|
Object.assign(scope.types, typeParameters)
|
||||||
|
}
|
||||||
;(e as MaybeWithScope)._ownerScope = scope
|
;(e as MaybeWithScope)._ownerScope = scope
|
||||||
const name = getId(e.key)
|
const name = getId(e.key)
|
||||||
if (name && !e.computed) {
|
if (name && !e.computed) {
|
||||||
|
@ -323,9 +374,15 @@ function createProperty(
|
||||||
function resolveInterfaceMembers(
|
function resolveInterfaceMembers(
|
||||||
ctx: TypeResolveContext,
|
ctx: TypeResolveContext,
|
||||||
node: TSInterfaceDeclaration & MaybeWithScope,
|
node: TSInterfaceDeclaration & MaybeWithScope,
|
||||||
scope: TypeScope
|
scope: TypeScope,
|
||||||
|
typeParameters?: Record<string, Node>
|
||||||
): ResolvedElements {
|
): ResolvedElements {
|
||||||
const base = typeElementsToMap(ctx, node.body.body, node._ownerScope)
|
const base = typeElementsToMap(
|
||||||
|
ctx,
|
||||||
|
node.body.body,
|
||||||
|
node._ownerScope,
|
||||||
|
typeParameters
|
||||||
|
)
|
||||||
if (node.extends) {
|
if (node.extends) {
|
||||||
for (const ext of node.extends) {
|
for (const ext of node.extends) {
|
||||||
if (
|
if (
|
||||||
|
@ -543,9 +600,15 @@ function resolveBuiltin(
|
||||||
ctx: TypeResolveContext,
|
ctx: TypeResolveContext,
|
||||||
node: TSTypeReference | TSExpressionWithTypeArguments,
|
node: TSTypeReference | TSExpressionWithTypeArguments,
|
||||||
name: GetSetType<typeof SupportedBuiltinsSet>,
|
name: GetSetType<typeof SupportedBuiltinsSet>,
|
||||||
scope: TypeScope
|
scope: TypeScope,
|
||||||
|
typeParameters?: Record<string, Node>
|
||||||
): ResolvedElements {
|
): ResolvedElements {
|
||||||
const t = resolveTypeElements(ctx, node.typeParameters!.params[0], scope)
|
const t = resolveTypeElements(
|
||||||
|
ctx,
|
||||||
|
node.typeParameters!.params[0],
|
||||||
|
scope,
|
||||||
|
typeParameters
|
||||||
|
)
|
||||||
switch (name) {
|
switch (name) {
|
||||||
case 'Partial': {
|
case 'Partial': {
|
||||||
const res: ResolvedElements = { props: {}, calls: t.calls }
|
const res: ResolvedElements = { props: {}, calls: t.calls }
|
||||||
|
@ -1103,14 +1166,7 @@ function moduleDeclToScope(
|
||||||
return node._resolvedChildScope
|
return node._resolvedChildScope
|
||||||
}
|
}
|
||||||
|
|
||||||
const scope = new TypeScope(
|
const scope = createChildScope(parentScope)
|
||||||
parentScope.filename,
|
|
||||||
parentScope.source,
|
|
||||||
parentScope.offset,
|
|
||||||
Object.create(parentScope.imports),
|
|
||||||
Object.create(parentScope.types),
|
|
||||||
Object.create(parentScope.declares)
|
|
||||||
)
|
|
||||||
|
|
||||||
if (node.body.type === 'TSModuleDeclaration') {
|
if (node.body.type === 'TSModuleDeclaration') {
|
||||||
const decl = node.body as TSModuleDeclaration & WithScope
|
const decl = node.body as TSModuleDeclaration & WithScope
|
||||||
|
@ -1124,6 +1180,17 @@ function moduleDeclToScope(
|
||||||
return (node._resolvedChildScope = scope)
|
return (node._resolvedChildScope = scope)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function createChildScope(parentScope: TypeScope) {
|
||||||
|
return new TypeScope(
|
||||||
|
parentScope.filename,
|
||||||
|
parentScope.source,
|
||||||
|
parentScope.offset,
|
||||||
|
Object.create(parentScope.imports),
|
||||||
|
Object.create(parentScope.types),
|
||||||
|
Object.create(parentScope.declares)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
const importExportRE = /^Import|^Export/
|
const importExportRE = /^Import|^Export/
|
||||||
|
|
||||||
function recordTypes(
|
function recordTypes(
|
||||||
|
@ -1262,7 +1329,7 @@ function recordType(
|
||||||
if (overwriteId || node.id) types[overwriteId || getId(node.id!)] = node
|
if (overwriteId || node.id) types[overwriteId || getId(node.id!)] = node
|
||||||
break
|
break
|
||||||
case 'TSTypeAliasDeclaration':
|
case 'TSTypeAliasDeclaration':
|
||||||
types[node.id.name] = node.typeAnnotation
|
types[node.id.name] = node.typeParameters ? node : node.typeAnnotation
|
||||||
break
|
break
|
||||||
case 'TSDeclareFunction':
|
case 'TSDeclareFunction':
|
||||||
if (node.id) declares[node.id.name] = node
|
if (node.id) declares[node.id.name] = node
|
||||||
|
|
Loading…
Reference in New Issue