diff --git a/.changeset/four-bees-exist.md b/.changeset/four-bees-exist.md new file mode 100644 index 000000000..3a78e7b3a --- /dev/null +++ b/.changeset/four-bees-exist.md @@ -0,0 +1,5 @@ +--- +'@vue-macros/api': minor +--- + +support TS namespace diff --git a/packages/api/src/ts.ts b/packages/api/src/ts.ts index 97d27c556..12b5d24f8 100644 --- a/packages/api/src/ts.ts +++ b/packages/api/src/ts.ts @@ -6,17 +6,20 @@ import { isDeclaration } from '@babel/types' import type { Identifier, + ImportNamespaceSpecifier, ImportSpecifier, Node, Statement, TSCallSignatureDeclaration, TSConstructSignatureDeclaration, TSDeclareFunction, + TSEntityName, TSEnumDeclaration, TSInterfaceBody, TSInterfaceDeclaration, TSIntersectionType, TSMethodSignature, + TSModuleBlock, TSModuleDeclaration, TSParenthesizedType, TSPropertySignature, @@ -40,34 +43,18 @@ export interface TSFile { ast: Statement[] } +export type TSScope = TSFile | TSResolvedType + export interface TSProperties { - callSignatures: Array<{ - type: TSCallSignatureDeclaration - file: TSFile - }> - constructSignatures: Array<{ - type: TSConstructSignatureDeclaration - file: TSFile - }> - methods: Record< - string | number, - Array<{ - type: TSMethodSignature - file: TSFile - }> - > + callSignatures: Array> + constructSignatures: Array> + methods: Record>> properties: Record< string | number, { - value: { - type: TSType - file: TSFile - } | null + value: TSResolvedType | null optional: boolean - signature: { - type: TSPropertySignature - file: TSFile - } + signature: TSResolvedType } > } @@ -109,23 +96,23 @@ export function mergeTSProperties( */ export async function resolveTSProperties({ type, - file, + scope, }: TSResolvedType< TSInterfaceDeclaration | TSInterfaceBody | TSTypeLiteral | TSIntersectionType >): Promise { if (type.type === 'TSInterfaceBody') { - return resolveTypeElements(file, type.body) + return resolveTypeElements(scope, type.body) } else if (type.type === 'TSTypeLiteral') { - return resolveTypeElements(file, type.members) + return resolveTypeElements(scope, type.members) } else if (type.type === 'TSInterfaceDeclaration') { - let properties = resolveTypeElements(file, type.body.body) + let properties = resolveTypeElements(scope, type.body.body) if (type.extends) { const resolvedExtends = ( await Promise.all( type.extends.map((node) => node.expression.type === 'Identifier' ? resolveTSReferencedType({ - file, + scope, type: node.expression, }) : undefined @@ -154,7 +141,7 @@ export async function resolveTSProperties({ } for (const subType of type.types) { const resolved = await resolveTSReferencedType({ - file, + scope, type: subType, }) if (!filterValidExtends(resolved)) continue @@ -169,15 +156,18 @@ export async function resolveTSProperties({ } function filterValidExtends( - node: TSResolvedType | undefined + node: TSResolvedType | TSExports | undefined ): node is TSResolvedType< TSInterfaceDeclaration | TSTypeLiteral | TSIntersectionType > { - return [ - 'TSInterfaceDeclaration', - 'TSTypeLiteral', - 'TSIntersectionType', - ].includes(node?.type.type as any) + return ( + !isTSExports(node) && + [ + 'TSInterfaceDeclaration', + 'TSTypeLiteral', + 'TSIntersectionType', + ].includes(node?.type.type as any) + ) } } @@ -185,7 +175,7 @@ export async function resolveTSProperties({ * @limitation don't support index signature */ export function resolveTypeElements( - file: TSFile, + scope: TSScope, elements: Array ) { const properties: TSProperties = { @@ -204,10 +194,10 @@ export function resolveTypeElements( for (const element of elements) { switch (element.type) { case 'TSCallSignatureDeclaration': - properties.callSignatures.push({ file, type: element }) + properties.callSignatures.push({ scope, type: element }) break case 'TSConstructSignatureDeclaration': - properties.constructSignatures.push({ file, type: element }) + properties.constructSignatures.push({ scope, type: element }) break case 'TSMethodSignature': { const key = tryGetKey(element) @@ -218,7 +208,7 @@ export function resolveTypeElements( if (!properties.methods[key]) properties.methods[key] = [] if (element.typeAnnotation) { - properties.methods[key].push({ file, type: element }) + properties.methods[key].push({ scope, type: element }) } break } @@ -230,9 +220,9 @@ export function resolveTypeElements( // cannot be override const type = element.typeAnnotation?.typeAnnotation properties.properties[key] = { - value: type ? { type, file } : null, + value: type ? { type, scope } : null, optional: !!element.optional, - signature: { type: element, file }, + signature: { type: element, scope }, } } break @@ -251,7 +241,7 @@ export interface TSResolvedType< | Exclude | Exclude > { - file: TSFile + scope: TSScope type: T } @@ -265,9 +255,9 @@ export interface TSResolvedType< export async function resolveTSReferencedType( ref: TSResolvedType, stacks: TSResolvedType[] = [] -): Promise { - const { file, type } = ref - if (stacks.some((stack) => stack.file === file && stack.type === type)) { +): Promise { + const { scope, type } = ref + if (stacks.some((stack) => stack.scope === scope && stack.type === type)) { return ref as any } stacks.push(ref) @@ -276,32 +266,51 @@ export async function resolveTSReferencedType( type.type === 'TSTypeAliasDeclaration' || type.type === 'TSParenthesizedType' ) { - return resolveTSReferencedType({ file, type: type.typeAnnotation }, stacks) + return resolveTSReferencedType({ scope, type: type.typeAnnotation }, stacks) } - let refName: string + let refNames: string[] if (type.type === 'Identifier') { - refName = type.name + refNames = [type.name] } else if (type.type === 'TSTypeReference') { - if (type.typeName.type !== 'Identifier') return undefined - refName = type.typeName.name + if (type.typeName.type === 'Identifier') { + refNames = [type.typeName.name] + } else { + refNames = resolveTSEntityName(type.typeName).map((id) => id.name) + } + } else if ( + type.type === 'TSModuleDeclaration' && + type.body.type === 'TSModuleBlock' + ) { + return resolveTSExports({ type: type.body, scope }) } else { - return { file, type } + return { scope, type } } + const [refName, ...restNames] = refNames - for (let node of file.ast) { + const { body, file } = resolveTSScope(scope) + for (let node of body) { if (node.type === 'ImportDeclaration') { const resolved = await resolveTSFileId(node.source.value, file.filePath) if (!resolved) continue const specifier = node.specifiers.find( - (specifier): specifier is ImportSpecifier => - specifier.type === 'ImportSpecifier' && - specifier.imported.type === 'Identifier' && - specifier.imported.name === refName + (specifier): specifier is ImportSpecifier | ImportNamespaceSpecifier => + (specifier.type === 'ImportSpecifier' && + specifier.imported.type === 'Identifier' && + specifier.imported.name === refName) || + (specifier.type === 'ImportNamespaceSpecifier' && + specifier.local.name === refName) ) if (!specifier) continue - const exports = await resolveTSFileExports(await getTSFile(resolved)) - return exports[specifier.local.name] + const exports = await resolveTSExports(await getTSFile(resolved)) + + let type: any = exports + for (const name of specifier.type === 'ImportSpecifier' + ? refNames + : restNames) { + type = type?.[name] + } + return type } if (node.type === 'ExportNamedDeclaration' && node.declaration) @@ -310,15 +319,59 @@ export async function resolveTSReferencedType( if (isTSDeclaration(node)) { if (node.id?.type !== 'Identifier') continue if (node.id.name !== refName) continue - return resolveTSReferencedType({ file, type: node }, stacks) + const resolved = await resolveTSReferencedType( + { scope, type: node }, + stacks + ) + if (!resolved) return + + if (restNames.length === 0) { + return resolved + } else { + let exports = resolved as TSExports + for (const name of restNames) { + exports = exports[name] + } + return exports as unknown as TSResolvedType + } } } - if (type.type === 'TSTypeReference') return { file, type } + if (type.type === 'TSTypeReference') return { scope, type } } -export type TSFileExports = Record -export const tsFileExportsCache: Map = new Map() +export function resolveTSScope(scope: TSScope): { + isFile: boolean + file: TSFile + body: Statement[] +} { + const isFile = 'ast' in scope + const file = isFile ? scope : resolveTSScope(scope.scope).file + const body = isFile ? scope.ast : scope.type.body + + return { + isFile, + file, + body, + } +} + +export function resolveTSEntityName(node: TSEntityName): Identifier[] { + if (node.type === 'Identifier') return [node] + else { + return [...resolveTSEntityName(node.left), node.right] + } +} + +export const exportsSymbol = Symbol('exports') +export type TSExports = { + [K in string]: TSResolvedType | TSExports | undefined +} & { [exportsSymbol]: true } +export const tsFileExportsCache: Map = new Map() + +export function isTSExports(val: unknown): val is TSExports { + return !!val && typeof val === 'object' && exportsSymbol in val +} /** * Get exports of the TS file. @@ -327,63 +380,63 @@ export const tsFileExportsCache: Map = new Map() * @limitation don't support `export default`, since TS don't support it currently. * @limitation don't support `export * as xxx from '...'` (aka namespace). */ -export async function resolveTSFileExports( - file: TSFile -): Promise { - if (tsFileExportsCache.has(file)) return tsFileExportsCache.get(file)! +export async function resolveTSExports(scope: TSScope): Promise { + if (tsFileExportsCache.has(scope)) return tsFileExportsCache.get(scope)! - const exports: TSFileExports = {} - tsFileExportsCache.set(file, exports) + const exports: TSExports = { + [exportsSymbol]: true, + } + tsFileExportsCache.set(scope, exports) - for (const stmt of file.ast) { + const { body, file } = resolveTSScope(scope) + for (const stmt of body) { if (stmt.type === 'ExportDefaultDeclaration') { // TS don't support it. } else if (stmt.type === 'ExportAllDeclaration') { const resolved = await resolveTSFileId(stmt.source.value, file.filePath) if (!resolved) continue - const sourceExports = await resolveTSFileExports( - await getTSFile(resolved) - ) + const sourceExports = await resolveTSExports(await getTSFile(resolved)) Object.assign(exports, sourceExports) } else if (stmt.type === 'ExportNamedDeclaration') { - let sourceExports: Awaited> + let sourceExports: Awaited> if (stmt.source) { const resolved = await resolveTSFileId(stmt.source.value, file.filePath) if (!resolved) continue - sourceExports = await resolveTSFileExports(await getTSFile(resolved)) + sourceExports = await resolveTSExports(await getTSFile(resolved)) } for (const specifier of stmt.specifiers) { - if ( - specifier.type === 'ExportDefaultSpecifier' || - specifier.type === 'ExportNamespaceSpecifier' - ) { + if (specifier.type === 'ExportDefaultSpecifier') { // default export: TS don't support it. - // TODO: namespace: doesn't support it yet. continue } - const exportedName = - specifier.exported.type === 'Identifier' - ? specifier.exported.name - : specifier.exported.value - - if (stmt.source) { - exports[exportedName] = sourceExports![specifier.local.name] + if (specifier.type === 'ExportNamespaceSpecifier') { + exports[specifier.exported.name] = sourceExports! } else { - exports[exportedName] = await resolveTSReferencedType({ - file, - type: specifier.local, - }) + const exportedName = + specifier.exported.type === 'Identifier' + ? specifier.exported.name + : specifier.exported.value + + if (stmt.source) { + exports[exportedName] = sourceExports![specifier.local.name] + } else { + exports[exportedName] = await resolveTSReferencedType({ + scope, + type: specifier.local, + }) + } } } if (isTSDeclaration(stmt.declaration)) { const decl = stmt.declaration + if (decl.id?.type === 'Identifier') { const exportedName = decl.id.name exports[exportedName] = await resolveTSReferencedType({ - file, + scope, type: decl, }) } diff --git a/packages/api/src/vue/emits.ts b/packages/api/src/vue/emits.ts index accd11a47..d7ab39d13 100644 --- a/packages/api/src/vue/emits.ts +++ b/packages/api/src/vue/emits.ts @@ -3,7 +3,12 @@ import { isStaticExpression, resolveLiteral, } from '@vue-macros/common' -import { resolveTSProperties, resolveTSReferencedType } from '../ts' +import { + isTSExports, + resolveTSProperties, + resolveTSReferencedType, + resolveTSScope, +} from '../ts' import { keyToString } from '../utils' import { DefinitionKind } from './types' import { attachNodeLoc } from './utils' @@ -49,13 +54,13 @@ export async function handleTSEmitsDefinition({ }): Promise { const { definitions, definitionsAst } = await resolveDefinitions({ type: typeDeclRaw, - file, + scope: file, }) const addEmit: TSEmits['addEmit'] = (name, signature) => { const key = keyToString(name) - if (definitionsAst.file === file) + if (definitionsAst.scope === file) // TODO: intersection s.appendLeft(definitionsAst.ast.end! + offset - 1, ` ${signature}\n`) @@ -64,7 +69,7 @@ export async function handleTSEmitsDefinition({ definitions[key].push({ code: signature, ast, - file: undefined, + scope: undefined, }) } const setEmit: TSEmits['setEmit'] = (name, idx, signature) => { @@ -75,12 +80,12 @@ export async function handleTSEmitsDefinition({ const ast = parseSignature(signature) attachNodeLoc(def.ast, ast) - if (def.file === file) s.overwriteNode(def.ast, signature, { offset }) + if (def.scope === file) s.overwriteNode(def.ast, signature, { offset }) definitions[key][idx] = { code: signature, ast, - file: undefined, + scope: undefined, } return true @@ -91,7 +96,7 @@ export async function handleTSEmitsDefinition({ const def = definitions[key][idx] if (!def) return false - if (def.file === file) s.removeNode(def.ast, { offset }) + if (def.scope === file) s.removeNode(def.ast, { offset }) delete definitions[key][idx] return true } @@ -116,9 +121,10 @@ export async function handleTSEmitsDefinition({ async function resolveDefinitions(typeDeclRaw: TSResolvedType) { const resolved = await resolveTSReferencedType(typeDeclRaw) - if (!resolved) throw new SyntaxError(`Cannot resolve TS definition.`) + if (!resolved || isTSExports(resolved)) + throw new SyntaxError(`Cannot resolve TS definition.`) - const { type: definitionsAst, file } = resolved + const { type: definitionsAst, scope } = resolved if ( definitionsAst.type !== 'TSInterfaceDeclaration' && definitionsAst.type !== 'TSTypeLiteral' && @@ -127,7 +133,7 @@ export async function handleTSEmitsDefinition({ throw new SyntaxError(`Cannot resolve TS definition.`) const properties = await resolveTSProperties({ - file, + scope, type: definitionsAst, }) @@ -143,9 +149,10 @@ export async function handleTSEmitsDefinition({ const evtType = await resolveTSReferencedType({ type: evtArg.typeAnnotation.typeAnnotation, - file: signature.file, + scope: signature.scope, }) - if (evtType?.type.type !== 'TSLiteralType') continue + if (isTSExports(evtType) || evtType?.type.type !== 'TSLiteralType') + continue const literal = evtType.type.literal if (!isStaticExpression(literal)) continue @@ -158,21 +165,18 @@ export async function handleTSEmitsDefinition({ return { definitions, - definitionsAst: buildDefinition({ file, type: definitionsAst }), + definitionsAst: buildDefinition({ scope, type: definitionsAst }), } } function buildDefinition({ type, - file, - }: { - type: T - file: TSFile - }): ASTDefinition { + scope, + }: TSResolvedType): ASTDefinition { return { - code: file.content.slice(type.start!, type.end!), + code: resolveTSScope(scope).file.content.slice(type.start!, type.end!), ast: type, - file, + scope, } } } diff --git a/packages/api/src/vue/props.ts b/packages/api/src/vue/props.ts index 49a7c4ae1..b64d6586f 100644 --- a/packages/api/src/vue/props.ts +++ b/packages/api/src/vue/props.ts @@ -3,7 +3,12 @@ import { isStaticExpression, resolveObjectExpression, } from '@vue-macros/common' -import { resolveTSProperties, resolveTSReferencedType } from '../ts' +import { + isTSExports, + resolveTSProperties, + resolveTSReferencedType, + resolveTSScope, +} from '../ts' import { keyToString } from '../utils' import { DefinitionKind } from './types' import { attachNodeLoc, inferRuntimeType } from './utils' @@ -58,7 +63,7 @@ export async function handleTSPropsDefinition({ }): Promise { const { definitions, definitionsAst } = await resolveDefinitions({ type: typeDeclRaw, - file, + scope: file, }) const { defaults, defaultsAst } = resolveDefaults(defaultsDeclRaw) @@ -70,7 +75,7 @@ export async function handleTSPropsDefinition({ ) if (definitions[key]) return false - if (definitionsAst.file === file) + if (definitionsAst.scope === file) // TODO: intersection s.appendLeft(definitionsAst.ast.end! + offset - 1, ` ${signature}\n`) @@ -79,13 +84,13 @@ export async function handleTSPropsDefinition({ value: { code: value, ast: valueAst, - file: undefined, + scope: undefined, }, optional: !!optional, signature: { code: signature, ast: signatureAst, - file: undefined, + scope: undefined, }, addByAPI: true, } @@ -106,11 +111,11 @@ export async function handleTSPropsDefinition({ case 'method': { attachNodeLoc(def.methods[0].ast, signatureAst) - if (def.methods[0].file === file) + if (def.methods[0].scope === file) s.overwriteNode(def.methods[0].ast, signature, { offset }) def.methods.slice(1).forEach((method) => { - if (method.file === file) { + if (method.scope === file) { s.removeNode(method.ast, { offset }) } }) @@ -119,7 +124,7 @@ export async function handleTSPropsDefinition({ case 'property': { attachNodeLoc(def.signature.ast, signatureAst) - if (def.signature.file === file && !def.addByAPI) { + if (def.signature.scope === file && !def.addByAPI) { s.overwriteNode(def.signature.ast, signature, { offset }) } break @@ -131,13 +136,13 @@ export async function handleTSPropsDefinition({ value: { code: value, ast: valueAst, - file: undefined as any, + scope: undefined as any, }, optional: !!optional, signature: { code: signature, ast: signatureAst, - file: undefined as any, + scope: undefined as any, }, addByAPI: def.type === 'property' && def.addByAPI, } @@ -151,14 +156,14 @@ export async function handleTSPropsDefinition({ const def = definitions[key] switch (def.type) { case 'property': { - if (def.signature.file === file && !def.addByAPI) { + if (def.signature.scope === file && !def.addByAPI) { s.removeNode(def.signature.ast, { offset }) } break } case 'method': def.methods.forEach((method) => { - if (method.file === file) s.removeNode(method.ast, { offset }) + if (method.scope === file) s.removeNode(method.ast, { offset }) }) break } @@ -183,7 +188,7 @@ export async function handleTSPropsDefinition({ if (resolvedType) { prop = { type: await inferRuntimeType({ - file: resolvedType.file || file, + scope: resolvedType.scope || file, type: resolvedType.ast, }), required: !def.optional, @@ -240,9 +245,10 @@ export async function handleTSPropsDefinition({ definitionsAst: TSProps['definitionsAst'] }> { const resolved = await resolveTSReferencedType(typeDeclRaw) - if (!resolved) throw new SyntaxError(`Cannot resolve TS definition.`) + if (!resolved || isTSExports(resolved)) + throw new SyntaxError(`Cannot resolve TS definition.`) - const { type: definitionsAst, file } = resolved + const { type: definitionsAst, scope } = resolved if ( definitionsAst.type !== 'TSInterfaceDeclaration' && definitionsAst.type !== 'TSTypeLiteral' && @@ -251,7 +257,7 @@ export async function handleTSPropsDefinition({ throw new SyntaxError(`Cannot resolve TS definition.`) const properties = await resolveTSProperties({ - file, + scope, type: definitionsAst, }) @@ -270,7 +276,10 @@ export async function handleTSPropsDefinition({ definitions[key] = { type: 'property', addByAPI: false, - value: referenced ? buildDefinition(referenced) : undefined, + value: + referenced && !isTSExports(referenced) + ? buildDefinition(referenced) + : undefined, optional: value.optional, signature: buildDefinition(value.signature), } @@ -278,7 +287,7 @@ export async function handleTSPropsDefinition({ return { definitions, - definitionsAst: buildDefinition({ file, type: definitionsAst }), + definitionsAst: buildDefinition({ scope, type: definitionsAst }), } } @@ -324,12 +333,12 @@ export async function handleTSPropsDefinition({ function buildDefinition({ type, - file, + scope, }: TSResolvedType): ASTDefinition { return { - code: file.content.slice(type.start!, type.end!), + code: resolveTSScope(scope).file.content.slice(type.start!, type.end!), ast: type, - file, + scope, } } } diff --git a/packages/api/src/vue/types.ts b/packages/api/src/vue/types.ts index 19a9c8cb4..38c0cbb5b 100644 --- a/packages/api/src/vue/types.ts +++ b/packages/api/src/vue/types.ts @@ -1,5 +1,5 @@ -import type { Node } from '@babel/types' -import type { TSFile } from '../ts' +import type { Node, TSModuleBlock } from '@babel/types' +import type { TSFile, TSResolvedType } from '../ts' export enum DefinitionKind { /** @@ -24,6 +24,6 @@ export enum DefinitionKind { export interface ASTDefinition { code: string - file: TSFile | undefined + scope: TSFile | TSResolvedType | undefined ast: T } diff --git a/packages/api/src/vue/utils.ts b/packages/api/src/vue/utils.ts index 1b7c285b2..076de77f5 100644 --- a/packages/api/src/vue/utils.ts +++ b/packages/api/src/vue/utils.ts @@ -1,4 +1,4 @@ -import { resolveTSReferencedType } from '../ts' +import { isTSExports, resolveTSReferencedType } from '../ts' import type { Node } from '@babel/types' import type { TSResolvedType } from '../ts' @@ -69,10 +69,12 @@ export async function inferRuntimeType( await Promise.all( node.type.types.map(async (subType) => { const resolved = await resolveTSReferencedType({ - file: node.file, + scope: node.scope, type: subType, }) - return resolved ? inferRuntimeType(resolved) : undefined + return resolved && !isTSExports(resolved) + ? inferRuntimeType(resolved) + : undefined }) ) ) diff --git a/packages/api/tests/_util.ts b/packages/api/tests/_util.ts index cecfdc194..1a4cc162b 100644 --- a/packages/api/tests/_util.ts +++ b/packages/api/tests/_util.ts @@ -3,7 +3,7 @@ import { expect } from 'vitest' export function hideAstLocation(ast: T): T { return JSON.parse( JSON.stringify(ast, (key, value) => { - if (key === 'file') return undefined + if (key === 'scope') return undefined return key === 'ast' || (typeof value === 'object' && 'type' in value && 'loc' in value) diff --git a/packages/api/tests/fixtures/namespace/bar.ts b/packages/api/tests/fixtures/namespace/bar.ts new file mode 100644 index 000000000..d4cea0f8f --- /dev/null +++ b/packages/api/tests/fixtures/namespace/bar.ts @@ -0,0 +1 @@ +export type Str = string diff --git a/packages/api/tests/fixtures/namespace/foo.ts b/packages/api/tests/fixtures/namespace/foo.ts new file mode 100644 index 000000000..150732c3f --- /dev/null +++ b/packages/api/tests/fixtures/namespace/foo.ts @@ -0,0 +1,8 @@ +export type Foo = 'foo' +export type Str = string +export type Num = number +export * as Bar from './bar' + +export namespace NSFoo { + export type Foo = boolean +} diff --git a/packages/api/tests/fixtures/namespace/index.ts b/packages/api/tests/fixtures/namespace/index.ts new file mode 100644 index 000000000..cf48d5998 --- /dev/null +++ b/packages/api/tests/fixtures/namespace/index.ts @@ -0,0 +1,14 @@ +import * as foo from './foo' +import { NestedNS } from './nested' + +module NS { + export type Bar = string +} + +export type Str = foo.Str +export type Num = foo.Num +export type Foo = foo.Foo +export type BarStr = foo.Bar.Str +export type NSFoo = foo.NSFoo.Foo +export type NSBar = NS.Bar +export type NestedNestedFoo = NestedNS.NestedNS2.NestedNS3.Foo diff --git a/packages/api/tests/fixtures/namespace/nested.ts b/packages/api/tests/fixtures/namespace/nested.ts new file mode 100644 index 000000000..a876bde72 --- /dev/null +++ b/packages/api/tests/fixtures/namespace/nested.ts @@ -0,0 +1,7 @@ +export namespace NestedNS { + export namespace NestedNS2 { + export module NestedNS3 { + export type Foo = string | boolean + } + } +} diff --git a/packages/api/tests/ts.test.ts b/packages/api/tests/ts.test.ts index 99cdfc84e..be9b9dde2 100644 --- a/packages/api/tests/ts.test.ts +++ b/packages/api/tests/ts.test.ts @@ -3,7 +3,7 @@ import { babelParse } from '@vue-macros/common' import { describe, expect, test } from 'vitest' import { getTSFile, - resolveTSFileExports, + resolveTSExports, resolveTSProperties, resolveTSReferencedType, } from '../src' @@ -66,7 +66,7 @@ type Base2 = { ` ) const interfaceProperties = await resolveTSProperties({ - file, + scope: file, type: file.ast[0] as TSInterfaceDeclaration, }) @@ -157,7 +157,7 @@ type Base2 = { ).toMatchInlineSnapshot('"TSStringKeyword..."') const intersectionProperties = await resolveTSProperties({ - file, + scope: file, type: (file.ast[1] as TSTypeAliasDeclaration) .typeAnnotation as TSIntersectionType, }) @@ -205,16 +205,16 @@ type Foo = AliasString` ) const node = file.ast[1] as TSTypeAliasDeclaration const result = (await resolveTSReferencedType({ - file, + scope: file, type: node.typeAnnotation, }))! - expect(result.type.type).toBe('TSStringKeyword') + expect(result.type!.type).toBe('TSStringKeyword') }) describe('resolveTSFileExports', () => { test('basic', async () => { const file = await getTSFile(path.resolve(fixtures, 'basic/index.ts')) - const exports = await resolveTSFileExports(file) + const exports = await resolveTSExports(file) expect(hideAstLocation(exports)).toMatchInlineSnapshot(` { "ExportAll": { @@ -250,7 +250,7 @@ type Foo = AliasString` expect( hideAstLocation( await resolveTSProperties({ - file, + scope: file, type: exports.Inferface?.type as any, }) ) @@ -296,7 +296,7 @@ type Foo = AliasString` const file = await getTSFile( path.resolve(fixtures, 'circular-referencing/foo.ts') ) - const exports = await resolveTSFileExports(file) + const exports = await resolveTSExports(file) expect(hideAstLocation(exports)).toMatchInlineSnapshot(` { "A": { @@ -314,5 +314,35 @@ type Foo = AliasString` } `) }) + + test('namespace', async () => { + const file = await getTSFile(path.resolve(fixtures, 'namespace/index.ts')) + const exports = await resolveTSExports(file) + expect(hideAstLocation(exports)).toMatchInlineSnapshot(` + { + "BarStr": { + "type": "TSStringKeyword...", + }, + "Foo": { + "type": "TSLiteralType...", + }, + "NSBar": { + "type": "TSStringKeyword...", + }, + "NSFoo": { + "type": "TSBooleanKeyword...", + }, + "NestedNestedFoo": { + "type": "TSUnionType...", + }, + "Num": { + "type": "TSNumberKeyword...", + }, + "Str": { + "type": "TSStringKeyword...", + }, + } + `) + }) }) })