@textmode/tooling-scripts 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +40 -0
- package/policy.json +29 -0
- package/src/cli/check-deps.mjs +61 -0
- package/src/cli/install-husky.mjs +23 -0
- package/src/config/create-config.mjs +75 -0
- package/src/husky/commit-msg +1 -0
- package/src/husky/pre-commit +1 -0
- package/src/husky/pre-push +6 -0
- package/src/scripts/add-api-doc-links.mjs +92 -0
- package/src/scripts/check-examples.mjs +81 -0
- package/src/scripts/check-public-api-docs.mjs +128 -0
- package/src/scripts/lib/api-doc-checks.mjs +147 -0
- package/src/scripts/lib/api-doc-links-config.mjs +32 -0
- package/src/scripts/lib/api-doc-routes.mjs +118 -0
- package/src/scripts/lib/example-checks.mjs +389 -0
- package/src/scripts/lib/jsdoc-comments.mjs +78 -0
- package/src/scripts/lib/reflection-policy.mjs +106 -0
- package/src/scripts/lib/reporting.mjs +23 -0
- package/src/scripts/lib/typedoc-project.mjs +25 -0
- package/src/typedoc-plugins/accessor-docs-normalizer/README.md +45 -0
- package/src/typedoc-plugins/accessor-docs-normalizer/accessor-comments.js +53 -0
- package/src/typedoc-plugins/accessor-docs-normalizer/index.js +32 -0
- package/src/typedoc-plugins/accessor-docs-normalizer/render-accessor.js +144 -0
- package/src/typedoc-plugins/accessor-docs-normalizer/theme-patch.js +28 -0
- package/src/typedoc-plugins/all-member-pages-router/README.md +57 -0
- package/src/typedoc-plugins/all-member-pages-router/child-pages.js +65 -0
- package/src/typedoc-plugins/all-member-pages-router/constants.js +58 -0
- package/src/typedoc-plugins/all-member-pages-router/index.js +28 -0
- package/src/typedoc-plugins/all-member-pages-router/member-paths.js +48 -0
- package/src/typedoc-plugins/all-member-pages-router/member-reflections.js +164 -0
- package/src/typedoc-plugins/all-member-pages-router/namespace-paths.js +44 -0
- package/src/typedoc-plugins/all-member-pages-router/router.js +58 -0
- package/src/typedoc-plugins/api-frontmatter/README.md +66 -0
- package/src/typedoc-plugins/api-frontmatter/constants.js +52 -0
- package/src/typedoc-plugins/api-frontmatter/descriptions.js +80 -0
- package/src/typedoc-plugins/api-frontmatter/frontmatter.js +56 -0
- package/src/typedoc-plugins/api-frontmatter/index.js +38 -0
- package/src/typedoc-plugins/api-frontmatter/library-context.js +62 -0
- package/src/typedoc-plugins/api-frontmatter/reflection-metadata.js +95 -0
- package/src/typedoc-plugins/include-code-examples/README.md +40 -0
- package/src/typedoc-plugins/include-code-examples/code-fences.js +55 -0
- package/src/typedoc-plugins/include-code-examples/example-metadata.js +41 -0
- package/src/typedoc-plugins/include-code-examples/index.js +23 -0
- package/src/typedoc-plugins/include-code-examples/transform-markdown.js +33 -0
- package/src/typedoc-plugins/strip-api-self-links/index.js +126 -0
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
import { readFileSync } from 'fs';
|
|
4
|
+
import { dirname, resolve } from 'path';
|
|
5
|
+
import { fileURLToPath } from 'url';
|
|
6
|
+
|
|
7
|
+
import { TEXTMODE_ECOSYSTEM_NAME, TEXTMODE_PACKAGE_PREFIX, UNKNOWN_LIBRARY_NAME } from './constants.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Library context extracted from package.json and the TypeDoc project.
|
|
11
|
+
*
|
|
12
|
+
* @typedef {Object} LibraryContext
|
|
13
|
+
* @property {string} name
|
|
14
|
+
* @property {string} description
|
|
15
|
+
* @property {string} ecosystem
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Read and parse package.json from the project root.
|
|
20
|
+
*
|
|
21
|
+
* @returns {{ name?: string; description?: string } | null}
|
|
22
|
+
*/
|
|
23
|
+
export function readPackageJson() {
|
|
24
|
+
try {
|
|
25
|
+
const directory = dirname(fileURLToPath(import.meta.url));
|
|
26
|
+
const packagePath = resolve(directory, '..', '..', 'package.json');
|
|
27
|
+
const content = readFileSync(packagePath, 'utf-8');
|
|
28
|
+
return JSON.parse(content);
|
|
29
|
+
} catch {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Get the ecosystem name for a package.
|
|
36
|
+
*
|
|
37
|
+
* @param {string} packageName
|
|
38
|
+
* @returns {string}
|
|
39
|
+
*/
|
|
40
|
+
export function getEcosystemName(packageName) {
|
|
41
|
+
return packageName.startsWith(TEXTMODE_PACKAGE_PREFIX) ? TEXTMODE_ECOSYSTEM_NAME : packageName;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Extract library context from TypeDoc project metadata and package.json.
|
|
46
|
+
*
|
|
47
|
+
* @param {import('typedoc').ProjectReflection} project
|
|
48
|
+
* @returns {LibraryContext}
|
|
49
|
+
*/
|
|
50
|
+
export function extractLibraryContext(project) {
|
|
51
|
+
const name = project.packageName ?? project.name ?? UNKNOWN_LIBRARY_NAME;
|
|
52
|
+
|
|
53
|
+
let description = `API reference for ${name}`;
|
|
54
|
+
const packageJson = readPackageJson();
|
|
55
|
+
if (packageJson?.description) {
|
|
56
|
+
description = packageJson.description;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const ecosystem = getEcosystemName(name);
|
|
60
|
+
|
|
61
|
+
return { name, description, ecosystem };
|
|
62
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
import { ReflectionKind } from 'typedoc';
|
|
4
|
+
|
|
5
|
+
import { API_REFERENCE_CATEGORY, CATEGORY_BY_KIND } from './constants.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Get the frontmatter category for a reflection kind.
|
|
9
|
+
*
|
|
10
|
+
* @param {import('typedoc').ReflectionKind} kind
|
|
11
|
+
* @returns {string}
|
|
12
|
+
*/
|
|
13
|
+
export function getCategoryForKind(kind) {
|
|
14
|
+
return CATEGORY_BY_KIND.get(kind) ?? API_REFERENCE_CATEGORY;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Check if a reflection belongs to a namespace.
|
|
19
|
+
*
|
|
20
|
+
* @param {import('typedoc').Reflection} model
|
|
21
|
+
* @returns {boolean}
|
|
22
|
+
*/
|
|
23
|
+
export function isInNamespace(model) {
|
|
24
|
+
let parent = model.parent;
|
|
25
|
+
while (parent) {
|
|
26
|
+
if (parent.kind === ReflectionKind.Namespace) {
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
parent = parent.parent;
|
|
30
|
+
}
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Get the full namespace path for a reflection.
|
|
36
|
+
*
|
|
37
|
+
* @param {import('typedoc').Reflection} model
|
|
38
|
+
* @returns {string | undefined}
|
|
39
|
+
*/
|
|
40
|
+
export function getNamespacePath(model) {
|
|
41
|
+
/** @type {string[]} */
|
|
42
|
+
const parts = [];
|
|
43
|
+
let parent = model.parent;
|
|
44
|
+
|
|
45
|
+
while (parent) {
|
|
46
|
+
if (parent.kind === ReflectionKind.Namespace) {
|
|
47
|
+
parts.unshift(parent.name);
|
|
48
|
+
}
|
|
49
|
+
parent = parent.parent;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return parts.length > 0 ? parts.join('.') : undefined;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Get the owning class or interface name for member reflections.
|
|
57
|
+
*
|
|
58
|
+
* @param {import('typedoc').Reflection} model
|
|
59
|
+
* @returns {string | undefined}
|
|
60
|
+
*/
|
|
61
|
+
export function getOwnerName(model) {
|
|
62
|
+
let parent = model.parent;
|
|
63
|
+
while (parent) {
|
|
64
|
+
if (parent.kind === ReflectionKind.Class || parent.kind === ReflectionKind.Interface) {
|
|
65
|
+
return parent.name;
|
|
66
|
+
}
|
|
67
|
+
parent = parent.parent;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return undefined;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Check if a class reflection has a constructor child.
|
|
75
|
+
*
|
|
76
|
+
* @param {import('typedoc').Reflection} model
|
|
77
|
+
* @returns {boolean | undefined}
|
|
78
|
+
*/
|
|
79
|
+
export function getHasConstructor(model) {
|
|
80
|
+
if (model.kind !== ReflectionKind.Class || !('children' in model)) {
|
|
81
|
+
return undefined;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return model.children?.some((child) => child.kind === ReflectionKind.Constructor);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Check if a reflection is an interface.
|
|
89
|
+
*
|
|
90
|
+
* @param {import('typedoc').Reflection} model
|
|
91
|
+
* @returns {true | undefined}
|
|
92
|
+
*/
|
|
93
|
+
export function getIsInterface(model) {
|
|
94
|
+
return model.kind === ReflectionKind.Interface ? true : undefined;
|
|
95
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Include Code Examples
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
|
|
5
|
+
This TypeDoc plugin cleans up example sketches included in API docs with `{@includeCode ...}`.
|
|
6
|
+
|
|
7
|
+
Example sketches keep leading gallery metadata such as `@title` and `@author` so the examples gallery and validation
|
|
8
|
+
scripts can read it. API reference pages should show only the runnable sketch code, so this plugin removes a leading
|
|
9
|
+
JSDoc metadata block from supported fenced code blocks after TypeDoc has rendered markdown.
|
|
10
|
+
|
|
11
|
+
The plugin also normalizes transformed `js` fences to `javascript` for VitePress syntax highlighting. That normalization
|
|
12
|
+
is intentionally gated by metadata stripping: a plain `js` fence without a leading metadata block is left unchanged.
|
|
13
|
+
|
|
14
|
+
This is a post-render markdown transform. It does not edit source examples, change TypeDoc reflections, or add new
|
|
15
|
+
TypeDoc options.
|
|
16
|
+
|
|
17
|
+
## Preserved Output Contract
|
|
18
|
+
|
|
19
|
+
- Only fenced `js`, `jsx`, `ts`, `tsx`, `javascript`, and `typescript` blocks are considered.
|
|
20
|
+
- Only a leading JSDoc metadata block is stripped.
|
|
21
|
+
- Plain code fences without stripped metadata are left byte-for-byte unchanged.
|
|
22
|
+
- `js` fences are normalized to `javascript` only when metadata was stripped.
|
|
23
|
+
- Unsupported and language-less fences are left unchanged.
|
|
24
|
+
|
|
25
|
+
## Contributor Notes
|
|
26
|
+
|
|
27
|
+
- Safe to edit: helper names, supported-language tests, and comments.
|
|
28
|
+
- Be careful with: `EXAMPLE_CODE_FENCE_PATTERN`, because it intentionally operates after TypeDoc renders markdown.
|
|
29
|
+
- Do not casually change: supported language gating, `js` normalization timing, or the “leading docblock only” rule.
|
|
30
|
+
|
|
31
|
+
## Maintenance
|
|
32
|
+
|
|
33
|
+
This plugin depends on TypeDoc rendering `{@includeCode ...}` examples as fenced code blocks before
|
|
34
|
+
`MarkdownPageEvent.END`. Re-check the transform after upgrading TypeDoc or `typedoc-plugin-markdown`.
|
|
35
|
+
|
|
36
|
+
Upgrade checklist:
|
|
37
|
+
|
|
38
|
+
- Confirm included examples still arrive in `page.contents` as fenced markdown.
|
|
39
|
+
- Run `npm run test:tooling`.
|
|
40
|
+
- Inspect a generated API example and verify gallery metadata such as `@title` is absent.
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
export const EXAMPLE_CODE_FENCE_PATTERN = /```([^\n]*)\n([\s\S]*?)```/g;
|
|
4
|
+
|
|
5
|
+
export const SUPPORTED_INCLUDED_EXAMPLE_LANGUAGES = new Set(['js', 'jsx', 'ts', 'tsx', 'javascript', 'typescript']);
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Get the language token from a fenced code block info string.
|
|
9
|
+
*
|
|
10
|
+
* @param {string} infoString
|
|
11
|
+
* @returns {string}
|
|
12
|
+
*/
|
|
13
|
+
export function getFenceLanguage(infoString) {
|
|
14
|
+
return infoString.trim().split(/\s+/, 1)[0]?.toLowerCase() ?? '';
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Check whether the language can contain an included sketch.
|
|
19
|
+
*
|
|
20
|
+
* @param {string} language
|
|
21
|
+
* @returns {boolean}
|
|
22
|
+
*/
|
|
23
|
+
export function isSupportedExampleLanguage(language) {
|
|
24
|
+
return SUPPORTED_INCLUDED_EXAMPLE_LANGUAGES.has(language);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Normalize the fence language for transformed included examples.
|
|
29
|
+
*
|
|
30
|
+
* @param {string} infoString
|
|
31
|
+
* @returns {string}
|
|
32
|
+
*/
|
|
33
|
+
export function normalizeIncludedExampleFenceInfo(infoString) {
|
|
34
|
+
const trimmedInfoString = infoString.trim();
|
|
35
|
+
if (!trimmedInfoString) {
|
|
36
|
+
return infoString;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const parts = trimmedInfoString.split(/\s+/);
|
|
40
|
+
if (parts[0].toLowerCase() === 'js') {
|
|
41
|
+
parts[0] = 'javascript';
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return parts.join(' ');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Ensure transformed code ends with one newline before the closing fence.
|
|
49
|
+
*
|
|
50
|
+
* @param {string} code
|
|
51
|
+
* @returns {string}
|
|
52
|
+
*/
|
|
53
|
+
export function ensureTrailingNewline(code) {
|
|
54
|
+
return code.endsWith('\n') ? code : `${code}\n`;
|
|
55
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
const LEADING_EXAMPLE_METADATA_PATTERN = /^\s*\/\*\*([\s\S]*?)\*\/\s*\n?/;
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Strip the leading gallery metadata docblock from an included example.
|
|
7
|
+
*
|
|
8
|
+
* @param {string} source
|
|
9
|
+
* @returns {string}
|
|
10
|
+
*/
|
|
11
|
+
export function stripLeadingExampleMetadataDocblock(source) {
|
|
12
|
+
const match = source.match(LEADING_EXAMPLE_METADATA_PATTERN);
|
|
13
|
+
if (!match) {
|
|
14
|
+
return source;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return source.slice(match[0].length).replace(/^\n+/, '');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Backwards-compatible helper name retained for copied add-on plugin stacks.
|
|
22
|
+
*
|
|
23
|
+
* @param {string} source
|
|
24
|
+
* @returns {string}
|
|
25
|
+
*/
|
|
26
|
+
export function stripLeadingExampleMetadata(source) {
|
|
27
|
+
return stripLeadingExampleMetadataDocblock(source);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Preserve the old helper shape while keeping metadata parsing intentionally minimal.
|
|
32
|
+
*
|
|
33
|
+
* @param {string} source
|
|
34
|
+
* @returns {{ metadata: null; code: string }}
|
|
35
|
+
*/
|
|
36
|
+
export function extractExampleMetadata(source) {
|
|
37
|
+
return {
|
|
38
|
+
metadata: null,
|
|
39
|
+
code: stripLeadingExampleMetadataDocblock(source),
|
|
40
|
+
};
|
|
41
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
import { MarkdownPageEvent } from 'typedoc-plugin-markdown';
|
|
4
|
+
|
|
5
|
+
import { transformExampleCodeBlocks } from './transform-markdown.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* TypeDoc plugin load function.
|
|
9
|
+
*
|
|
10
|
+
* @param {import('typedoc-plugin-markdown').MarkdownApplication} app
|
|
11
|
+
* @returns {void}
|
|
12
|
+
*/
|
|
13
|
+
export function load(app) {
|
|
14
|
+
app.renderer.on(MarkdownPageEvent.END, (page) => {
|
|
15
|
+
if (!page.contents) {
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
page.contents = transformExampleCodeBlocks(page.contents);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
app.logger.verbose('[typedoc] Registered includeCode example normalizer');
|
|
23
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
ensureTrailingNewline,
|
|
5
|
+
EXAMPLE_CODE_FENCE_PATTERN,
|
|
6
|
+
getFenceLanguage,
|
|
7
|
+
isSupportedExampleLanguage,
|
|
8
|
+
normalizeIncludedExampleFenceInfo,
|
|
9
|
+
} from './code-fences.js';
|
|
10
|
+
import { stripLeadingExampleMetadataDocblock } from './example-metadata.js';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Remove gallery metadata from included example code blocks.
|
|
14
|
+
*
|
|
15
|
+
* @param {string} markdown
|
|
16
|
+
* @returns {string}
|
|
17
|
+
*/
|
|
18
|
+
export function transformExampleCodeBlocks(markdown) {
|
|
19
|
+
return markdown.replace(EXAMPLE_CODE_FENCE_PATTERN, (fullMatch, infoString, code) => {
|
|
20
|
+
const language = getFenceLanguage(infoString);
|
|
21
|
+
if (!isSupportedExampleLanguage(language)) {
|
|
22
|
+
return fullMatch;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const strippedCode = stripLeadingExampleMetadataDocblock(code);
|
|
26
|
+
if (strippedCode === code) {
|
|
27
|
+
return fullMatch;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const normalizedInfoString = normalizeIncludedExampleFenceInfo(infoString);
|
|
31
|
+
return `\`\`\`${normalizedInfoString}\n${ensureTrailingNewline(strippedCode)}\`\`\``;
|
|
32
|
+
});
|
|
33
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
import { Converter, ParameterType, ReflectionKind } from 'typedoc';
|
|
4
|
+
import { MarkdownPageEvent } from 'typedoc-plugin-markdown';
|
|
5
|
+
|
|
6
|
+
let apiBaseUrl = 'https://code.textmode.art/api/textmode.js';
|
|
7
|
+
let stripMode = 'full';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Check whether a comment display part links to the hosted textmode.js API.
|
|
11
|
+
*
|
|
12
|
+
* @param {import('typedoc').CommentDisplayPart} part
|
|
13
|
+
* @returns {boolean}
|
|
14
|
+
*/
|
|
15
|
+
function isHostedApiLink(part) {
|
|
16
|
+
return part.kind === 'inline-tag' && part.tag === '@link' && String(part.target ?? '').startsWith(apiBaseUrl);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Remove `@see` tags that point back to the generated API site itself.
|
|
21
|
+
*
|
|
22
|
+
* These links are useful in emitted declaration hover docs, but they are redundant
|
|
23
|
+
* inside the TypeDoc API pages hosted at the same URLs.
|
|
24
|
+
*
|
|
25
|
+
* @param {import('typedoc').Comment | undefined} comment
|
|
26
|
+
* @returns {void}
|
|
27
|
+
*/
|
|
28
|
+
function stripHostedApiSeeTags(comment) {
|
|
29
|
+
if (!comment) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
comment.blockTags = comment.blockTags.filter(
|
|
34
|
+
(tag) => tag.tag !== '@see' || !tag.content.some((part) => isHostedApiLink(part))
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* @param {import('typedoc').Reflection} reflection
|
|
40
|
+
* @returns {void}
|
|
41
|
+
*/
|
|
42
|
+
function stripReflectionHostedApiSeeTags(reflection) {
|
|
43
|
+
stripHostedApiSeeTags(reflection.comment);
|
|
44
|
+
|
|
45
|
+
if (!reflection.isDeclaration()) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
for (const signature of reflection.signatures ?? []) {
|
|
50
|
+
stripHostedApiSeeTags(signature.comment);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
stripHostedApiSeeTags(reflection.getSignature?.comment);
|
|
54
|
+
stripHostedApiSeeTags(reflection.setSignature?.comment);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Remove generated Markdown links back to the same hosted API reference.
|
|
59
|
+
*
|
|
60
|
+
* @param {string} contents
|
|
61
|
+
* @returns {string}
|
|
62
|
+
*/
|
|
63
|
+
function stripHostedApiMarkdownLinks(contents) {
|
|
64
|
+
const escapedBaseUrl = apiBaseUrl.replaceAll('.', '\\.');
|
|
65
|
+
const hostedApiLinkPattern = `\\[[^\\]]+ API reference\\]\\(${escapedBaseUrl}[^)]*\\)`;
|
|
66
|
+
const standaloneSeeSection = new RegExp(`\\n#{2,6} See\\n\\n${hostedApiLinkPattern}\\n`, 'g');
|
|
67
|
+
const inlineSeeLink = new RegExp(`\\s*\\*\\*See\\*\\*\\s*${hostedApiLinkPattern}`, 'g');
|
|
68
|
+
const standaloneLinkLine =
|
|
69
|
+
stripMode === 'minimal'
|
|
70
|
+
? new RegExp(`^${hostedApiLinkPattern}\\n`, 'gm')
|
|
71
|
+
: new RegExp(`^\\s*${hostedApiLinkPattern}\\n?`, 'gm');
|
|
72
|
+
|
|
73
|
+
let nextContents = contents.replace(standaloneSeeSection, '\n').replace(inlineSeeLink, '').replace(standaloneLinkLine, '');
|
|
74
|
+
|
|
75
|
+
if (stripMode === 'full') {
|
|
76
|
+
const listItemLinkLine = new RegExp(`^\\s*-\\s*${hostedApiLinkPattern}\\n?`, 'gm');
|
|
77
|
+
const emptySeeSection = /\n#{2,6} See\n(?:\s*\n)+(?=#{1,6}\s|\n*$)/g;
|
|
78
|
+
nextContents = nextContents.replace(listItemLinkLine, '').replace(emptySeeSection, '\n');
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return nextContents;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* TypeDoc plugin load function.
|
|
86
|
+
*
|
|
87
|
+
* @param {import('typedoc').Application} app
|
|
88
|
+
* @returns {void}
|
|
89
|
+
*/
|
|
90
|
+
export function load(app) {
|
|
91
|
+
app.options.addDeclaration({
|
|
92
|
+
name: 'textmodeApiBaseUrl',
|
|
93
|
+
help: 'Base URL of the hosted textmode API for self-link stripping.',
|
|
94
|
+
type: ParameterType.String,
|
|
95
|
+
defaultValue: apiBaseUrl,
|
|
96
|
+
});
|
|
97
|
+
app.options.addDeclaration({
|
|
98
|
+
name: 'textmodeStripApiSelfLinksMode',
|
|
99
|
+
help: 'Hosted API self-link Markdown stripping mode.',
|
|
100
|
+
type: ParameterType.String,
|
|
101
|
+
defaultValue: stripMode,
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
app.converter.on(Converter.EVENT_BEGIN, () => {
|
|
105
|
+
apiBaseUrl = app.options.getValue('textmodeApiBaseUrl');
|
|
106
|
+
stripMode = app.options.getValue('textmodeStripApiSelfLinksMode');
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
app.converter.on(Converter.EVENT_RESOLVE_END, (context) => {
|
|
110
|
+
const reflections = context.project.getReflectionsByKind(ReflectionKind.All);
|
|
111
|
+
|
|
112
|
+
for (const reflection of reflections) {
|
|
113
|
+
stripReflectionHostedApiSeeTags(reflection);
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
app.renderer.on(MarkdownPageEvent.END, (page) => {
|
|
118
|
+
if (!page.contents) {
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
page.contents = stripHostedApiMarkdownLinks(page.contents);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
app.logger.verbose('[typedoc] Registered hosted API self-link stripper');
|
|
126
|
+
}
|