Skip to content

Commit 31d39ee

Browse files
committed
feat(remark): refactor remark-toc implementation
1 parent fe0ef4e commit 31d39ee

File tree

18 files changed

+155
-97
lines changed

18 files changed

+155
-97
lines changed

devtools/packages/prebuild-options/tasks/generate-models.ts

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,7 @@ import * as path from 'path';
55
import * as prettier from 'prettier';
66
import { DeclarationOption, ParameterType } from 'typedoc';
77

8-
const ignoreTypes = [
9-
'textContentMappings',
10-
'remarkPlugins',
11-
'remarkStringifyOptions',
12-
];
8+
const ignoreTypes = ['textContentMappings', 'remarkStringifyOptions'];
139

1410
export async function generateOptionsModels(docsConfig: DocsConfig) {
1511
const optionsConfig = await import(docsConfig.declarationsPath as string);
@@ -34,7 +30,7 @@ async function writeTypeDocDeclarations(
3430
(option as any).type === ParameterType.Mixed &&
3531
(option as any).defaultValue,
3632
)
37-
.map(([name, option]) => capitalize(name));
33+
.map(([name, option]) => capitalize(name, false));
3834

3935
const out: string[] = [];
4036

@@ -126,17 +122,20 @@ ${name}: ${getType(name, option, true)};`,
126122
?.filter(([name]) => !ignoreTypes.includes(name))
127123
.map(([name, option]) => {
128124
return `
129-
/**
130-
* ${getComments(name)}
131-
*/
132-
export interface ${capitalize(name)} {
125+
${
126+
// this is a hack need to fix properly
127+
name === 'remarkPlugins'
128+
? 'export type RemarkPlugin = string | [string, Record<string, any>];'
129+
: `export interface ${capitalize(name)} {
133130
${Object.entries(option.defaultValue as any)
134131
.map(
135132
([key, value]) =>
136133
`'${key}'${value === undefined ? '?' : ''}: ${getValueType(key, value)}`,
137134
)
138135
.join(';')}
139-
}
136+
}`
137+
}
138+
140139
`;
141140
})
142141
.join('\n')}
@@ -255,6 +254,9 @@ function getObjectType(name: string) {
255254
return 'string';
256255
}
257256

258-
function capitalize(str: string) {
257+
function capitalize(str: string, includeArray = true) {
258+
if (str.endsWith('s')) {
259+
str = `${str.slice(0, -1)}${includeArray ? '[]' : ''}`;
260+
}
259261
return str.charAt(0).toUpperCase() + str.slice(1);
260262
}

package-lock.json

Lines changed: 3 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/typedoc-plugin-remark/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
"prepublishOnly": "npm run lint && npm run build",
1515
"prebuild": "rm -rf dist && prebuild-options",
1616
"build": "tsc",
17+
"build-and-run": "npm run build && npm run pretest",
1718
"pretest": "tsx ./test/__scripts__/prepare.ts",
1819
"test": "jest",
1920
"test:update": "npm run build && npm run test -- -u"
@@ -31,7 +32,8 @@
3132
"remark-frontmatter": "^5.0.0",
3233
"remark-gfm": "^4.0.0",
3334
"remark-mdx": "^3.1.0",
34-
"to-vfile": "^8.0.0"
35+
"to-vfile": "^8.0.0",
36+
"unist-util-visit": "^5.0.0"
3537
},
3638
"peerDependencies": {
3739
"typedoc-plugin-markdown": ">=4.3.0"
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
// THIS FILE IS AUTO GENERATED FROM THE OPTIONS CONFIG. DO NOT EDIT DIRECTLY.
22
import { ManuallyValidatedOption } from 'typedoc';
3+
import { RemarkPlugin } from './types/options.js';
34
declare module 'typedoc' {
45
export interface TypeDocOptionMap {
56
defaultRemarkPlugins: { gfm: boolean; frontmatter: boolean; mdx: boolean };
6-
remarkPlugins: ManuallyValidatedOption<Partial<any>>;
7+
remarkPlugins: ManuallyValidatedOption<RemarkPlugin[]>;
78
remarkStringifyOptions: ManuallyValidatedOption<Record<string, any>>;
89
}
910
}

packages/typedoc-plugin-remark/src/helpers/add-toc.ts

Lines changed: 0 additions & 59 deletions
This file was deleted.

packages/typedoc-plugin-remark/src/helpers/get-default-plugins.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { RemarkPlugin } from '../types/remark-plugin.js';
1+
import { RemarkPlugin } from '../types/options.js';
22

33
export function getDefaultPlugins(defaultPluginsFlag: {
44
gfm: boolean;

packages/typedoc-plugin-remark/src/index.ts

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,16 @@
44
* @module
55
*/
66

7+
import * as path from 'path';
78
import { Application, DeclarationOption, RendererEvent } from 'typedoc';
8-
import { MarkdownPageEvent } from 'typedoc-plugin-markdown';
9-
import { addTableOfContents } from './helpers/add-toc.js';
9+
import { fileURLToPath } from 'url';
1010
import { getDefaultPlugins } from './helpers/get-default-plugins.js';
1111
import * as options from './options/declarations.js';
1212
import { parse } from './parse.js';
1313

14+
const __filename = fileURLToPath(import.meta.url);
15+
const __dirname = path.dirname(__filename);
16+
1417
export function load(app: Application) {
1518
Object.entries(options).forEach(([name, option]) => {
1619
app.options.addDeclaration({
@@ -19,28 +22,37 @@ export function load(app: Application) {
1922
} as DeclarationOption);
2023
});
2124

22-
app.renderer.on(MarkdownPageEvent.END, (event: MarkdownPageEvent) => {
23-
const remarkPlugins = app.options.getValue('remarkPlugins') as [];
24-
const remarkPluginsNames = remarkPlugins.map((plugin) =>
25-
Array.isArray(plugin) ? plugin[0] : plugin,
26-
) as string[];
27-
28-
if (remarkPluginsNames.includes('remark-toc')) {
29-
addTableOfContents(event, remarkPlugins, remarkPluginsNames, app);
30-
}
31-
});
32-
3325
app.renderer.postRenderAsyncJobs.push(async (output: RendererEvent) => {
26+
const remarkPlugins = app.options.getValue('remarkPlugins') as [];
3427
const defaultPlugins = getDefaultPlugins(
3528
app.options.getValue('defaultRemarkPlugins'),
3629
);
3730
const userPlugins = app.options.getValue('remarkPlugins') as string[];
3831
const remarkStringifyOptions = app.options.getValue(
3932
'remarkStringifyOptions',
4033
);
34+
const remarkPluginsNames = remarkPlugins.map((plugin) =>
35+
Array.isArray(plugin) ? plugin[0] : plugin,
36+
) as string[];
37+
4138
if (output.urls?.length) {
4239
await Promise.all(
4340
output.urls?.map(async (urlMapping) => {
41+
if (remarkPluginsNames.includes('remark-toc')) {
42+
const tocPluginIndex = remarkPluginsNames.findIndex(
43+
(name) => name === 'remark-toc',
44+
);
45+
const tocPlugin = remarkPlugins[tocPluginIndex];
46+
const tocOptions = Array.isArray(tocPlugin) ? tocPlugin[1] : {};
47+
defaultPlugins.push([
48+
path.join(__dirname, 'plugins', 'add-toc.js'),
49+
{
50+
reflection: urlMapping.model,
51+
typedocOptions: app.options,
52+
tocOptions,
53+
},
54+
]);
55+
}
4456
const filePath = `${output.outputDirectory}/${urlMapping.url}`;
4557
return await parse(
4658
filePath,

packages/typedoc-plugin-remark/src/options/index.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,4 @@
44
* @module
55
*/
66

7-
export * as helpers from '../helpers/add-toc.js';
87
export * as declarations from './declarations.js';

packages/typedoc-plugin-remark/src/parse.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import * as path from 'path';
22
import { remark } from 'remark';
33
import { read, writeSync } from 'to-vfile';
4-
import { RemarkPlugin } from './types/remark-plugin.js';
4+
import { RemarkPlugin } from './types/options.js';
55

66
export async function parse(
77
filePath: string,
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import { Heading } from 'mdast';
2+
import { DeclarationReflection, Options, ReflectionKind } from 'typedoc';
3+
import { Node } from 'unist';
4+
import { visit } from 'unist-util-visit';
5+
6+
/**
7+
* This plugin is used internally to add a toc heading as required to be present in the page when using the remark-toc plugin.
8+
*/
9+
export default function (options: any) {
10+
const { reflection, typedocOptions, tocOptions } = options;
11+
12+
return (tree: Node) => {
13+
// If the current page is already an index page, do nothing.
14+
if (isIndexPage(reflection, typedocOptions)) {
15+
return tree;
16+
}
17+
visit(
18+
tree,
19+
'heading',
20+
(node: Heading, index, parent: { children: Node[] }) => {
21+
if (node.depth === 2) {
22+
const newHeading: Heading = {
23+
type: 'heading',
24+
depth: 2,
25+
children: [
26+
{
27+
type: 'text',
28+
value: tocOptions?.heading || 'Contents',
29+
},
30+
],
31+
};
32+
parent?.children.splice(index, 0, newHeading);
33+
return false;
34+
}
35+
},
36+
);
37+
};
38+
}
39+
40+
/**
41+
* Determine if the current page is already an index page.
42+
* - Reflection is a project and all children are modules.
43+
* - Reflection is a module and outputFileStrategy is equal to "members".
44+
*/
45+
function isIndexPage(
46+
reflection: DeclarationReflection,
47+
typedocOptions: Options,
48+
) {
49+
if (
50+
reflection.kind === ReflectionKind.Project &&
51+
reflection.children?.every((child) => child.kind === ReflectionKind.Module)
52+
) {
53+
return true;
54+
}
55+
if (
56+
[ReflectionKind.Project, ReflectionKind.Module].includes(reflection.kind) &&
57+
typedocOptions?.getValue('outputFileStrategy') === 'members'
58+
) {
59+
return true;
60+
}
61+
return false;
62+
}

0 commit comments

Comments
 (0)