Skip to content

Commit 6c60fbe

Browse files
refactor: extract markdown types and jsdocs to separate modules (#70)
* refactor: extract markdown types and jsdocs to separate modules * 🤖 Documentation auto-update --------- Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
1 parent 8ff5417 commit 6c60fbe

File tree

6 files changed

+120
-126
lines changed

6 files changed

+120
-126
lines changed

README.md

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,6 @@ generateDocumentation({
133133
## :toolbox: Functions
134134

135135
- [buildDocumentation](#gear-builddocumentation)
136-
- [documentationToMarkdown](#gear-documentationtomarkdown)
137136
- [generateDocumentation](#gear-generatedocumentation)
138137

139138
### :gear: buildDocumentation
@@ -155,21 +154,6 @@ An array of documentation entries
155154

156155
[:link: Source](https://github.com/peterpeterparker/tsdoc-markdown/tree/main/src/lib/docs.ts#L497)
157156

158-
### :gear: documentationToMarkdown
159-
160-
Convert the documentation entries to an opinionated Markdown format.
161-
162-
| Function | Type |
163-
| ------------------------- | ---------------------------------------------------------------------------------------------------- |
164-
| `documentationToMarkdown` | `({ entries, options }: { entries: DocEntry[]; options?: MarkdownOptions or undefined; }) => string` |
165-
166-
Parameters:
167-
168-
- `params.entries`: The entries of the documentation (functions, constants and classes).
169-
- `params.options`: Optional configuration to render the Markdown content. See `types.ts` for details.
170-
171-
[:link: Source](https://github.com/peterpeterparker/tsdoc-markdown/tree/main/src/lib/markdown.ts#L418)
172-
173157
### :gear: generateDocumentation
174158

175159
Generate documentation and write output to a file.

src/lib/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import {existsSync, readFileSync, writeFileSync} from 'node:fs';
22
import {buildDocumentation} from './docs';
3-
import {documentationToMarkdown} from './markdown';
3+
import {documentationToMarkdown} from './parser/markdown';
44
import type {
55
BuildOptions,
66
DocEntry,

src/lib/parser/jdocs/mapper.ts

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import type {JSDocTagInfo, SymbolDisplayPart} from 'typescript';
2+
import type {Params} from '../types';
3+
4+
const jsDocsToSymbolDisplayParts = ({
5+
jsDocs = [],
6+
tagInfoName
7+
}: {
8+
jsDocs?: JSDocTagInfo[];
9+
tagInfoName: 'returns' | 'param' | 'see';
10+
}): SymbolDisplayPart[][] => {
11+
const tags = jsDocs.filter(({name}: JSDocTagInfo) => name === tagInfoName);
12+
const texts = tags.map(({text}) => text);
13+
14+
return texts.reduce<SymbolDisplayPart[][]>((acc, values) => {
15+
if (values === undefined) {
16+
return acc;
17+
}
18+
19+
return [...acc, values];
20+
}, []);
21+
};
22+
23+
export const jsDocsToReturnType = (jsDocs?: JSDocTagInfo[]): string => {
24+
const returns = jsDocsToSymbolDisplayParts({jsDocs, tagInfoName: 'returns'});
25+
return returns.map((parts) => parts.map(({text}) => text).join('')).join(' ');
26+
};
27+
28+
export const jsDocsToReferences = (jsDocs?: JSDocTagInfo[]): string[] => {
29+
const sees = jsDocsToSymbolDisplayParts({jsDocs, tagInfoName: 'see'});
30+
31+
return sees
32+
.map((texts) =>
33+
texts
34+
// Filter TypeScript unstripped comment asterix
35+
.filter(({text}) => text !== '*')
36+
.reduce((acc, {text}) => `${acc}${text}`, '')
37+
)
38+
.map((value) => value.trim());
39+
};
40+
41+
export const jsDocsToParams = (jsDocs?: JSDocTagInfo[]): Params[] => {
42+
const params = jsDocsToSymbolDisplayParts({jsDocs, tagInfoName: 'param'});
43+
44+
const toParam = (parts: SymbolDisplayPart[]): Params | undefined => {
45+
if (parts.find(({kind, text}) => kind === 'parameterName' && text !== '') === undefined) {
46+
return undefined;
47+
}
48+
49+
const name = parts.find(({kind}) => kind === 'parameterName')?.text ?? '';
50+
const documentation = parts.find(({kind}) => kind === 'text')?.text ?? '';
51+
52+
return {name, documentation};
53+
};
54+
55+
return params.map(toParam).filter((param) => param !== undefined) as Params[];
56+
};
57+
58+
export const jsDocsToExamples = (jsDocs: JSDocTagInfo[]): string[] => {
59+
const examples: JSDocTagInfo[] = jsDocs.filter(({name}: JSDocTagInfo) => name === 'example');
60+
const texts = examples
61+
.map(({text}) => text)
62+
.filter(Boolean)
63+
.flat(1) as SymbolDisplayPart[];
64+
return texts.map(({text}) => text).filter(Boolean);
65+
};

src/lib/parser/jdocs/parser.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// Example of inputs:
2+
// [
3+
// 'hello2',
4+
// 'https://daviddalbusco.com',
5+
// '{@link hello } – Another related function',
6+
// '{@link https://github.com/peterpeterparker/tsdoc-markdown Source code}'
7+
// ]
8+
export const inlineReferences = (references: string[]): string[] => {
9+
const inlineReference = (reference: string): string => {
10+
const linkMatch = /\{@link\s+([^\s}]+)\s*(?:\s+([^}]+))?\}/.exec(reference);
11+
12+
if (linkMatch !== null) {
13+
const [_, target, label] = linkMatch;
14+
15+
if (target.startsWith('http')) {
16+
return `* [${label ?? target}](${target})`;
17+
}
18+
19+
return `* \`${target.trim()}\`${label ? ` – ${label}` : ''}`;
20+
}
21+
22+
if (reference.startsWith('http')) {
23+
return `* [${reference}](${reference})`;
24+
}
25+
26+
return `* ${reference}`;
27+
};
28+
29+
return references.map(inlineReference);
30+
};

src/lib/markdown.ts renamed to src/lib/parser/markdown.ts

Lines changed: 10 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,19 @@
1-
import type {JSDocTagInfo, SymbolDisplayPart} from 'typescript';
1+
import type {JSDocTagInfo} from 'typescript';
22
import type {
33
DocEntry,
44
DocEntryConstructor,
55
MarkdownEmoji,
66
MarkdownHeadingLevel,
77
MarkdownOptions
8-
} from './types';
9-
10-
interface Params {
11-
name: string;
12-
documentation: string;
13-
}
14-
15-
type Row = Required<Pick<DocEntry, 'name' | 'type' | 'documentation'>> &
16-
Pick<DocEntry, 'url'> & {
17-
params: Params[];
18-
examples: string[];
19-
returnType?: string;
20-
references?: string[];
21-
};
8+
} from '../types';
9+
import {
10+
jsDocsToExamples,
11+
jsDocsToParams,
12+
jsDocsToReferences,
13+
jsDocsToReturnType
14+
} from './jdocs/mapper';
15+
import {inlineReferences} from './jdocs/parser';
16+
import type {Params, Row} from './types';
2217

2318
const toParams = (parameters?: DocEntry[]): Params[] =>
2419
(parameters ?? []).map(({name, documentation}: DocEntry) => ({
@@ -32,37 +27,6 @@ const inlineDocParam = (documentation: string | undefined): string =>
3227
const inlineParams = (params: Params[]): string[] =>
3328
params.map(({name, documentation}) => `* \`${name}\`${inlineDocParam(documentation)}`);
3429

35-
// Example of inputs:
36-
// [
37-
// 'hello2',
38-
// 'https://daviddalbusco.com',
39-
// '{@link hello } – Another related function',
40-
// '{@link https://github.com/peterpeterparker/tsdoc-markdown Source code}'
41-
// ]
42-
const inlineReferences = (references: string[]): string[] => {
43-
const inlineReference = (reference: string): string => {
44-
const linkMatch = /\{@link\s+([^\s}]+)\s*(?:\s+([^}]+))?\}/.exec(reference);
45-
46-
if (linkMatch !== null) {
47-
const [_, target, label] = linkMatch;
48-
49-
if (target.startsWith('http')) {
50-
return `* [${label ?? target}](${target})`;
51-
}
52-
53-
return `* \`${target.trim()}\`${label ? ` – ${label}` : ''}`;
54-
}
55-
56-
if (reference.startsWith('http')) {
57-
return `* [${reference}](${reference})`;
58-
}
59-
60-
return `* ${reference}`;
61-
};
62-
63-
return references.map(inlineReference);
64-
};
65-
6630
const reduceStatic = (values: DocEntry[]): [DocEntry[], DocEntry[]] =>
6731
values.reduce<[DocEntry[], DocEntry[]]>(
6832
([i, s], value) => [
@@ -241,69 +205,6 @@ const toMarkdown = ({
241205
headingLevel: MarkdownHeadingLevel | '####';
242206
docType: 'Constant' | 'Function' | 'Method' | 'Property' | 'Type' | 'Enum';
243207
} & Pick<MarkdownOptions, 'emoji'>): string => {
244-
const jsDocsToSymbolDisplayParts = ({
245-
jsDocs = [],
246-
tagInfoName
247-
}: {
248-
jsDocs?: JSDocTagInfo[];
249-
tagInfoName: 'returns' | 'param' | 'see';
250-
}): SymbolDisplayPart[][] => {
251-
const tags = jsDocs.filter(({name}: JSDocTagInfo) => name === tagInfoName);
252-
const texts = tags.map(({text}) => text);
253-
254-
return texts.reduce<SymbolDisplayPart[][]>((acc, values) => {
255-
if (values === undefined) {
256-
return acc;
257-
}
258-
259-
return [...acc, values];
260-
}, []);
261-
};
262-
263-
const jsDocsToReturnType = (jsDocs?: JSDocTagInfo[]): string => {
264-
const returns = jsDocsToSymbolDisplayParts({jsDocs, tagInfoName: 'returns'});
265-
return returns.map((parts) => parts.map(({text}) => text).join('')).join(' ');
266-
};
267-
268-
const jsDocsToReferences = (jsDocs?: JSDocTagInfo[]): string[] => {
269-
const sees = jsDocsToSymbolDisplayParts({jsDocs, tagInfoName: 'see'});
270-
271-
return sees
272-
.map((texts) =>
273-
texts
274-
// Filter TypeScript unstripped comment asterix
275-
.filter(({text}) => text !== '*')
276-
.reduce((acc, {text}) => `${acc}${text}`, '')
277-
)
278-
.map((value) => value.trim());
279-
};
280-
281-
const jsDocsToParams = (jsDocs?: JSDocTagInfo[]): Params[] => {
282-
const params = jsDocsToSymbolDisplayParts({jsDocs, tagInfoName: 'param'});
283-
284-
const toParam = (parts: SymbolDisplayPart[]): Params | undefined => {
285-
if (parts.find(({kind, text}) => kind === 'parameterName' && text !== '') === undefined) {
286-
return undefined;
287-
}
288-
289-
const name = parts.find(({kind}) => kind === 'parameterName')?.text ?? '';
290-
const documentation = parts.find(({kind}) => kind === 'text')?.text ?? '';
291-
292-
return {name, documentation};
293-
};
294-
295-
return params.map(toParam).filter((param) => param !== undefined) as Params[];
296-
};
297-
298-
const jsDocsToExamples = (jsDocs: JSDocTagInfo[]): string[] => {
299-
const examples: JSDocTagInfo[] = jsDocs.filter(({name}: JSDocTagInfo) => name === 'example');
300-
const texts = examples
301-
.map(({text}) => text)
302-
.filter(Boolean)
303-
.flat(1) as SymbolDisplayPart[];
304-
return texts.map(({text}) => text).filter(Boolean);
305-
};
306-
307208
const rows: Row[] = entries.map(
308209
({name, type, documentation, parameters, jsDocs, url}: DocEntry) => ({
309210
name,

src/lib/parser/types.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import type {DocEntry} from '../types';
2+
3+
export interface Params {
4+
name: string;
5+
documentation: string;
6+
}
7+
8+
export type Row = Required<Pick<DocEntry, 'name' | 'type' | 'documentation'>> &
9+
Pick<DocEntry, 'url'> & {
10+
params: Params[];
11+
examples: string[];
12+
returnType?: string;
13+
references?: string[];
14+
};

0 commit comments

Comments
 (0)