Skip to content

Commit dd3c186

Browse files
authored
fix: unexpected unknown type for api docs format type (#1663)
1 parent 098c2aa commit dd3c186

File tree

8 files changed

+559
-16
lines changed

8 files changed

+559
-16
lines changed

server/utils/docs/format.ts

Lines changed: 56 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ export function getNodeSignature(node: DenoDocNode): string | null {
2525
return `${asyncStr}function ${name}${typeParamsStr}(${params}): ${ret}`
2626
}
2727
case 'class': {
28-
const ext = node.classDef?.extends ? ` extends ${formatType(node.classDef.extends)}` : ''
28+
const ext = node.classDef?.extends ? ` extends ${node.classDef.extends}` : ''
2929
const impl = node.classDef?.implements?.map(t => formatType(t)).join(', ')
3030
const implStr = impl ? ` implements ${impl}` : ''
3131
const abstractStr = node.classDef?.isAbstract ? 'abstract ' : ''
@@ -72,25 +72,66 @@ export function formatParam(param: FunctionParam): string {
7272
export function formatType(type?: TsType): string {
7373
if (!type) return ''
7474

75-
// Strip ANSI codes from repr (deno doc may include terminal colors since it's built for that)
76-
if (type.repr) return stripAnsi(type.repr)
75+
const formatter = TYPE_FORMATTERS[type.kind]
76+
const formatted = formatter?.(type)
7777

78-
if (type.kind === 'keyword' && type.keyword) {
79-
return type.keyword
80-
}
78+
if (formatted) return formatted
79+
return type.repr ? stripAnsi(type.repr) : 'unknown'
80+
}
8181

82-
if (type.kind === 'typeRef' && type.typeRef) {
82+
const TYPE_FORMATTERS: Partial<Record<TsType['kind'], (type: TsType) => string>> = {
83+
keyword: type => type.keyword || '',
84+
literal: type => {
85+
if (!type.literal) return ''
86+
if (type.literal.kind === 'string') return `"${type.literal.string}"`
87+
if (type.literal.kind === 'number') return String(type.literal.number)
88+
if (type.literal.kind === 'boolean') return String(type.literal.boolean)
89+
return ''
90+
},
91+
typeRef: type => {
92+
if (!type.typeRef) return ''
8393
const params = type.typeRef.typeParams?.map(t => formatType(t)).join(', ')
8494
return params ? `${type.typeRef.typeName}<${params}>` : type.typeRef.typeName
85-
}
95+
},
96+
array: type => {
97+
if (!type.array) return ''
98+
const element = formatType(type.array)
99+
return type.array.kind === 'union' ? `(${element})[]` : `${element}[]`
100+
},
101+
union: type => (type.union ? type.union.map(t => formatType(t)).join(' | ') : ''),
102+
this: () => 'this',
103+
indexedAccess: type => {
104+
if (!type.indexedAccess) return ''
105+
return `${formatType(type.indexedAccess.objType)}[${formatType(type.indexedAccess.indexType)}]`
106+
},
107+
typeOperator: type => {
108+
if (!type.typeOperator) return ''
109+
return `${type.typeOperator.operator} ${formatType(type.typeOperator.tsType)}`
110+
},
111+
fnOrConstructor: type =>
112+
type.fnOrConstructor ? formatFnOrConstructorType(type.fnOrConstructor) : '',
113+
typeLiteral: type => (type.typeLiteral ? formatTypeLiteralType(type.typeLiteral) : ''),
114+
}
86115

87-
if (type.kind === 'array' && type.array) {
88-
return `${formatType(type.array)}[]`
89-
}
116+
function formatFnOrConstructorType(fn: NonNullable<TsType['fnOrConstructor']>): string {
117+
const typeParams = fn.typeParams?.map(t => t.name).join(', ')
118+
const typeParamsStr = typeParams ? `<${typeParams}>` : ''
119+
const params = fn.params.map(p => formatParam(p)).join(', ')
120+
const ret = formatType(fn.tsType) || 'void'
121+
return `${typeParamsStr}(${params}) => ${ret}`
122+
}
90123

91-
if (type.kind === 'union' && type.union) {
92-
return type.union.map(t => formatType(t)).join(' | ')
124+
function formatTypeLiteralType(lit: NonNullable<TsType['typeLiteral']>): string {
125+
const parts: string[] = []
126+
for (const prop of lit.properties) {
127+
const opt = prop.optional ? '?' : ''
128+
const ro = prop.readonly ? 'readonly ' : ''
129+
parts.push(`${ro}${prop.name}${opt}: ${formatType(prop.tsType) || 'unknown'}`)
93130
}
94-
95-
return type.repr ? stripAnsi(type.repr) : 'unknown'
131+
for (const method of lit.methods) {
132+
const params = method.params?.map(p => formatParam(p)).join(', ') || ''
133+
const ret = formatType(method.returnType) || 'void'
134+
parts.push(`${method.name}(${params}): ${ret}`)
135+
}
136+
return `{ ${parts.join('; ')} }`
96137
}

shared/types/deno-doc.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,27 @@ export interface TsType {
3535
number?: number
3636
boolean?: boolean
3737
}
38+
fnOrConstructor?: {
39+
constructor: boolean
40+
tsType: TsType
41+
params: FunctionParam[]
42+
typeParams?: Array<{ name: string; constraint?: TsType }>
43+
}
44+
indexedAccess?: {
45+
objType: TsType
46+
indexType: TsType
47+
}
48+
typeOperator?: {
49+
operator: string
50+
tsType: TsType
51+
}
52+
this?: boolean
53+
typeLiteral?: {
54+
properties: Array<{ name: string; tsType?: TsType; readonly?: boolean; optional?: boolean }>
55+
methods: Array<{ name: string; params?: FunctionParam[]; returnType?: TsType }>
56+
callSignatures: Array<{ params?: FunctionParam[]; tsType?: TsType }>
57+
indexSignatures: Array<{ params: FunctionParam[]; tsType?: TsType }>
58+
}
3859
}
3960

4061
/** Function parameter from deno doc */
@@ -89,7 +110,7 @@ export interface DenoDocNode {
89110
constructors?: Array<{
90111
params?: FunctionParam[]
91112
}>
92-
extends?: TsType
113+
extends?: string
93114
implements?: TsType[]
94115
}
95116
interfaceDef?: {
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
{
2+
"name": "ClientMessage",
3+
"kind": "typeAlias",
4+
"typeAliasDef": {
5+
"tsType": {
6+
"repr": "",
7+
"kind": "union",
8+
"union": [
9+
{
10+
"repr": "",
11+
"kind": "typeLiteral",
12+
"typeLiteral": {
13+
"properties": [
14+
{
15+
"name": "op",
16+
"optional": false,
17+
"tsType": {
18+
"repr": "voiceUpdate",
19+
"kind": "literal",
20+
"literal": { "kind": "string", "string": "voiceUpdate" }
21+
}
22+
},
23+
{
24+
"name": "guildId",
25+
"optional": false,
26+
"tsType": { "repr": "string", "kind": "keyword", "keyword": "string" }
27+
}
28+
],
29+
"methods": [],
30+
"callSignatures": [],
31+
"indexSignatures": []
32+
}
33+
},
34+
{
35+
"repr": "",
36+
"kind": "typeLiteral",
37+
"typeLiteral": {
38+
"properties": [
39+
{
40+
"name": "op",
41+
"optional": false,
42+
"tsType": {
43+
"repr": "play",
44+
"kind": "literal",
45+
"literal": { "kind": "string", "string": "play" }
46+
}
47+
},
48+
{
49+
"name": "guildId",
50+
"optional": false,
51+
"tsType": { "repr": "string", "kind": "keyword", "keyword": "string" }
52+
}
53+
],
54+
"methods": [],
55+
"callSignatures": [],
56+
"indexSignatures": []
57+
}
58+
}
59+
]
60+
},
61+
"typeParams": []
62+
}
63+
}
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
{
2+
"name": "LinkDaveClient",
3+
"kind": "interface",
4+
"interfaceDef": {
5+
"extends": [],
6+
"constructors": [],
7+
"methods": [],
8+
"properties": [
9+
{
10+
"name": "on",
11+
"computed": false,
12+
"optional": false,
13+
"tsType": {
14+
"repr": "",
15+
"kind": "fnOrConstructor",
16+
"fnOrConstructor": {
17+
"constructor": false,
18+
"tsType": { "repr": "this", "kind": "this", "this": true },
19+
"params": [
20+
{
21+
"kind": "identifier",
22+
"name": "event",
23+
"optional": false,
24+
"tsType": {
25+
"repr": "K",
26+
"kind": "typeRef",
27+
"typeRef": { "typeName": "K" }
28+
}
29+
},
30+
{
31+
"kind": "identifier",
32+
"name": "listener",
33+
"optional": false,
34+
"tsType": {
35+
"repr": "",
36+
"kind": "fnOrConstructor",
37+
"fnOrConstructor": {
38+
"constructor": false,
39+
"tsType": { "repr": "void", "kind": "keyword", "keyword": "void" },
40+
"params": [
41+
{
42+
"kind": "identifier",
43+
"name": "data",
44+
"optional": false,
45+
"tsType": {
46+
"repr": "",
47+
"kind": "indexedAccess",
48+
"indexedAccess": {
49+
"readonly": false,
50+
"objType": {
51+
"repr": "ManagerEvents",
52+
"kind": "typeRef",
53+
"typeRef": { "typeName": "ManagerEvents" }
54+
},
55+
"indexType": {
56+
"repr": "K",
57+
"kind": "typeRef",
58+
"typeRef": { "typeName": "K" }
59+
}
60+
}
61+
}
62+
}
63+
],
64+
"typeParams": []
65+
}
66+
}
67+
}
68+
],
69+
"typeParams": [
70+
{
71+
"name": "K",
72+
"constraint": {
73+
"repr": "",
74+
"kind": "typeOperator",
75+
"typeOperator": {
76+
"operator": "keyof",
77+
"tsType": {
78+
"repr": "ManagerEvents",
79+
"kind": "typeRef",
80+
"typeRef": { "typeName": "ManagerEvents" }
81+
}
82+
}
83+
}
84+
}
85+
]
86+
}
87+
}
88+
},
89+
{
90+
"name": "emit",
91+
"computed": false,
92+
"optional": false,
93+
"tsType": {
94+
"repr": "",
95+
"kind": "fnOrConstructor",
96+
"fnOrConstructor": {
97+
"constructor": false,
98+
"tsType": { "repr": "boolean", "kind": "keyword", "keyword": "boolean" },
99+
"params": [
100+
{
101+
"kind": "identifier",
102+
"name": "event",
103+
"optional": false,
104+
"tsType": {
105+
"repr": "K",
106+
"kind": "typeRef",
107+
"typeRef": { "typeName": "K" }
108+
}
109+
},
110+
{
111+
"kind": "identifier",
112+
"name": "data",
113+
"optional": false,
114+
"tsType": {
115+
"repr": "",
116+
"kind": "indexedAccess",
117+
"indexedAccess": {
118+
"readonly": false,
119+
"objType": {
120+
"repr": "ManagerEvents",
121+
"kind": "typeRef",
122+
"typeRef": { "typeName": "ManagerEvents" }
123+
},
124+
"indexType": {
125+
"repr": "K",
126+
"kind": "typeRef",
127+
"typeRef": { "typeName": "K" }
128+
}
129+
}
130+
}
131+
}
132+
],
133+
"typeParams": [
134+
{
135+
"name": "K",
136+
"constraint": {
137+
"repr": "",
138+
"kind": "typeOperator",
139+
"typeOperator": {
140+
"operator": "keyof",
141+
"tsType": {
142+
"repr": "ManagerEvents",
143+
"kind": "typeRef",
144+
"typeRef": { "typeName": "ManagerEvents" }
145+
}
146+
}
147+
}
148+
}
149+
]
150+
}
151+
}
152+
}
153+
],
154+
"callSignatures": [],
155+
"indexSignatures": [],
156+
"typeParams": []
157+
}
158+
}

0 commit comments

Comments
 (0)