|
1 | 1 | import { Heading, Markdown } from '@storybook/blocks'; |
| 2 | +import dedent from 'dedent'; |
2 | 3 | import { Fragment } from 'react'; |
3 | 4 |
|
4 | | -interface CommandsAndQueries { |
5 | | - id: number; |
6 | | - name: string; |
7 | | - kind: number; |
8 | | - kindString: string; |
9 | | - flags: CommandsAndQueriesFlags; |
10 | | - sources: Source[]; |
11 | | - signatures: Signature[]; |
| 5 | +// Note: these types may be incomplete or faulty. If an error is thrown, first check if the interfaces have to be extended. |
| 6 | +interface TextNode { |
| 7 | + type: 'text'; |
| 8 | + value: string; |
12 | 9 | } |
13 | 10 |
|
14 | | -// eslint-disable-next-line @typescript-eslint/no-empty-interface |
15 | | -interface CommandsAndQueriesFlags {} |
| 11 | +interface StrongNode { |
| 12 | + type: 'strong'; |
| 13 | + children: TextNode[]; |
| 14 | +} |
16 | 15 |
|
17 | | -interface Signature { |
18 | | - id: number; |
19 | | - name: string; |
20 | | - kind: number; |
21 | | - kindString: string; |
22 | | - flags: CommandsAndQueriesFlags; |
23 | | - comment: SignatureComment; |
24 | | - parameters: Parameter[]; |
25 | | - type: Type; |
| 16 | +interface InlineCodeNode { |
| 17 | + type: 'inlineCode'; |
| 18 | + value: string; |
26 | 19 | } |
27 | 20 |
|
28 | | -interface SignatureComment { |
29 | | - summary: Summary[]; |
30 | | - blockTags: BlockTag[]; |
| 21 | +interface ParagraphNode { |
| 22 | + type: 'paragraph'; |
| 23 | + children: Array<TextNode | StrongNode | InlineCodeNode>; |
31 | 24 | } |
32 | 25 |
|
33 | | -interface BlockTag { |
34 | | - tag: string; |
35 | | - content: Summary[]; |
| 26 | +interface RootNode { |
| 27 | + type: 'root'; |
| 28 | + children: ParagraphNode[]; |
36 | 29 | } |
37 | 30 |
|
38 | | -interface Summary { |
39 | | - kind: string; |
40 | | - text: string; |
| 31 | +interface TypeExpression { |
| 32 | + type: 'NameExpression' | 'OptionalType' | 'TypeApplication'; |
| 33 | + name?: string; |
| 34 | + expression?: TypeExpression; |
| 35 | + applications?: TypeExpression[]; |
41 | 36 | } |
42 | 37 |
|
43 | | -interface Parameter { |
44 | | - id: number; |
| 38 | +interface Tag { |
| 39 | + title: string; |
| 40 | + description?: string | RootNode; |
| 41 | + lineNumber: number; |
| 42 | + type?: TypeExpression; |
| 43 | + name?: string; |
| 44 | + default?: string; |
| 45 | +} |
| 46 | + |
| 47 | +interface Loc { |
| 48 | + start: { |
| 49 | + line: number; |
| 50 | + column: number; |
| 51 | + index: number; |
| 52 | + }; |
| 53 | + end: { |
| 54 | + line: number; |
| 55 | + column: number; |
| 56 | + index: number; |
| 57 | + }; |
| 58 | +} |
| 59 | + |
| 60 | +interface Context { |
| 61 | + loc: Loc; |
| 62 | + file: string; |
| 63 | +} |
| 64 | + |
| 65 | +interface Example { |
| 66 | + description: string; |
| 67 | +} |
| 68 | + |
| 69 | +interface Param { |
| 70 | + title: string; |
45 | 71 | name: string; |
46 | | - kind: number; |
47 | | - kindString: string; |
48 | | - flags: ParameterFlags; |
49 | | - comment?: ParameterComment; |
50 | | - type: Type; |
| 72 | + lineNumber: number; |
| 73 | + description: RootNode; |
| 74 | + type: TypeExpression; |
| 75 | + default?: string; |
| 76 | +} |
| 77 | + |
| 78 | +interface Return { |
| 79 | + title: string; |
| 80 | + type: TypeExpression; |
51 | 81 | } |
52 | 82 |
|
53 | | -interface ParameterComment { |
54 | | - summary: Summary[]; |
| 83 | +interface Path { |
| 84 | + name: string; |
| 85 | + kind: string; |
| 86 | + scope: string; |
55 | 87 | } |
56 | 88 |
|
57 | | -interface ParameterFlags { |
58 | | - isOptional?: boolean; |
| 89 | +interface Members { |
| 90 | + global: any[]; |
| 91 | + inner: any[]; |
| 92 | + instance: any[]; |
| 93 | + events: any[]; |
| 94 | + static: any[]; |
59 | 95 | } |
60 | 96 |
|
61 | | -interface Type { |
62 | | - type: string; |
| 97 | +interface FunctionMetadata { |
| 98 | + description: RootNode; |
| 99 | + tags: Tag[]; |
| 100 | + loc: Loc; |
| 101 | + context: Context; |
| 102 | + augments: any[]; |
| 103 | + examples: Example[]; |
| 104 | + implements: any[]; |
| 105 | + params: Param[]; |
| 106 | + properties: any[]; |
| 107 | + returns: Return[]; |
| 108 | + sees: any[]; |
| 109 | + throws: any[]; |
| 110 | + todos: any[]; |
| 111 | + yields: any[]; |
63 | 112 | name: string; |
64 | | - typeArguments?: Type[]; |
65 | | - qualifiedName?: string; |
66 | | - package?: string; |
| 113 | + kind: 'function'; |
| 114 | + memberof: string; |
| 115 | + scope: 'instance'; |
| 116 | + members: Members; |
| 117 | + path: Path[]; |
| 118 | + namespace: string; |
67 | 119 | } |
68 | 120 |
|
69 | | -interface Source { |
70 | | - fileName: string; |
71 | | - line: number; |
72 | | - character: number; |
73 | | - url: string; |
| 121 | +function generateMdCodeBlock(codeStr: string) { |
| 122 | + return dedent` |
| 123 | +\`\`\`ts |
| 124 | +${codeStr} |
| 125 | +\`\`\` |
| 126 | + |
| 127 | +`; |
74 | 128 | } |
75 | 129 |
|
76 | | -function typeArgumentsString(typeArguments: Type[] | undefined) { |
77 | | - if (!typeArguments) { |
78 | | - return ''; |
79 | | - } |
80 | | - return typeArguments.map((args, index, arr) => { |
81 | | - let str = args.name; |
82 | | - if (arr.length > 1 && index + 1 !== arr.length) { |
83 | | - str += ', '; |
| 130 | +function generateGenericType(typeExpression: TypeExpression) { |
| 131 | + if (typeExpression) { |
| 132 | + const { expression, applications } = typeExpression; |
| 133 | + |
| 134 | + if (typeof typeExpression?.name === 'string') { |
| 135 | + return typeExpression.name; |
84 | 136 | } |
85 | | - return str; |
86 | | - }); |
| 137 | + return `${expression.name}<${applications |
| 138 | + ?.map((app) => { |
| 139 | + return app.name; |
| 140 | + }) |
| 141 | + .join(', ')}>`; |
| 142 | + } |
| 143 | + return ''; |
| 144 | +} |
| 145 | + |
| 146 | +function generateParamTypes(type: Param['type']) { |
| 147 | + if (type?.name) { |
| 148 | + return type.name; |
| 149 | + } |
| 150 | + return generateGenericType(type.expression); |
87 | 151 | } |
88 | 152 |
|
89 | | -export const CommandsAndQueries = ({ api }: { api: CommandsAndQueries[] }) => { |
90 | | - return api.map((item) => { |
91 | | - const { signatures } = item; |
92 | | - const { parameters } = signatures[0]; |
| 153 | +function generateExample(tags: FunctionMetadata['tags']) { |
| 154 | + const example = tags.find((tag) => tag.title === 'example'); |
| 155 | + if (example && typeof example.description === 'string') { |
93 | 156 | return ( |
94 | | - <Fragment key={item.name}> |
95 | | - <Heading>{item.name}</Heading> |
96 | | - <code> |
97 | | - {item.name}( |
98 | | - {parameters |
99 | | - ?.map( |
100 | | - (param) => |
101 | | - `${param.name}:${param.type.name}${ |
102 | | - param.type?.typeArguments?.length ? `<${typeArgumentsString(param.type.typeArguments)}>` : '' |
103 | | - }` |
104 | | - ) |
105 | | - .join(', ')} |
106 | | - ): |
107 | | - {signatures[0]?.type?.name} |
108 | | - {`<${typeArgumentsString(signatures[0].type?.typeArguments)}>`} |
109 | | - </code> |
110 | | - <div> |
111 | | - <Markdown> |
112 | | - {signatures[0]?.comment.summary.reduce((acc, cur) => `${acc}${cur.text.replaceAll('\n', '<br>')}`, '')} |
113 | | - </Markdown> |
114 | | - {signatures[0]?.comment?.blockTags |
115 | | - ?.filter((blockTag) => { |
116 | | - return blockTag.tag === '@example'; |
117 | | - }) |
118 | | - .map((example, index) => { |
119 | | - return ( |
120 | | - <Fragment key={`${example.tag}${index}`}> |
121 | | - {index === 0 && <b>Example</b>} |
122 | | - <Markdown>{example.content.reduce((acc, cur) => `${acc}${cur.text}`, '')}</Markdown> |
123 | | - </Fragment> |
124 | | - ); |
| 157 | + <> |
| 158 | + <Markdown>### Example</Markdown> |
| 159 | + <Markdown>{generateMdCodeBlock(example.description)}</Markdown> |
| 160 | + </> |
| 161 | + ); |
| 162 | + } |
| 163 | + return null; |
| 164 | +} |
| 165 | + |
| 166 | +function formatText(text: ParagraphNode['children'][0]) { |
| 167 | + switch (text.type) { |
| 168 | + case 'text': |
| 169 | + return text.value; |
| 170 | + case 'strong': |
| 171 | + return `**${text.children.reduce((acc, cur) => { |
| 172 | + acc += cur.value; |
| 173 | + return acc; |
| 174 | + }, '')}**`; |
| 175 | + case 'inlineCode': |
| 176 | + return `\`${text.value}\``; |
| 177 | + default: |
| 178 | + if (typeof text.value === 'string') { |
| 179 | + return text.value; |
| 180 | + } |
| 181 | + console.warn('Unknown text type!'); |
| 182 | + return ''; |
| 183 | + } |
| 184 | +} |
| 185 | + |
| 186 | +function generateDescription(description: RootNode) { |
| 187 | + return description.children.reduce((acc, descriptionNode) => { |
| 188 | + if (descriptionNode.type === 'paragraph') { |
| 189 | + acc += descriptionNode.children.reduce((acc, p) => { |
| 190 | + acc += formatText(p); |
| 191 | + return acc; |
| 192 | + }, ''); |
| 193 | + acc += '\n\n'; |
| 194 | + return acc; |
| 195 | + } |
| 196 | + }, ''); |
| 197 | +} |
| 198 | + |
| 199 | +export const CommandsAndQueries = ({ api }: { api: FunctionMetadata[] }) => { |
| 200 | + return api |
| 201 | + .sort((a, b) => a.name.localeCompare(b.name)) |
| 202 | + .map((item) => { |
| 203 | + return ( |
| 204 | + <Fragment key={item.name}> |
| 205 | + <Heading>{item.name}</Heading> |
| 206 | + <code> |
| 207 | + {item.name}( |
| 208 | + {item.params |
| 209 | + ?.map((param) => { |
| 210 | + return `${param.name}${param.type.type === 'OptionalType' ? '?' : ''}:${generateParamTypes(param.type)}`; |
| 211 | + }) |
| 212 | + .join(', ')} |
| 213 | + ): |
| 214 | + {item.returns.map(({ type }) => { |
| 215 | + return generateGenericType(type); |
125 | 216 | })} |
126 | | - {parameters?.length && ( |
| 217 | + </code> |
| 218 | + <Markdown>{generateDescription(item.description)}</Markdown> |
| 219 | + {generateExample(item.tags)} |
| 220 | + {!!item.params.length && ( |
127 | 221 | <> |
128 | | - <b>Parameters</b> |
| 222 | + <Markdown>### Parameters</Markdown> |
129 | 223 | <table> |
130 | 224 | <thead> |
131 | 225 | <tr> |
132 | | - <td>Name</td> |
133 | | - <td>Type</td> |
134 | | - <td>Description</td> |
| 226 | + <td> |
| 227 | + <b>Name</b> |
| 228 | + </td> |
| 229 | + <td> |
| 230 | + <b>Type</b> |
| 231 | + </td> |
| 232 | + <td> |
| 233 | + <b>Description</b> |
| 234 | + </td> |
135 | 235 | </tr> |
136 | 236 | </thead> |
137 | 237 | <tbody> |
138 | | - {parameters?.map((param) => ( |
139 | | - <tr key={param.name}> |
140 | | - <td> |
141 | | - {param.name} |
142 | | - {param.flags?.isOptional && '?'} |
143 | | - </td> |
144 | | - <td> |
145 | | - <code> |
146 | | - {param.type?.name} |
147 | | - {param.type?.typeArguments && |
148 | | - `<${param.type?.typeArguments && typeArgumentsString(param.type.typeArguments)}>`} |
149 | | - </code> |
150 | | - </td> |
151 | | - <td> |
152 | | - {param.comment?.summary.reduce((acc, cur) => `${acc}${cur.text.replaceAll('\n', '<br>')}`, '')} |
153 | | - </td> |
154 | | - </tr> |
155 | | - ))} |
| 238 | + {item.params.map((param) => { |
| 239 | + return ( |
| 240 | + <tr key={param.name}> |
| 241 | + <td>{param.type.type === 'OptionalType' ? <i>{param.name}?</i> : param.name}</td> |
| 242 | + <td>{generateParamTypes(param.type)}</td> |
| 243 | + <td>{param?.description ? generateDescription(param.description) : '-'}</td> |
| 244 | + </tr> |
| 245 | + ); |
| 246 | + })} |
156 | 247 | </tbody> |
157 | 248 | </table> |
158 | 249 | </> |
159 | 250 | )} |
160 | | - <p> |
161 | | - <b>Source:</b> <a href={item.sources[0].url}>{`${item.sources[0].fileName}:${item.sources[0].line}`}</a> |
162 | | - </p> |
163 | | - </div> |
164 | | - <br /> |
165 | | - <br /> |
166 | | - </Fragment> |
167 | | - ); |
168 | | - }); |
| 251 | + </Fragment> |
| 252 | + ); |
| 253 | + }); |
169 | 254 | }; |
170 | 255 |
|
171 | 256 | CommandsAndQueries.displayName = 'CommandsAndQueries'; |
0 commit comments