Skip to content

Commit 58a27be

Browse files
committed
feat: export replaceMarkdownPlaceholders, parse returns a function
1 parent 3b76e1b commit 58a27be

12 files changed

+116
-43
lines changed

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ Below is an index of all the methods available.
2727
<!--DOCGEN_INDEX_START-->
2828
<!--DOCGEN_INDEX_END-->
2929
30-
## Custom Content
30+
## Custom Readme Content
3131
3232
Manage your readme content however you'd like, and on every docgen
3333
rebuild it will leave your original content as is, but update the

bin/docgen

100644100755
File mode changed.

src/generate.ts

+9-5
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,25 @@ import { parse } from './parse';
33
import { outputJson, outputReadme } from './output';
44

55
/**
6-
* Given the input files, will return generated results and optionally
7-
* write the data as a json file, readme, or both.
6+
* Given a tsconfig file path, or input files, will return generated
7+
* results and optionally write the data as a json file, readme, or both.
88
*/
99
export async function generate(opts: DocsGenerateOptions) {
10+
const apiFinder = parse(opts);
11+
12+
const data = apiFinder(opts.api);
13+
1014
const results: DocsGenerateResults = {
1115
...opts,
12-
data: await parse(opts),
16+
data,
1317
};
1418

1519
if (opts.outputJsonPath) {
16-
await outputJson(opts.outputJsonPath, results.data);
20+
await outputJson(opts.outputJsonPath, data);
1721
}
1822

1923
if (opts.outputReadmePath) {
20-
await outputReadme(opts.outputReadmePath, results.data);
24+
await outputReadme(opts.outputReadmePath, data);
2125
}
2226

2327
return results;

src/index.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
export { generate } from './generate';
2-
export { outputJson, outputReadme } from './output';
2+
export {
3+
outputJson,
4+
outputReadme,
5+
replaceMarkdownPlaceholders,
6+
} from './output';
37
export { parse } from './parse';
48
export { run } from './cli';
59
export * from './types';

src/output.ts

+31-6
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import fs from 'fs';
2+
import path from 'path';
23
import { promisify } from 'util';
34
import { MarkdownTable } from './markdown';
45
import type {
@@ -14,6 +15,12 @@ const readFile = promisify(fs.readFile);
1415
const writeFile = promisify(fs.writeFile);
1516

1617
export async function outputReadme(readmeFilePath: string, data: DocsData) {
18+
if (typeof readmeFilePath !== 'string') {
19+
throw new Error(`Missing readme file path`);
20+
}
21+
if (!path.isAbsolute(readmeFilePath)) {
22+
throw new Error(`Readme file path must be an absolute path`);
23+
}
1724
let content: string;
1825
try {
1926
content = await readFile(readmeFilePath, 'utf8');
@@ -23,16 +30,20 @@ export async function outputReadme(readmeFilePath: string, data: DocsData) {
2330
);
2431
}
2532

26-
content = replaceMarkdownDocs(content, data);
33+
content = replaceMarkdownPlaceholders(content, data);
2734
await writeFile(readmeFilePath, content);
2835
}
2936

30-
export function replaceMarkdownDocs(content: string, data: DocsData) {
31-
if (data?.api) {
32-
data = JSON.parse(JSON.stringify(data));
33-
content = replaceMarkdownDocsIndex(content, data);
34-
content = replaceMarkdownDocsApi(content, data);
37+
export function replaceMarkdownPlaceholders(content: string, data: DocsData) {
38+
if (typeof content !== 'string') {
39+
throw new Error(`Invalid content`);
40+
}
41+
if (data == null || data.api == null) {
42+
throw new Error(`Missing data`);
3543
}
44+
data = JSON.parse(JSON.stringify(data));
45+
content = replaceMarkdownDocsIndex(content, data);
46+
content = replaceMarkdownDocsApi(content, data);
3647
return content;
3748
}
3849

@@ -93,6 +104,11 @@ function markdownIndex(data: DocsData) {
93104
function markdownApi(data: DocsData) {
94105
const o: string[] = [];
95106

107+
if (typeof data.api?.docs === 'string' && data.api.docs.length > 0) {
108+
o.push(data.api.docs);
109+
o.push(``);
110+
}
111+
96112
o.push(`## API`);
97113
o.push(``);
98114

@@ -313,6 +329,15 @@ function getTagText(tags: DocsTagInfo[], tagName: string) {
313329
}
314330

315331
export async function outputJson(jsonFilePath: string, data: DocsData) {
332+
if (typeof jsonFilePath !== 'string') {
333+
throw new Error(`Missing json file path`);
334+
}
335+
if (!path.isAbsolute(jsonFilePath)) {
336+
throw new Error(`JSON file path must be an absolute path`);
337+
}
338+
if (data == null) {
339+
throw new Error(`Missing data`);
340+
}
316341
const content = JSON.stringify(data, null, 2);
317342
await writeFile(jsonFilePath, content);
318343
}

src/parse.ts

+19-11
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,14 @@ import type {
1313
import { getTsProgram } from './transpile';
1414
import GithubSlugger from 'github-slugger';
1515

16+
/**
17+
* Given either a tsconfig file path, or exact input files, will
18+
* use TypeScript to parse apart the source file's JSDoc comments
19+
* and returns a function which can be used to get a specific
20+
* interface as the primary api. Used by the generate() function.
21+
*/
1622
export function parse(opts: DocsParseOptions) {
17-
const tsProgram = getTsProgram(opts.tsconfigPath);
23+
const tsProgram = getTsProgram(opts);
1824
const typeChecker = tsProgram.getTypeChecker();
1925
const tsSourceFiles = tsProgram.getSourceFiles();
2026

@@ -25,19 +31,21 @@ export function parse(opts: DocsParseOptions) {
2531
parseSourceFile(tsSourceFile, typeChecker, interfaces, enums);
2632
});
2733

28-
const api = interfaces.find(i => i.name === opts.api) || null;
34+
return (api: string) => {
35+
const apiInterface = interfaces.find(i => i.name === api) || null;
2936

30-
const data: DocsData = {
31-
api,
32-
interfaces: [],
33-
enums: [],
34-
};
37+
const data: DocsData = {
38+
api: apiInterface,
39+
interfaces: [],
40+
enums: [],
41+
};
3542

36-
if (api) {
37-
collectInterfaces(data, api, interfaces, enums);
38-
}
43+
if (apiInterface) {
44+
collectInterfaces(data, apiInterface, interfaces, enums);
45+
}
3946

40-
return data;
47+
return data;
48+
};
4149
}
4250

4351
function collectInterfaces(

src/test/README.md

+8-1
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,21 @@ Below is an index of all the methods available.
1515
* [Enums](#enums)
1616
<!--DOCGEN_INDEX_END-->
1717

18-
## Custom Content
18+
## Custom Readme Content
1919

2020
Manage your readme content however you'd like, and on every docgen
2121
rebuild it will leave your original content as is, but update the
2222
HTML placeholder comments with the updated generated docs.
2323

2424
<!--DOCGEN_API_START-->
2525
<!--Update the source file JSDoc comments and rerun docgen to update the docs below-->
26+
## Docs from JSDoc comments!
27+
28+
This content is from the JSDOC comments on top of
29+
the `HapticsPlugin` interface. All the API data below
30+
is generated from comments from its methods, interfaces
31+
and enums.
32+
2633
## API
2734

2835
### impact

src/test/docs.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"api": {
33
"name": "HapticsPlugin",
44
"slug": "hapticsplugin",
5-
"docs": "Top level docs.",
5+
"docs": "## Docs from JSDoc comments!\n\nThis content is from the JSDOC comments on top of\nthe `HapticsPlugin` interface. All the API data below\nis generated from comments from its methods, interfaces\nand enums.",
66
"tags": [],
77
"methods": [
88
{

src/test/fixtures/definitions.ts

+6-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
/**
2-
* Top level docs.
2+
* ## Docs from JSDoc comments!
3+
*
4+
* This content is from the JSDOC comments on top of
5+
* the `HapticsPlugin` interface. All the API data below
6+
* is generated from comments from its methods, interfaces
7+
* and enums.
38
*/
49
export interface HapticsPlugin {
510
/**

src/test/parse.spec.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,12 @@ import { parse } from '../parse';
22
import path from 'path';
33

44
describe('parse', () => {
5-
const { api, interfaces, enums } = parse({
5+
const apiFinder = parse({
66
tsconfigPath: path.join(__dirname, 'fixtures', 'tsconfig.json'),
7-
api: 'HapticsPlugin',
87
});
98

9+
const { api, interfaces, enums } = apiFinder('HapticsPlugin');
10+
1011
it('api', () => {
1112
expect(api.name).toBe(`HapticsPlugin`);
1213
expect(api.slug).toBe(`hapticsplugin`);

src/transpile.ts

+30-12
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,41 @@
11
import ts from 'typescript';
22
import fs from 'fs';
33
import path from 'path';
4+
import { DocsParseOptions } from './types';
45

5-
export function getTsProgram(tsconfigPath: string) {
6-
const configResult = ts.readConfigFile(tsconfigPath, p =>
7-
fs.readFileSync(p, 'utf-8'),
8-
);
6+
export function getTsProgram(opts: DocsParseOptions) {
7+
let rootNames: string[];
8+
let options: ts.CompilerOptions;
99

10-
if (configResult.error) {
10+
if (typeof opts.tsconfigPath === 'string') {
11+
const configResult = ts.readConfigFile(opts.tsconfigPath, p =>
12+
fs.readFileSync(p, 'utf-8'),
13+
);
14+
15+
if (configResult.error) {
16+
throw new Error(
17+
`Unable to read tsconfig path: "${opts.tsconfigPath}". ` +
18+
ts.flattenDiagnosticMessageText(configResult.error.messageText, '\n'),
19+
);
20+
}
21+
const tsconfigDir = path.dirname(opts.tsconfigPath);
22+
rootNames = configResult.config.files.map((f: string) => {
23+
return path.join(tsconfigDir, f);
24+
});
25+
options = configResult.config.compilerOptions;
26+
} else if (Array.isArray(opts.inputFiles) && opts.inputFiles.length > 0) {
27+
opts.inputFiles.forEach(i => {
28+
if (!path.isAbsolute(i)) {
29+
throw new Error(`inputFile "${i}" must be absolute`);
30+
}
31+
});
32+
options = {};
33+
rootNames = [...opts.inputFiles];
34+
} else {
1135
throw new Error(
12-
`Unable to read tsconfig path: "${tsconfigPath}". ` +
13-
ts.flattenDiagnosticMessageText(configResult.error.messageText, '\n'),
36+
`Either "tsconfigPath" or "inputFiles" option must be provided`,
1437
);
1538
}
16-
const tsconfigDir = path.dirname(tsconfigPath);
17-
const rootNames = configResult.config.files.map((f: string) => {
18-
return path.join(tsconfigDir, f);
19-
});
20-
const options = configResult.config.compilerOptions;
2139

2240
// same defaults as transpile() for faster parse-only transpiling
2341
options.isolatedModules = true;

src/types.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -70,11 +70,12 @@ export interface DocsTagInfo {
7070
}
7171

7272
export interface DocsParseOptions {
73-
tsconfigPath: string;
74-
api: string;
73+
tsconfigPath?: string;
74+
inputFiles?: string[];
7575
}
7676

7777
export interface DocsGenerateOptions extends DocsParseOptions {
78+
api: string;
7879
outputJsonPath?: string;
7980
outputReadmePath?: string;
8081
}

0 commit comments

Comments
 (0)