// required
'source' | 'filename' | 'error' | 'options'
> &
- Partial<Pick<ScriptCompileContext, 'scope' | 'deps'>> & {
+ Partial<Pick<ScriptCompileContext, 'scope' | 'globalScopes' | 'deps'>> & {
ast: Statement[]
}
type Import = Pick<ImportBinding, 'source' | 'imported'>
+type ScopeTypeNode = Node & {
+ // scope types always has ownerScope attached
+ _ownerScope: TypeScope
+}
+
export interface TypeScope {
filename: string
source: string
offset: number
imports: Record<string, Import>
- types: Record<
- string,
- Node & {
- // scope types always has ownerScope attached
- _ownerScope: TypeScope
- }
- >
- exportedTypes: Record<
- string,
- Node & {
- // scope types always has ownerScope attached
- _ownerScope: TypeScope
- }
- >
+ types: Record<string, ScopeTypeNode>
+ exportedTypes: Record<string, ScopeTypeNode>
}
export interface WithScope {
function resolveTypeReference(
ctx: TypeResolveContext,
node: (TSTypeReference | TSExpressionWithTypeArguments) & {
- _resolvedReference?: Node
+ _resolvedReference?: ScopeTypeNode
},
scope?: TypeScope,
name?: string,
onlyExported = false
-): (Node & WithScope) | undefined {
+): ScopeTypeNode | undefined {
if (node._resolvedReference) {
return node._resolvedReference
}
name: string | string[],
node: TSTypeReference | TSExpressionWithTypeArguments,
onlyExported: boolean
-): Node | undefined {
+): ScopeTypeNode | undefined {
if (typeof name === 'string') {
if (scope.imports[name]) {
return resolveTypeFromImport(ctx, node, name, scope)
} else {
const types = onlyExported ? scope.exportedTypes : scope.types
- return types[name]
+ if (types[name]) {
+ return types[name]
+ } else {
+ // fallback to global
+ const globalScopes = resolveGlobalScope(ctx)
+ if (globalScopes) {
+ for (const s of globalScopes) {
+ if (s.types[name]) {
+ ;(ctx.deps || (ctx.deps = new Set())).add(s.filename)
+ return s.types[name]
+ }
+ }
+ }
+ }
}
} else {
const ns = innerResolveTypeReference(
childScope,
name.length > 2 ? name.slice(1) : name[name.length - 1],
node,
- true
+ !ns.declare
)
}
}
}
}
+function resolveGlobalScope(ctx: TypeResolveContext): TypeScope[] | undefined {
+ if (ctx.options.globalTypeFiles) {
+ const fs: FS = ctx.options.fs || ts?.sys
+ if (!fs) {
+ throw new Error('[vue/compiler-sfc] globalTypeFiles requires fs access.')
+ }
+ return ctx.options.globalTypeFiles.map(file =>
+ // TODO: differentiate ambient vs non-ambient module
+ fileToScope(file, fs, ctx.options.babelParserPlugins, true)
+ )
+ }
+}
+
let ts: typeof TS
/**
node: TSTypeReference | TSExpressionWithTypeArguments,
name: string,
scope: TypeScope
-): Node | undefined {
+): ScopeTypeNode | undefined {
const fs: FS = ctx.options.fs || ts?.sys
if (!fs) {
ctx.error(
return resolveTypeReference(
ctx,
node,
- fileToScope(ctx, resolved, fs),
+ fileToScope(resolved, fs, ctx.options.babelParserPlugins),
imported,
true
)
tsConfigCache.delete(filename)
}
-function fileToScope(
- ctx: TypeResolveContext,
+export function fileToScope(
filename: string,
- fs: FS
+ fs: FS,
+ parserPlugins: SFCScriptCompileOptions['babelParserPlugins'],
+ asGlobal = false
): TypeScope {
const cached = fileToScopeCache.get(filename)
if (cached) {
}
const source = fs.readFile(filename) || ''
- const body = parseFile(ctx, filename, source)
+ const body = parseFile(filename, source, parserPlugins)
const scope: TypeScope = {
filename,
source,
offset: 0,
+ imports: recordImports(body),
types: Object.create(null),
- exportedTypes: Object.create(null),
- imports: recordImports(body)
+ exportedTypes: Object.create(null)
}
- recordTypes(body, scope)
+ recordTypes(body, scope, asGlobal)
fileToScopeCache.set(filename, scope)
return scope
}
function parseFile(
- ctx: TypeResolveContext,
filename: string,
- content: string
+ content: string,
+ parserPlugins?: SFCScriptCompileOptions['babelParserPlugins']
): Statement[] {
const ext = extname(filename)
if (ext === '.ts' || ext === '.tsx') {
return babelParse(content, {
- plugins: resolveParserPlugins(
- ext.slice(1),
- ctx.options.babelParserPlugins
- ),
+ plugins: resolveParserPlugins(ext.slice(1), parserPlugins),
sourceType: 'module'
}).program.body
} else if (ext === '.vue') {
}
const lang = script?.lang || scriptSetup?.lang
return babelParse(scriptContent, {
- plugins: resolveParserPlugins(lang!, ctx.options.babelParserPlugins),
+ plugins: resolveParserPlugins(lang!, parserPlugins),
sourceType: 'module'
}).program.body
}
function moduleDeclToScope(
node: TSModuleDeclaration & { _resolvedChildScope?: TypeScope },
- parent: TypeScope
+ parentScope: TypeScope
): TypeScope {
if (node._resolvedChildScope) {
return node._resolvedChildScope
}
const scope: TypeScope = {
- ...parent,
- types: Object.create(parent.types),
- imports: Object.create(parent.imports)
+ ...parentScope,
+ types: Object.create(parentScope.types),
+ imports: Object.create(parentScope.imports)
}
recordTypes((node.body as TSModuleBlock).body, scope)
return (node._resolvedChildScope = scope)
}
-function recordTypes(body: Statement[], scope: TypeScope) {
+const importExportRE = /^Import|^Export/
+
+function recordTypes(body: Statement[], scope: TypeScope, asGlobal = false) {
const { types, exportedTypes, imports } = scope
+ const isAmbient = asGlobal
+ ? !body.some(s => importExportRE.test(s.type))
+ : false
for (const stmt of body) {
- recordType(stmt, types)
+ if (asGlobal) {
+ if (isAmbient) {
+ if ((stmt as any).declare) {
+ recordType(stmt, types)
+ }
+ } else if (stmt.type === 'TSModuleDeclaration' && stmt.global) {
+ for (const s of (stmt.body as TSModuleBlock).body) {
+ recordType(s, types)
+ }
+ }
+ } else {
+ recordType(stmt, types)
+ }
}
- for (const stmt of body) {
- if (stmt.type === 'ExportNamedDeclaration') {
- if (stmt.declaration) {
- recordType(stmt.declaration, types)
- recordType(stmt.declaration, exportedTypes)
- } else {
- for (const spec of stmt.specifiers) {
- if (spec.type === 'ExportSpecifier') {
- const local = spec.local.name
- const exported = getId(spec.exported)
- if (stmt.source) {
- // re-export, register an import + export as a type reference
- imports[local] = {
- source: stmt.source.value,
- imported: local
- }
- exportedTypes[exported] = {
- type: 'TSTypeReference',
- typeName: {
- type: 'Identifier',
- name: local
- },
- _ownerScope: scope
+ if (!asGlobal) {
+ for (const stmt of body) {
+ if (stmt.type === 'ExportNamedDeclaration') {
+ if (stmt.declaration) {
+ recordType(stmt.declaration, types)
+ recordType(stmt.declaration, exportedTypes)
+ } else {
+ for (const spec of stmt.specifiers) {
+ if (spec.type === 'ExportSpecifier') {
+ const local = spec.local.name
+ const exported = getId(spec.exported)
+ if (stmt.source) {
+ // re-export, register an import + export as a type reference
+ imports[local] = {
+ source: stmt.source.value,
+ imported: local
+ }
+ exportedTypes[exported] = {
+ type: 'TSTypeReference',
+ typeName: {
+ type: 'Identifier',
+ name: local
+ },
+ _ownerScope: scope
+ }
+ } else if (types[local]) {
+ // exporting local defined type
+ exportedTypes[exported] = types[local]
}
- } else if (types[local]) {
- // exporting local defined type
- exportedTypes[exported] = types[local]
}
}
}