mirror of https://github.com/vuejs/core.git
feat(compiler-sfc): support export * when resolving types
This commit is contained in:
parent
f17a82c769
commit
7c3ca3cc3e
|
@ -454,6 +454,24 @@ describe('resolveType', () => {
|
||||||
expect(deps && [...deps]).toStrictEqual(Object.keys(files))
|
expect(deps && [...deps]).toStrictEqual(Object.keys(files))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('relative (chained, export *)', () => {
|
||||||
|
const files = {
|
||||||
|
'/foo.ts': `export * from './bar'`,
|
||||||
|
'/bar.ts': 'export type P = { bar: string }'
|
||||||
|
}
|
||||||
|
const { props, deps } = resolve(
|
||||||
|
`
|
||||||
|
import { P } from './foo'
|
||||||
|
defineProps<P>()
|
||||||
|
`,
|
||||||
|
files
|
||||||
|
)
|
||||||
|
expect(props).toStrictEqual({
|
||||||
|
bar: ['String']
|
||||||
|
})
|
||||||
|
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({
|
||||||
|
@ -563,10 +581,10 @@ describe('resolveType', () => {
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('failed improt source resolve', () => {
|
test('failed import source resolve', () => {
|
||||||
expect(() =>
|
expect(() =>
|
||||||
resolve(`import { X } from './foo'; defineProps<X>()`)
|
resolve(`import { X } from './foo'; defineProps<X>()`)
|
||||||
).toThrow(`Failed to resolve import source "./foo" for type X`)
|
).toThrow(`Failed to resolve import source "./foo"`)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('should not error on unresolved type when inferring runtime type', () => {
|
test('should not error on unresolved type when inferring runtime type', () => {
|
||||||
|
|
|
@ -542,7 +542,7 @@ function innerResolveTypeReference(
|
||||||
ns = ns._ns
|
ns = ns._ns
|
||||||
}
|
}
|
||||||
if (ns) {
|
if (ns) {
|
||||||
const childScope = moduleDeclToScope(ns, ns._ownerScope || scope)
|
const childScope = moduleDeclToScope(ctx, ns, ns._ownerScope || scope)
|
||||||
return innerResolveTypeReference(
|
return innerResolveTypeReference(
|
||||||
ctx,
|
ctx,
|
||||||
childScope,
|
childScope,
|
||||||
|
@ -581,7 +581,7 @@ function resolveGlobalScope(ctx: TypeResolveContext): TypeScope[] | undefined {
|
||||||
throw new Error('[vue/compiler-sfc] globalTypeFiles requires fs access.')
|
throw new Error('[vue/compiler-sfc] globalTypeFiles requires fs access.')
|
||||||
}
|
}
|
||||||
return ctx.options.globalTypeFiles.map(file =>
|
return ctx.options.globalTypeFiles.map(file =>
|
||||||
fileToScope(normalizePath(file), fs, ctx.options.babelParserPlugins, true)
|
fileToScope(ctx, normalizePath(file), true)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -603,23 +603,36 @@ function resolveTypeFromImport(
|
||||||
name: string,
|
name: string,
|
||||||
scope: TypeScope
|
scope: TypeScope
|
||||||
): ScopeTypeNode | undefined {
|
): ScopeTypeNode | undefined {
|
||||||
|
const { source, imported } = scope.imports[name]
|
||||||
|
const resolved = resolveImportSource(ctx, node, scope, source)
|
||||||
|
return resolveTypeReference(
|
||||||
|
ctx,
|
||||||
|
node,
|
||||||
|
fileToScope(ctx, resolved),
|
||||||
|
imported,
|
||||||
|
true
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveImportSource(
|
||||||
|
ctx: TypeResolveContext,
|
||||||
|
node: Node,
|
||||||
|
scope: TypeScope,
|
||||||
|
source: string
|
||||||
|
): string {
|
||||||
const fs: FS = ctx.options.fs || ts?.sys
|
const fs: FS = ctx.options.fs || ts?.sys
|
||||||
if (!fs) {
|
if (!fs) {
|
||||||
ctx.error(
|
ctx.error(
|
||||||
`No fs option provided to \`compileScript\` in non-Node environment. ` +
|
`No fs option provided to \`compileScript\` in non-Node environment. ` +
|
||||||
`File system access is required for resolving imported types.`,
|
`File system access is required for resolving imported types.`,
|
||||||
node
|
node,
|
||||||
|
scope
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
let resolved
|
||||||
const containingFile = scope.filename
|
|
||||||
const { source, imported } = scope.imports[name]
|
|
||||||
|
|
||||||
let resolved: string | undefined
|
|
||||||
|
|
||||||
if (source.startsWith('.')) {
|
if (source.startsWith('.')) {
|
||||||
// relative import - fast path
|
// relative import - fast path
|
||||||
const filename = path.join(containingFile, '..', source)
|
const filename = path.join(scope.filename, '..', source)
|
||||||
resolved = resolveExt(filename, fs)
|
resolved = resolveExt(filename, fs)
|
||||||
} else {
|
} else {
|
||||||
// module or aliased import - use full TS resolution, only supported in Node
|
// module or aliased import - use full TS resolution, only supported in Node
|
||||||
|
@ -632,36 +645,22 @@ function resolveTypeFromImport(
|
||||||
}
|
}
|
||||||
if (!ts) {
|
if (!ts) {
|
||||||
ctx.error(
|
ctx.error(
|
||||||
`Failed to resolve type ${imported} from module ${JSON.stringify(
|
`Failed to resolve import source ${JSON.stringify(source)}. ` +
|
||||||
source
|
|
||||||
)}. ` +
|
|
||||||
`typescript is required as a peer dep for vue in order ` +
|
`typescript is required as a peer dep for vue in order ` +
|
||||||
`to support resolving types from module imports.`,
|
`to support resolving types from module imports.`,
|
||||||
node,
|
node,
|
||||||
scope
|
scope
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
resolved = resolveWithTS(containingFile, source, fs)
|
resolved = resolveWithTS(scope.filename, source, fs)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (resolved) {
|
if (resolved) {
|
||||||
resolved = normalizePath(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 resolveTypeReference(
|
|
||||||
ctx,
|
|
||||||
node,
|
|
||||||
fileToScope(resolved, fs, ctx.options.babelParserPlugins),
|
|
||||||
imported,
|
|
||||||
true
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
ctx.error(
|
return ctx.error(
|
||||||
`Failed to resolve import source ${JSON.stringify(
|
`Failed to resolve import source ${JSON.stringify(source)}.`,
|
||||||
source
|
|
||||||
)} for type ${name}`,
|
|
||||||
node,
|
node,
|
||||||
scope
|
scope
|
||||||
)
|
)
|
||||||
|
@ -753,18 +752,18 @@ export function invalidateTypeCache(filename: string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function fileToScope(
|
export function fileToScope(
|
||||||
|
ctx: TypeResolveContext,
|
||||||
filename: string,
|
filename: string,
|
||||||
fs: FS,
|
|
||||||
parserPlugins: SFCScriptCompileOptions['babelParserPlugins'],
|
|
||||||
asGlobal = false
|
asGlobal = false
|
||||||
): TypeScope {
|
): TypeScope {
|
||||||
const cached = fileToScopeCache.get(filename)
|
const cached = fileToScopeCache.get(filename)
|
||||||
if (cached) {
|
if (cached) {
|
||||||
return cached
|
return cached
|
||||||
}
|
}
|
||||||
|
// fs should be guaranteed to exist here
|
||||||
|
const fs = ctx.options.fs || ts?.sys
|
||||||
const source = fs.readFile(filename) || ''
|
const source = fs.readFile(filename) || ''
|
||||||
const body = parseFile(filename, source, parserPlugins)
|
const body = parseFile(filename, source, ctx.options.babelParserPlugins)
|
||||||
const scope: TypeScope = {
|
const scope: TypeScope = {
|
||||||
filename,
|
filename,
|
||||||
source,
|
source,
|
||||||
|
@ -773,7 +772,7 @@ export function fileToScope(
|
||||||
types: Object.create(null),
|
types: Object.create(null),
|
||||||
exportedTypes: Object.create(null)
|
exportedTypes: Object.create(null)
|
||||||
}
|
}
|
||||||
recordTypes(body, scope, asGlobal)
|
recordTypes(ctx, body, scope, asGlobal)
|
||||||
fileToScopeCache.set(filename, scope)
|
fileToScopeCache.set(filename, scope)
|
||||||
return scope
|
return scope
|
||||||
}
|
}
|
||||||
|
@ -846,12 +845,13 @@ function ctxToScope(ctx: TypeResolveContext): TypeScope {
|
||||||
exportedTypes: Object.create(null)
|
exportedTypes: Object.create(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
recordTypes(body, scope)
|
recordTypes(ctx, body, scope)
|
||||||
|
|
||||||
return (ctx.scope = scope)
|
return (ctx.scope = scope)
|
||||||
}
|
}
|
||||||
|
|
||||||
function moduleDeclToScope(
|
function moduleDeclToScope(
|
||||||
|
ctx: TypeResolveContext,
|
||||||
node: TSModuleDeclaration & { _resolvedChildScope?: TypeScope },
|
node: TSModuleDeclaration & { _resolvedChildScope?: TypeScope },
|
||||||
parentScope: TypeScope
|
parentScope: TypeScope
|
||||||
): TypeScope {
|
): TypeScope {
|
||||||
|
@ -872,7 +872,7 @@ function moduleDeclToScope(
|
||||||
const id = getId(decl.id)
|
const id = getId(decl.id)
|
||||||
scope.types[id] = scope.exportedTypes[id] = decl
|
scope.types[id] = scope.exportedTypes[id] = decl
|
||||||
} else {
|
} else {
|
||||||
recordTypes(node.body.body, scope)
|
recordTypes(ctx, node.body.body, scope)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (node._resolvedChildScope = scope)
|
return (node._resolvedChildScope = scope)
|
||||||
|
@ -880,7 +880,12 @@ function moduleDeclToScope(
|
||||||
|
|
||||||
const importExportRE = /^Import|^Export/
|
const importExportRE = /^Import|^Export/
|
||||||
|
|
||||||
function recordTypes(body: Statement[], scope: TypeScope, asGlobal = false) {
|
function recordTypes(
|
||||||
|
ctx: TypeResolveContext,
|
||||||
|
body: Statement[],
|
||||||
|
scope: TypeScope,
|
||||||
|
asGlobal = false
|
||||||
|
) {
|
||||||
const { types, exportedTypes, imports } = scope
|
const { types, exportedTypes, imports } = scope
|
||||||
const isAmbient = asGlobal
|
const isAmbient = asGlobal
|
||||||
? !body.some(s => importExportRE.test(s.type))
|
? !body.some(s => importExportRE.test(s.type))
|
||||||
|
@ -932,6 +937,15 @@ function recordTypes(body: Statement[], scope: TypeScope, asGlobal = false) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if (stmt.type === 'ExportAllDeclaration') {
|
||||||
|
const targetFile = resolveImportSource(
|
||||||
|
ctx,
|
||||||
|
stmt.source,
|
||||||
|
scope,
|
||||||
|
stmt.source.value
|
||||||
|
)
|
||||||
|
const targetScope = fileToScope(ctx, targetFile)
|
||||||
|
Object.assign(scope.exportedTypes, targetScope.exportedTypes)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue