feat(compiler-sfc): support dynamic imports when resolving types

This commit is contained in:
Evan You 2023-04-20 16:18:35 +08:00
parent 7c3ca3cc3e
commit 4496456d7d
2 changed files with 70 additions and 23 deletions

View File

@ -472,6 +472,24 @@ describe('resolveType', () => {
expect(deps && [...deps]).toStrictEqual(Object.keys(files)) expect(deps && [...deps]).toStrictEqual(Object.keys(files))
}) })
test('relative (dynamic import)', () => {
const files = {
'/foo.ts': `export type P = { foo: string, bar: import('./bar').N }`,
'/bar.ts': 'export type N = number'
}
const { props, deps } = resolve(
`
defineProps<import('./foo').P>()
`,
files
)
expect(props).toStrictEqual({
foo: ['String'],
bar: ['Number']
})
expect(deps && [...deps]).toStrictEqual(Object.keys(files))
})
test('ts module resolve', () => { test('ts module resolve', () => {
const files = { const files = {
'/node_modules/foo/package.json': JSON.stringify({ '/node_modules/foo/package.json': JSON.stringify({

View File

@ -7,6 +7,7 @@ import {
TSEnumDeclaration, TSEnumDeclaration,
TSExpressionWithTypeArguments, TSExpressionWithTypeArguments,
TSFunctionType, TSFunctionType,
TSImportType,
TSIndexedAccessType, TSIndexedAccessType,
TSInterfaceDeclaration, TSInterfaceDeclaration,
TSMappedType, TSMappedType,
@ -168,6 +169,17 @@ function innerResolveTypeElements(
) )
} }
} }
case 'TSImportType':
const sourceScope = importSourceToScope(
ctx,
node.argument,
scope,
node.argument.value
)
const resolved = resolveTypeReference(ctx, node, sourceScope)
if (resolved) {
return resolveTypeElements(ctx, resolved, resolved._ownerScope)
}
} }
return ctx.error(`Unresolvable type: ${node.type}`, node, scope) return ctx.error(`Unresolvable type: ${node.type}`, node, scope)
} }
@ -486,9 +498,14 @@ function resolveBuiltin(
} }
} }
type ReferenceTypes =
| TSTypeReference
| TSExpressionWithTypeArguments
| TSImportType
function resolveTypeReference( function resolveTypeReference(
ctx: TypeResolveContext, ctx: TypeResolveContext,
node: (TSTypeReference | TSExpressionWithTypeArguments) & { node: ReferenceTypes & {
_resolvedReference?: ScopeTypeNode _resolvedReference?: ScopeTypeNode
}, },
scope?: TypeScope, scope?: TypeScope,
@ -511,7 +528,7 @@ function innerResolveTypeReference(
ctx: TypeResolveContext, ctx: TypeResolveContext,
scope: TypeScope, scope: TypeScope,
name: string | string[], name: string | string[],
node: TSTypeReference | TSExpressionWithTypeArguments, node: ReferenceTypes,
onlyExported: boolean onlyExported: boolean
): ScopeTypeNode | undefined { ): ScopeTypeNode | undefined {
if (typeof name === 'string') { if (typeof name === 'string') {
@ -555,11 +572,16 @@ function innerResolveTypeReference(
} }
} }
function getReferenceName( function getReferenceName(node: ReferenceTypes): string | string[] {
node: TSTypeReference | TSExpressionWithTypeArguments const ref =
): string | string[] { node.type === 'TSTypeReference'
const ref = node.type === 'TSTypeReference' ? node.typeName : node.expression ? node.typeName
if (ref.type === 'Identifier') { : node.type === 'TSExpressionWithTypeArguments'
? node.expression
: node.qualifier
if (!ref) {
return 'default'
} else if (ref.type === 'Identifier') {
return ref.name return ref.name
} else { } else {
return qualifiedNameToPath(ref) return qualifiedNameToPath(ref)
@ -599,27 +621,21 @@ type FS = NonNullable<SFCScriptCompileOptions['fs']>
function resolveTypeFromImport( function resolveTypeFromImport(
ctx: TypeResolveContext, ctx: TypeResolveContext,
node: TSTypeReference | TSExpressionWithTypeArguments, node: ReferenceTypes,
name: string, name: string,
scope: TypeScope scope: TypeScope
): ScopeTypeNode | undefined { ): ScopeTypeNode | undefined {
const { source, imported } = scope.imports[name] const { source, imported } = scope.imports[name]
const resolved = resolveImportSource(ctx, node, scope, source) const sourceScope = importSourceToScope(ctx, node, scope, source)
return resolveTypeReference( return resolveTypeReference(ctx, node, sourceScope, imported, true)
ctx,
node,
fileToScope(ctx, resolved),
imported,
true
)
} }
function resolveImportSource( function importSourceToScope(
ctx: TypeResolveContext, ctx: TypeResolveContext,
node: Node, node: Node,
scope: TypeScope, scope: TypeScope,
source: string source: string
): string { ): TypeScope {
const fs: FS = ctx.options.fs || ts?.sys const fs: FS = ctx.options.fs || ts?.sys
if (!fs) { if (!fs) {
ctx.error( ctx.error(
@ -657,7 +673,7 @@ function resolveImportSource(
if (resolved) { if (resolved) {
// (hmr) register dependency file on ctx // (hmr) register dependency file on ctx
;(ctx.deps || (ctx.deps = new Set())).add(resolved) ;(ctx.deps || (ctx.deps = new Set())).add(resolved)
return normalizePath(resolved) return fileToScope(ctx, normalizePath(resolved))
} else { } else {
return ctx.error( return ctx.error(
`Failed to resolve import source ${JSON.stringify(source)}.`, `Failed to resolve import source ${JSON.stringify(source)}.`,
@ -938,14 +954,13 @@ function recordTypes(
} }
} }
} else if (stmt.type === 'ExportAllDeclaration') { } else if (stmt.type === 'ExportAllDeclaration') {
const targetFile = resolveImportSource( const sourceScope = importSourceToScope(
ctx, ctx,
stmt.source, stmt.source,
scope, scope,
stmt.source.value stmt.source.value
) )
const targetScope = fileToScope(ctx, targetFile) Object.assign(scope.exportedTypes, sourceScope.exportedTypes)
Object.assign(scope.exportedTypes, targetScope.exportedTypes)
} }
} }
} }
@ -1134,7 +1149,7 @@ export function inferRuntimeType(
return [UNKNOWN_TYPE] return [UNKNOWN_TYPE]
} }
case 'TSTypeReference': case 'TSTypeReference': {
const resolved = resolveTypeReference(ctx, node, scope) const resolved = resolveTypeReference(ctx, node, scope)
if (resolved) { if (resolved) {
return inferRuntimeType(ctx, resolved, resolved._ownerScope) return inferRuntimeType(ctx, resolved, resolved._ownerScope)
@ -1197,6 +1212,7 @@ export function inferRuntimeType(
} }
// cannot infer, fallback to UNKNOWN: ThisParameterType // cannot infer, fallback to UNKNOWN: ThisParameterType
return [UNKNOWN_TYPE] return [UNKNOWN_TYPE]
}
case 'TSParenthesizedType': case 'TSParenthesizedType':
return inferRuntimeType(ctx, node.typeAnnotation, scope) return inferRuntimeType(ctx, node.typeAnnotation, scope)
@ -1228,6 +1244,19 @@ export function inferRuntimeType(
case 'ClassDeclaration': case 'ClassDeclaration':
return ['Object'] return ['Object']
case 'TSImportType': {
const sourceScope = importSourceToScope(
ctx,
node.argument,
scope,
node.argument.value
)
const resolved = resolveTypeReference(ctx, node, sourceScope)
if (resolved) {
return inferRuntimeType(ctx, resolved, resolved._ownerScope)
}
}
default: default:
return [UNKNOWN_TYPE] // no runtime check return [UNKNOWN_TYPE] // no runtime check
} }