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

+14-12
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

+3-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/typedoc-plugin-remark/package.json

+3-1
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"
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

-59
This file was deleted.

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

+1-1
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

+25-13
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

-1
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

+1-1
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,
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+
}

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

+3-1
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,12 @@ export interface PluginOptions {
1313
/**
1414
* An array of remark plugin names to be executed.
1515
*/
16-
remarkPlugins: any;
16+
remarkPlugins: RemarkPlugin[];
1717

1818
/**
1919
* Custom options for the remark-stringify plugin.
2020
*/
2121
remarkStringifyOptions: Record<string, any>;
2222
}
23+
24+
export type RemarkPlugin = string | [string, Record<string, any>];

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

-1
This file was deleted.

packages/typedoc-plugin-remark/test/__scripts__/prepare.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,10 @@ fs.removeSync(`./test/out`);
1212
const fixtures = [
1313
{ options: 'typedoc.modules.json', outDir: 'modules' },
1414
{ options: 'typedoc.members.json', outDir: 'members' },
15+
{ options: 'typedoc.toc.json', outDir: 'toc' },
1516
{ options: 'typedoc.globals.json', outDir: 'globals' },
16-
{ options: 'typedoc.globals-notoc.json', outDir: 'globals-notoc' },
1717
{ options: 'typedoc.globals-mdx.json', outDir: 'globals-mdx' },
18+
{ options: 'typedoc.globals-notoc.json', outDir: 'globals-notoc' },
1819
];
1920

2021
// write fixtures
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/**
2+
* @module
3+
*/
4+
5+
export const someVariable = true;
6+
7+
export const someFunction = (a: string, b: number) => {
8+
return true;
9+
};
10+
11+
export class Class {
12+
a: string;
13+
b: number;
14+
}
15+
16+
export type SimpleTypeAlias = string | boolean;
17+
18+
export type TypeAliasWithTypeDeclaration = {
19+
a: string;
20+
b: number;
21+
};
22+
23+
export interface Interface {
24+
a: string;
25+
b: number;
26+
}
27+
28+
export enum Enum {
29+
A,
30+
B,
31+
}

packages/typedoc-plugin-remark/test/typedoc.base.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"entryPoints": ["./stubs/*.ts"],
2+
"entryPoints": ["./stubs/module-1.ts", "./stubs/module-2.ts"],
33
"tsconfig": "./stubs/tsconfig.json",
44
"out": "./out",
55
"plugin": [

packages/typedoc-plugin-remark/test/typedoc.members.json

-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
{
22
"extends": "./typedoc.base.json",
3-
"entryPoints": ["./stubs/*.ts"],
43
"remarkPlugins": ["remark-github", ["remark-toc", { "maxDepth": 3 }]],
54
"defaultRemarkPlugins": {
65
"mdx": false

0 commit comments

Comments
 (0)