feat(compiler-sfc): support relative imported types in macros

This commit is contained in:
Evan You 2023-04-13 20:49:16 +08:00
parent 1c06fe1d02
commit 8aa4ea81d6
6 changed files with 473 additions and 153 deletions

View File

@ -6,10 +6,7 @@ import type {
Function,
ObjectProperty,
BlockStatement,
Program,
ImportDefaultSpecifier,
ImportNamespaceSpecifier,
ImportSpecifier
Program
} from '@babel/types'
import { walk } from 'estree-walker'
@ -246,17 +243,6 @@ export const isStaticProperty = (node: Node): node is ObjectProperty =>
export const isStaticPropertyKey = (node: Node, parent: Node) =>
isStaticProperty(parent) && parent.key === node
export function getImportedName(
specifier: ImportSpecifier | ImportDefaultSpecifier | ImportNamespaceSpecifier
) {
if (specifier.type === 'ImportSpecifier')
return specifier.imported.type === 'Identifier'
? specifier.imported.name
: specifier.imported.value
else if (specifier.type === 'ImportNamespaceSpecifier') return '*'
return 'default'
}
/**
* Copied from https://github.com/babel/babel/blob/main/packages/babel-types/src/validators/isReferenced.ts
* To avoid runtime dependency on @babel/types (which includes process references)

View File

@ -3,6 +3,7 @@ import { parse } from '../../src'
import { ScriptCompileContext } from '../../src/script/context'
import {
inferRuntimeType,
recordImports,
resolveTypeElements
} from '../../src/script/resolveType'
@ -246,6 +247,85 @@ describe('resolveType', () => {
})
})
describe('external type imports', () => {
test('relative ts', () => {
expect(
resolve(
`
import { P } from './foo'
import { Y as PP } from './bar'
type Target = P & PP
`,
{
'foo.ts': 'export type P = { foo: number }',
'bar.d.ts': 'type X = { bar: string }; export { X as Y }'
}
).props
).toStrictEqual({
foo: ['Number'],
bar: ['String']
})
})
test('relative vue', () => {
expect(
resolve(
`
import { P } from './foo.vue'
import { P as PP } from './bar.vue'
type Target = P & PP
`,
{
'foo.vue':
'<script lang="ts">export type P = { foo: number }</script>',
'bar.vue':
'<script setup lang="tsx">export type P = { bar: string }</script>'
}
).props
).toStrictEqual({
foo: ['Number'],
bar: ['String']
})
})
test('relative (chained)', () => {
expect(
resolve(
`
import { P } from './foo'
type Target = P
`,
{
'foo.ts': `import type { P as PP } from './nested/bar.vue'
export type P = { foo: number } & PP`,
'nested/bar.vue':
'<script setup lang="ts">export type P = { bar: string }</script>'
}
).props
).toStrictEqual({
foo: ['Number'],
bar: ['String']
})
})
test('relative (chained, re-export)', () => {
expect(
resolve(
`
import { PP as P } from './foo'
type Target = P
`,
{
'foo.ts': `export { P as PP } from './bar'`,
'bar.ts': 'export type P = { bar: string }'
}
).props
).toStrictEqual({
bar: ['String']
})
})
})
describe('errors', () => {
test('error on computed keys', () => {
expect(() => resolve(`type Target = { [Foo]: string }`)).toThrow(
@ -255,9 +335,26 @@ describe('resolveType', () => {
})
})
function resolve(code: string) {
const { descriptor } = parse(`<script setup lang="ts">${code}</script>`)
const ctx = new ScriptCompileContext(descriptor, { id: 'test' })
function resolve(code: string, files: Record<string, string> = {}) {
const { descriptor } = parse(`<script setup lang="ts">${code}</script>`, {
filename: 'Test.vue'
})
const ctx = new ScriptCompileContext(descriptor, {
id: 'test',
fs: {
fileExists(file) {
return !!files[file]
},
readFile(file) {
return files[file]
}
}
})
// ctx.userImports is collected when calling compileScript(), but we are
// skipping that here, so need to manually register imports
ctx.userImports = recordImports(ctx.scriptSetupAst!.body) as any
const targetDecl = ctx.scriptSetupAst!.body.find(
s => s.type === 'TSTypeAliasDeclaration' && s.id.name === 'Target'
) as TSTypeAliasDeclaration

View File

@ -2,8 +2,7 @@ import {
BindingTypes,
UNREF,
isFunctionType,
walkIdentifiers,
getImportedName
walkIdentifiers
} from '@vue/compiler-dom'
import { DEFAULT_FILENAME, SFCDescriptor, SFCScriptBlock } from './parse'
import { parse as _parse, ParserPlugin } from '@babel/parser'
@ -45,7 +44,12 @@ import { DEFINE_EXPOSE, processDefineExpose } from './script/defineExpose'
import { DEFINE_OPTIONS, processDefineOptions } from './script/defineOptions'
import { processDefineSlots } from './script/defineSlots'
import { DEFINE_MODEL, processDefineModel } from './script/defineModel'
import { isLiteralNode, unwrapTSNode, isCallOf } from './script/utils'
import {
isLiteralNode,
unwrapTSNode,
isCallOf,
getImportedName
} from './script/utils'
import { analyzeScriptBindings } from './script/analyzeScriptBindings'
import { isImportUsed } from './script/importUsageCheck'
import { processAwait } from './script/topLevelAwait'
@ -106,6 +110,13 @@ export interface SFCScriptCompileOptions {
* (**Experimental**) Enable macro `defineModel`
*/
defineModel?: boolean
/**
*
*/
fs?: {
fileExists(file: string): boolean
readFile(file: string): string
}
}
export interface ImportBinding {

View File

@ -1,13 +1,13 @@
import { Node, ObjectPattern, Program } from '@babel/types'
import { SFCDescriptor } from '../parse'
import { generateCodeFrame } from '@vue/shared'
import { parse as babelParse, ParserOptions, ParserPlugin } from '@babel/parser'
import { parse as babelParse, ParserPlugin } from '@babel/parser'
import { ImportBinding, SFCScriptCompileOptions } from '../compileScript'
import { PropsDestructureBindings } from './defineProps'
import { ModelDecl } from './defineModel'
import { BindingMetadata } from '../../../compiler-core/src'
import MagicString from 'magic-string'
import { TypeScope } from './resolveType'
import { TypeScope, WithScope } from './resolveType'
export class ScriptCompileContext {
isJS: boolean
@ -83,31 +83,17 @@ export class ScriptCompileContext {
scriptSetupLang === 'tsx'
// resolve parser plugins
const plugins: ParserPlugin[] = []
if (!this.isTS || scriptLang === 'tsx' || scriptSetupLang === 'tsx') {
plugins.push('jsx')
} else {
// If don't match the case of adding jsx, should remove the jsx from the babelParserPlugins
if (options.babelParserPlugins)
options.babelParserPlugins = options.babelParserPlugins.filter(
n => n !== 'jsx'
const plugins: ParserPlugin[] = resolveParserPlugins(
(scriptLang || scriptSetupLang)!,
options.babelParserPlugins
)
}
if (options.babelParserPlugins) plugins.push(...options.babelParserPlugins)
if (this.isTS) {
plugins.push('typescript')
if (!plugins.includes('decorators')) {
plugins.push('decorators-legacy')
}
}
function parse(
input: string,
options: ParserOptions,
offset: number
): Program {
function parse(input: string, offset: number): Program {
try {
return babelParse(input, options).program
return babelParse(input, {
plugins,
sourceType: 'module'
}).program
} catch (e: any) {
e.message = `[@vue/compiler-sfc] ${e.message}\n\n${
descriptor.filename
@ -124,23 +110,12 @@ export class ScriptCompileContext {
this.descriptor.script &&
parse(
this.descriptor.script.content,
{
plugins,
sourceType: 'module'
},
this.descriptor.script.loc.start.offset
)
this.scriptSetupAst =
this.descriptor.scriptSetup &&
parse(
this.descriptor.scriptSetup!.content,
{
plugins: [...plugins, 'topLevelAwait'],
sourceType: 'module'
},
this.startOffset!
)
parse(this.descriptor.scriptSetup!.content, this.startOffset!)
}
getString(node: Node, scriptSetup = true): string {
@ -150,19 +125,39 @@ export class ScriptCompileContext {
return block.content.slice(node.start!, node.end!)
}
error(
msg: string,
node: Node,
end: number = node.end! + this.startOffset!
): never {
error(msg: string, node: Node & WithScope, scope?: TypeScope): never {
throw new Error(
`[@vue/compiler-sfc] ${msg}\n\n${
this.descriptor.filename
}\n${generateCodeFrame(
this.descriptor.source,
node.start! + this.startOffset!,
end
node.end! + this.startOffset!
)}`
)
}
}
export function resolveParserPlugins(
lang: string,
userPlugins?: ParserPlugin[]
) {
const plugins: ParserPlugin[] = []
if (lang === 'jsx' || lang === 'tsx') {
plugins.push('jsx')
} else if (userPlugins) {
// If don't match the case of adding jsx
// should remove the jsx from user options
userPlugins = userPlugins.filter(p => p !== 'jsx')
}
if (lang === 'ts' || lang === 'tsx') {
plugins.push('typescript')
if (!plugins.includes('decorators')) {
plugins.push('decorators-legacy')
}
}
if (userPlugins) {
plugins.push(...userPlugins)
}
return plugins
}

View File

@ -1,11 +1,13 @@
import {
Expression,
Identifier,
Node as _Node,
Node,
Statement,
TSCallSignatureDeclaration,
TSEnumDeclaration,
TSExpressionWithTypeArguments,
TSFunctionType,
TSInterfaceDeclaration,
TSMappedType,
TSMethodSignature,
TSModuleBlock,
@ -18,81 +20,108 @@ import {
TSTypeReference,
TemplateLiteral
} from '@babel/types'
import { UNKNOWN_TYPE } from './utils'
import { ScriptCompileContext } from './context'
import { ImportBinding } from '../compileScript'
import { TSInterfaceDeclaration } from '@babel/types'
import { UNKNOWN_TYPE, getId, getImportedName } from './utils'
import { ScriptCompileContext, resolveParserPlugins } from './context'
import { ImportBinding, SFCScriptCompileOptions } from '../compileScript'
import { capitalize, hasOwn } from '@vue/shared'
import { Expression } from '@babel/types'
import path from 'path'
import { parse as babelParse } from '@babel/parser'
import { parse } from '../parse'
type Import = Pick<ImportBinding, 'source' | 'imported'>
export interface TypeScope {
filename: string
imports: Record<string, ImportBinding>
types: Record<string, Node>
parent?: TypeScope
source: string
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
}
>
}
interface WithScope {
export interface WithScope {
_ownerScope?: TypeScope
}
interface ResolvedElements {
props: Record<string, (TSPropertySignature | TSMethodSignature) & WithScope>
props: Record<
string,
(TSPropertySignature | TSMethodSignature) & {
// resolved props always has ownerScope attached
_ownerScope: TypeScope
}
>
calls?: (TSCallSignatureDeclaration | TSFunctionType)[]
}
type Node = _Node &
WithScope & {
_resolvedElements?: ResolvedElements
}
/**
* Resolve arbitrary type node to a list of type elements that can be then
* mapped to runtime props or emits.
*/
export function resolveTypeElements(
ctx: ScriptCompileContext,
node: Node
node: Node & WithScope & { _resolvedElements?: ResolvedElements },
scope?: TypeScope
): ResolvedElements {
if (node._resolvedElements) {
return node._resolvedElements
}
return (node._resolvedElements = innerResolveTypeElements(ctx, node))
return (node._resolvedElements = innerResolveTypeElements(
ctx,
node,
node._ownerScope || scope || ctxToScope(ctx)
))
}
function innerResolveTypeElements(
ctx: ScriptCompileContext,
node: Node
node: Node,
scope: TypeScope
): ResolvedElements {
switch (node.type) {
case 'TSTypeLiteral':
return typeElementsToMap(ctx, node.members, node._ownerScope)
return typeElementsToMap(ctx, node.members, scope)
case 'TSInterfaceDeclaration':
return resolveInterfaceMembers(ctx, node)
return resolveInterfaceMembers(ctx, node, scope)
case 'TSTypeAliasDeclaration':
case 'TSParenthesizedType':
return resolveTypeElements(ctx, node.typeAnnotation)
return resolveTypeElements(ctx, node.typeAnnotation, scope)
case 'TSFunctionType': {
return { props: {}, calls: [node] }
}
case 'TSUnionType':
case 'TSIntersectionType':
return mergeElements(
node.types.map(t => resolveTypeElements(ctx, t)),
node.types.map(t => resolveTypeElements(ctx, t, scope)),
node.type
)
case 'TSMappedType':
return resolveMappedType(ctx, node)
return resolveMappedType(ctx, node, scope)
case 'TSIndexedAccessType': {
if (
node.indexType.type === 'TSLiteralType' &&
node.indexType.literal.type === 'StringLiteral'
) {
const resolved = resolveTypeElements(ctx, node.objectType)
const resolved = resolveTypeElements(ctx, node.objectType, scope)
const key = node.indexType.literal.value
const targetType = resolved.props[key].typeAnnotation
if (targetType) {
return resolveTypeElements(ctx, targetType.typeAnnotation)
return resolveTypeElements(
ctx,
targetType.typeAnnotation,
resolved.props[key]._ownerScope
)
} else {
break
}
@ -105,9 +134,9 @@ function innerResolveTypeElements(
}
case 'TSExpressionWithTypeArguments': // referenced by interface extends
case 'TSTypeReference': {
const resolved = resolveTypeReference(ctx, node)
const resolved = resolveTypeReference(ctx, node, scope)
if (resolved) {
return resolveTypeElements(ctx, resolved)
return resolveTypeElements(ctx, resolved, resolved._ownerScope)
} else {
const typeName = getReferenceName(node)
if (
@ -118,7 +147,7 @@ function innerResolveTypeElements(
return resolveBuiltin(ctx, node, typeName as any)
}
ctx.error(
`Failed to resolved type reference, or unsupported built-in utlility type.`,
`Failed to resolve type reference, or unsupported built-in utlility type.`,
node
)
}
@ -135,18 +164,13 @@ function typeElementsToMap(
const res: ResolvedElements = { props: {} }
for (const e of elements) {
if (e.type === 'TSPropertySignature' || e.type === 'TSMethodSignature') {
;(e as Node)._ownerScope = scope
const name =
e.key.type === 'Identifier'
? e.key.name
: e.key.type === 'StringLiteral'
? e.key.value
: null
;(e as WithScope)._ownerScope = scope
const name = getId(e.key)
if (name && !e.computed) {
res.props[name] = e
res.props[name] = e as ResolvedElements['props'][string]
} else if (e.key.type === 'TemplateLiteral') {
for (const key of resolveTemplateKeys(ctx, e.key)) {
res.props[key] = e
res.props[key] = e as ResolvedElements['props'][string]
}
} else {
ctx.error(
@ -172,11 +196,15 @@ function mergeElements(
if (!hasOwn(baseProps, key)) {
baseProps[key] = props[key]
} else {
baseProps[key] = createProperty(baseProps[key].key, {
baseProps[key] = createProperty(
baseProps[key].key,
{
type,
// @ts-ignore
types: [baseProps[key], props[key]]
})
},
baseProps[key]._ownerScope
)
}
}
if (calls) {
@ -188,8 +216,9 @@ function mergeElements(
function createProperty(
key: Expression,
typeAnnotation: TSType
): TSPropertySignature {
typeAnnotation: TSType,
scope: TypeScope
): TSPropertySignature & { _ownerScope: TypeScope } {
return {
type: 'TSPropertySignature',
key,
@ -197,18 +226,20 @@ function createProperty(
typeAnnotation: {
type: 'TSTypeAnnotation',
typeAnnotation
}
},
_ownerScope: scope
}
}
function resolveInterfaceMembers(
ctx: ScriptCompileContext,
node: TSInterfaceDeclaration & WithScope
node: TSInterfaceDeclaration & WithScope,
scope: TypeScope
): ResolvedElements {
const base = typeElementsToMap(ctx, node.body.body, node._ownerScope)
if (node.extends) {
for (const ext of node.extends) {
const { props } = resolveTypeElements(ctx, ext)
const { props } = resolveTypeElements(ctx, ext, scope)
for (const key in props) {
if (!hasOwn(base.props, key)) {
base.props[key] = props[key]
@ -221,7 +252,8 @@ function resolveInterfaceMembers(
function resolveMappedType(
ctx: ScriptCompileContext,
node: TSMappedType
node: TSMappedType,
scope: TypeScope
): ResolvedElements {
const res: ResolvedElements = { props: {} }
if (!node.typeParameter.constraint) {
@ -234,7 +266,8 @@ function resolveMappedType(
type: 'Identifier',
name: key
},
node.typeAnnotation!
node.typeAnnotation!,
scope
)
}
return res
@ -357,32 +390,52 @@ function resolveTypeReference(
node: (TSTypeReference | TSExpressionWithTypeArguments) & {
_resolvedReference?: Node
},
scope = ctxToScope(ctx)
): Node | undefined {
scope?: TypeScope,
name?: string,
onlyExported = false
): (Node & WithScope) | undefined {
if (node._resolvedReference) {
return node._resolvedReference
}
const name = getReferenceName(node)
return (node._resolvedReference = innerResolveTypeReference(scope, name))
return (node._resolvedReference = innerResolveTypeReference(
ctx,
scope || ctxToScope(ctx),
name || getReferenceName(node),
node,
onlyExported
))
}
function innerResolveTypeReference(
ctx: ScriptCompileContext,
scope: TypeScope,
name: string | string[]
name: string | string[],
node: TSTypeReference | TSExpressionWithTypeArguments,
onlyExported: boolean
): Node | undefined {
if (typeof name === 'string') {
if (scope.imports[name]) {
// TODO external import
} else if (scope.types[name]) {
return scope.types[name]
return resolveTypeFromImport(ctx, scope, scope.imports[name], node)
} else {
const types = onlyExported ? scope.exportedTypes : scope.types
return types[name]
}
} else {
const ns = innerResolveTypeReference(scope, name[0])
const ns = innerResolveTypeReference(
ctx,
scope,
name[0],
node,
onlyExported
)
if (ns && ns.type === 'TSModuleDeclaration') {
const childScope = moduleDeclToScope(ns, scope)
return innerResolveTypeReference(
ctx,
childScope,
name.length > 2 ? name.slice(1) : name[name.length - 1]
name.length > 2 ? name.slice(1) : name[name.length - 1],
node,
true
)
}
}
@ -407,20 +460,125 @@ function qualifiedNameToPath(node: Identifier | TSQualifiedName): string[] {
}
}
function resolveTypeFromImport(
ctx: ScriptCompileContext,
scope: TypeScope,
{ source, imported }: Import,
node: TSTypeReference | TSExpressionWithTypeArguments
): Node | undefined {
const fs = ctx.options.fs
if (!fs) {
ctx.error(
`fs options for compileScript are required for resolving imported types`,
node
)
}
// TODO (hmr) register dependency file on ctx
const containingFile = scope.filename
if (source.startsWith('.')) {
// relative import - fast path
const filename = path.join(containingFile, '..', source)
const resolved = resolveExt(filename, fs)
if (resolved) {
return resolveTypeReference(
ctx,
node,
fileToScope(ctx, resolved, fs),
imported,
true
)
} else {
ctx.error(`Failed to resolve import source for type`, node)
}
} else {
// TODO module or aliased import - use full TS resolution
return
}
}
function resolveExt(
filename: string,
fs: NonNullable<SFCScriptCompileOptions['fs']>
) {
const tryResolve = (filename: string) => {
if (fs.fileExists(filename)) return filename
}
return (
tryResolve(filename) ||
tryResolve(filename + `.ts`) ||
tryResolve(filename + `.d.ts`) ||
tryResolve(filename + `/index.ts`) ||
tryResolve(filename + `/index.d.ts`)
)
}
function fileToScope(
ctx: ScriptCompileContext,
filename: string,
fs: NonNullable<SFCScriptCompileOptions['fs']>
): TypeScope {
// TODO cache
const source = fs.readFile(filename)
const body = parseFile(ctx, filename, source)
const scope: TypeScope = {
filename,
source,
types: Object.create(null),
exportedTypes: Object.create(null),
imports: recordImports(body)
}
recordTypes(body, scope)
return scope
}
function parseFile(
ctx: ScriptCompileContext,
filename: string,
content: string
): Statement[] {
const ext = path.extname(filename)
if (ext === '.ts' || ext === '.tsx') {
return babelParse(content, {
plugins: resolveParserPlugins(
ext.slice(1),
ctx.options.babelParserPlugins
),
sourceType: 'module'
}).program.body
} else if (ext === '.vue') {
const {
descriptor: { script, scriptSetup }
} = parse(content)
const scriptContent = (script?.content || '') + (scriptSetup?.content || '')
const lang = script?.lang || scriptSetup?.lang
return babelParse(scriptContent, {
plugins: resolveParserPlugins(lang!, ctx.options.babelParserPlugins),
sourceType: 'module'
}).program.body
}
return []
}
function ctxToScope(ctx: ScriptCompileContext): TypeScope {
if (ctx.scope) {
return ctx.scope
}
const scope: TypeScope = {
filename: ctx.descriptor.filename,
source: ctx.descriptor.source,
imports: Object.create(ctx.userImports),
types: Object.create(null),
exportedTypes: Object.create(null)
}
const body = ctx.scriptAst
? [...ctx.scriptAst.body, ...ctx.scriptSetupAst!.body]
: ctx.scriptSetupAst!.body
return (ctx.scope = {
filename: ctx.descriptor.filename,
imports: ctx.userImports,
types: recordTypes(body)
})
recordTypes(body, scope)
return (ctx.scope = scope)
}
function moduleDeclToScope(
@ -430,27 +588,56 @@ function moduleDeclToScope(
if (node._resolvedChildScope) {
return node._resolvedChildScope
}
const types: TypeScope['types'] = Object.create(parent.types)
const scope: TypeScope = {
filename: parent.filename,
imports: Object.create(parent.imports),
types: recordTypes((node.body as TSModuleBlock).body, types),
parent
...parent,
types: Object.create(parent.types),
imports: Object.create(parent.imports)
}
recordTypes((node.body as TSModuleBlock).body, scope)
return (node._resolvedChildScope = scope)
}
function recordTypes(body: Statement[], scope: TypeScope) {
const { types, exportedTypes, imports } = scope
for (const stmt of body) {
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
}
} else if (types[local]) {
// exporting local defined type
exportedTypes[exported] = types[local]
}
}
}
}
}
}
for (const key of Object.keys(types)) {
types[key]._ownerScope = scope
}
return (node._resolvedChildScope = scope)
}
function recordTypes(
body: Statement[],
types: Record<string, Node> = Object.create(null)
) {
for (const s of body) {
recordType(s, types)
}
return types
}
function recordType(node: Node, types: Record<string, Node>) {
@ -465,12 +652,6 @@ function recordType(node: Node, types: Record<string, Node>) {
case 'TSTypeAliasDeclaration':
types[node.id.name] = node.typeAnnotation
break
case 'ExportNamedDeclaration': {
if (node.declaration) {
recordType(node.declaration, types)
}
break
}
case 'VariableDeclaration': {
if (node.declare) {
for (const decl of node.declarations) {
@ -486,9 +667,29 @@ function recordType(node: Node, types: Record<string, Node>) {
}
}
export function recordImports(body: Statement[]) {
const imports: TypeScope['imports'] = Object.create(null)
for (const s of body) {
recordImport(s, imports)
}
return imports
}
function recordImport(node: Node, imports: TypeScope['imports']) {
if (node.type !== 'ImportDeclaration') {
return
}
for (const s of node.specifiers) {
imports[s.local.name] = {
imported: getImportedName(s),
source: node.source.value
}
}
}
export function inferRuntimeType(
ctx: ScriptCompileContext,
node: Node,
node: Node & WithScope,
scope = node._ownerScope || ctxToScope(ctx)
): string[] {
switch (node.type) {

View File

@ -1,4 +1,13 @@
import { CallExpression, Node } from '@babel/types'
import {
CallExpression,
Expression,
Identifier,
ImportDefaultSpecifier,
ImportNamespaceSpecifier,
ImportSpecifier,
Node,
StringLiteral
} from '@babel/types'
import { TS_NODE_TYPES } from '@vue/compiler-dom'
export const UNKNOWN_TYPE = 'Unknown'
@ -48,3 +57,24 @@ export function isCallOf(
export function toRuntimeTypeString(types: string[]) {
return types.length > 1 ? `[${types.join(', ')}]` : types[0]
}
export function getImportedName(
specifier: ImportSpecifier | ImportDefaultSpecifier | ImportNamespaceSpecifier
) {
if (specifier.type === 'ImportSpecifier')
return specifier.imported.type === 'Identifier'
? specifier.imported.name
: specifier.imported.value
else if (specifier.type === 'ImportNamespaceSpecifier') return '*'
return 'default'
}
export function getId(node: Identifier | StringLiteral): string
export function getId(node: Expression): string | null
export function getId(node: Expression) {
return node.type === 'Identifier'
? node.name
: node.type === 'StringLiteral'
? node.value
: null
}