Skip to content

Commit 3522d6f

Browse files
committed
fix: support more cases
1 parent 8a8a0b6 commit 3522d6f

File tree

2 files changed

+120
-68
lines changed

2 files changed

+120
-68
lines changed

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

Lines changed: 82 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -347,46 +347,6 @@ describe('resolveType', () => {
347347
})
348348
})
349349

350-
test('generic type', () => {
351-
expect(
352-
resolve(`
353-
type Foo = { foo: string; }
354-
type Bar = { bar: number; }
355-
type Props<T,U> = T & U & { baz: boolean }
356-
defineProps<Props<Foo, Bar>>()
357-
`).props
358-
).toStrictEqual({
359-
foo: ['String'],
360-
bar: ['Number'],
361-
baz: ['Boolean']
362-
})
363-
})
364-
365-
test('generic type /w generic type alias', () => {
366-
expect(
367-
resolve(`
368-
type Aliased<T> = Readonly<Partial<T>>
369-
type Props<T> = Aliased<T>
370-
type Foo = { foo: string; }
371-
defineProps<Props<Foo>>()
372-
`).props
373-
).toStrictEqual({
374-
foo: ['String']
375-
})
376-
})
377-
378-
test('generic type /w simple type alias', () => {
379-
expect(
380-
resolve(`
381-
type Aliased<T> = T
382-
type Foo = { foo: string; }
383-
defineProps<Aliased<Foo>>()
384-
`).props
385-
).toStrictEqual({
386-
foo: ['String']
387-
})
388-
})
389-
390350
test('namespace merging', () => {
391351
expect(
392352
resolve(`
@@ -495,6 +455,88 @@ describe('resolveType', () => {
495455
})
496456
})
497457

458+
describe('generics', () => {
459+
test('generic with type literal', () => {
460+
expect(
461+
resolve(`
462+
type Props<T> = T
463+
defineProps<Props<{ foo: string }>>()
464+
`).props
465+
).toStrictEqual({
466+
foo: ['String']
467+
})
468+
})
469+
470+
test('generic used in intersection', () => {
471+
expect(
472+
resolve(`
473+
type Foo = { foo: string; }
474+
type Bar = { bar: number; }
475+
type Props<T,U> = T & U & { baz: boolean }
476+
defineProps<Props<Foo, Bar>>()
477+
`).props
478+
).toStrictEqual({
479+
foo: ['String'],
480+
bar: ['Number'],
481+
baz: ['Boolean']
482+
})
483+
})
484+
485+
test('generic type /w generic type alias', () => {
486+
expect(
487+
resolve(`
488+
type Aliased<T> = Readonly<Partial<T>>
489+
type Props<T> = Aliased<T>
490+
type Foo = { foo: string; }
491+
defineProps<Props<Foo>>()
492+
`).props
493+
).toStrictEqual({
494+
foo: ['String']
495+
})
496+
})
497+
498+
test('generic type /w aliased type literal', () => {
499+
expect(
500+
resolve(`
501+
type Aliased<T> = { foo: T }
502+
defineProps<Aliased<string>>()
503+
`).props
504+
).toStrictEqual({
505+
foo: ['String']
506+
})
507+
})
508+
509+
test('generic type /w interface', () => {
510+
expect(
511+
resolve(`
512+
interface Props<T> {
513+
foo: T
514+
}
515+
type Foo = string
516+
defineProps<Props<Foo>>()
517+
`).props
518+
).toStrictEqual({
519+
foo: ['String']
520+
})
521+
})
522+
523+
test('generic from external-file', () => {
524+
const files = {
525+
'/foo.ts': 'export type P<T> = { foo: T }'
526+
}
527+
const { props } = resolve(
528+
`
529+
import { P } from './foo'
530+
defineProps<P<string>>()
531+
`,
532+
files
533+
)
534+
expect(props).toStrictEqual({
535+
foo: ['String']
536+
})
537+
})
538+
})
539+
498540
describe('external type imports', () => {
499541
test('relative ts', () => {
500542
const files = {

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

Lines changed: 38 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -140,9 +140,9 @@ function innerResolveTypeElements(
140140
): ResolvedElements {
141141
switch (node.type) {
142142
case 'TSTypeLiteral':
143-
return typeElementsToMap(ctx, node.members, scope)
143+
return typeElementsToMap(ctx, node.members, scope, typeParameters)
144144
case 'TSInterfaceDeclaration':
145-
return resolveInterfaceMembers(ctx, node, scope)
145+
return resolveInterfaceMembers(ctx, node, scope, typeParameters)
146146
case 'TSTypeAliasDeclaration':
147147
case 'TSParenthesizedType':
148148
return resolveTypeElements(
@@ -156,20 +156,8 @@ function innerResolveTypeElements(
156156
}
157157
case 'TSUnionType':
158158
case 'TSIntersectionType':
159-
const types = typeParameters
160-
? node.types.map(t => {
161-
if (
162-
t.type === 'TSTypeReference' &&
163-
t.typeName.type === 'Identifier' &&
164-
typeParameters[t.typeName.name]
165-
) {
166-
return typeParameters[t.typeName.name]
167-
}
168-
return t
169-
})
170-
: node.types
171159
return mergeElements(
172-
types.map(t => resolveTypeElements(ctx, t, scope)),
160+
node.types.map(t => resolveTypeElements(ctx, t, scope, typeParameters)),
173161
node.type
174162
)
175163
case 'TSMappedType':
@@ -191,15 +179,21 @@ function innerResolveTypeElements(
191179
scope.imports[typeName]?.source === 'vue'
192180
) {
193181
return resolveExtractPropTypes(
194-
resolveTypeElements(ctx, node.typeParameters.params[0], scope),
182+
resolveTypeElements(
183+
ctx,
184+
node.typeParameters.params[0],
185+
scope,
186+
typeParameters
187+
),
195188
scope
196189
)
197190
}
198191
const resolved = resolveTypeReference(ctx, node, scope)
199192
if (resolved) {
200193
const typeParams: Record<string, Node> = Object.create(null)
201194
if (
202-
resolved.type === 'TSTypeAliasDeclaration' &&
195+
(resolved.type === 'TSTypeAliasDeclaration' ||
196+
resolved.type === 'TSInterfaceDeclaration') &&
203197
resolved.typeParameters &&
204198
node.typeParameters
205199
) {
@@ -294,11 +288,17 @@ function innerResolveTypeElements(
294288
function typeElementsToMap(
295289
ctx: TypeResolveContext,
296290
elements: TSTypeElement[],
297-
scope = ctxToScope(ctx)
291+
scope = ctxToScope(ctx),
292+
typeParameters?: Record<string, Node>
298293
): ResolvedElements {
299294
const res: ResolvedElements = { props: {} }
300295
for (const e of elements) {
301296
if (e.type === 'TSPropertySignature' || e.type === 'TSMethodSignature') {
297+
// capture generic parameters on node's scope
298+
if (typeParameters) {
299+
scope = createChildScope(scope)
300+
Object.assign(scope.types, typeParameters)
301+
}
302302
;(e as MaybeWithScope)._ownerScope = scope
303303
const name = getId(e.key)
304304
if (name && !e.computed) {
@@ -374,9 +374,15 @@ function createProperty(
374374
function resolveInterfaceMembers(
375375
ctx: TypeResolveContext,
376376
node: TSInterfaceDeclaration & MaybeWithScope,
377-
scope: TypeScope
377+
scope: TypeScope,
378+
typeParameters?: Record<string, Node>
378379
): ResolvedElements {
379-
const base = typeElementsToMap(ctx, node.body.body, node._ownerScope)
380+
const base = typeElementsToMap(
381+
ctx,
382+
node.body.body,
383+
node._ownerScope,
384+
typeParameters
385+
)
380386
if (node.extends) {
381387
for (const ext of node.extends) {
382388
if (
@@ -1160,14 +1166,7 @@ function moduleDeclToScope(
11601166
return node._resolvedChildScope
11611167
}
11621168

1163-
const scope = new TypeScope(
1164-
parentScope.filename,
1165-
parentScope.source,
1166-
parentScope.offset,
1167-
Object.create(parentScope.imports),
1168-
Object.create(parentScope.types),
1169-
Object.create(parentScope.declares)
1170-
)
1169+
const scope = createChildScope(parentScope)
11711170

11721171
if (node.body.type === 'TSModuleDeclaration') {
11731172
const decl = node.body as TSModuleDeclaration & WithScope
@@ -1181,6 +1180,17 @@ function moduleDeclToScope(
11811180
return (node._resolvedChildScope = scope)
11821181
}
11831182

1183+
function createChildScope(parentScope: TypeScope) {
1184+
return new TypeScope(
1185+
parentScope.filename,
1186+
parentScope.source,
1187+
parentScope.offset,
1188+
Object.create(parentScope.imports),
1189+
Object.create(parentScope.types),
1190+
Object.create(parentScope.declares)
1191+
)
1192+
}
1193+
11841194
const importExportRE = /^Import|^Export/
11851195

11861196
function recordTypes(

0 commit comments

Comments
 (0)