rspress-plugin-api-extractor 0.1.2 → 0.2.1
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/README.md +2 -2
- package/api-extracted-package.js +426 -0
- package/build-program.js +120 -0
- package/build-stages.js +664 -0
- package/category-resolver.js +50 -0
- package/code-post-processor.js +38 -0
- package/config-helpers.js +106 -0
- package/config-utils.js +258 -0
- package/content-hash.js +79 -0
- package/errors.js +29 -0
- package/formatter.js +69 -0
- package/hide-cut-transformer.js +96 -0
- package/index.d.ts +901 -831
- package/index.js +4 -6128
- package/layers/ConfigServiceLive.js +377 -0
- package/layers/ObservabilityLive.js +136 -0
- package/layers/PathDerivationServiceLive.js +16 -0
- package/layers/SnapshotServiceLive.js +94 -0
- package/layers/TypeRegistryServiceLive.js +46 -0
- package/llms-processing.js +270 -0
- package/llms-program.js +262 -0
- package/loader.js +186 -0
- package/markdown/cross-linker.js +156 -0
- package/markdown/helpers.js +364 -0
- package/markdown/index.js +11 -0
- package/markdown/page-generators/class-page.js +357 -0
- package/markdown/page-generators/enum-page.js +152 -0
- package/markdown/page-generators/function-page.js +127 -0
- package/markdown/page-generators/index-pages.js +27 -0
- package/markdown/page-generators/interface-page.js +307 -0
- package/markdown/page-generators/namespace-page.js +280 -0
- package/markdown/page-generators/type-alias-page.js +110 -0
- package/markdown/page-generators/variable-page.js +110 -0
- package/markdown/shiki-utils.js +48 -0
- package/migrations/001_create_snapshots.js +25 -0
- package/model-loader.js +95 -0
- package/multi-entry-resolver.js +70 -0
- package/og-resolver.js +271 -0
- package/package.json +64 -73
- package/path-derivation.js +48 -0
- package/plugin.js +218 -0
- package/prettier-formatter.js +73 -0
- package/public/tsconfig/rspress.json +44 -0
- package/remark-api-codeblocks.js +130 -0
- package/remark-with-api.js +172 -0
- package/route-collisions.js +52 -0
- package/runtime/components/ApiExample/index.js +29 -25
- package/runtime/components/ApiLlmsPackageActions/index.js +252 -323
- package/runtime/components/ApiLlmsViewOptions/index.js +269 -340
- package/runtime/components/ApiMember/index.js +49 -47
- package/runtime/components/ApiSignature/index.js +32 -28
- package/runtime/components/EnumMembersTable/{index_module.css → index.css} +18 -19
- package/runtime/components/EnumMembersTable/index.js +36 -67
- package/runtime/components/EnumMembersTable/index.module.js +9 -6
- package/runtime/components/ExampleBlock/{index_module.css → index.css} +2 -3
- package/runtime/components/ExampleBlock/index.js +23 -28
- package/runtime/components/ExampleBlock/index.module.js +8 -5
- package/runtime/components/MarkdownContent/index.js +26 -18
- package/runtime/components/MarkdownText/index.js +28 -22
- package/runtime/components/MemberSignature/{index_module.css → index.css} +5 -6
- package/runtime/components/MemberSignature/index.js +46 -46
- package/runtime/components/MemberSignature/index.module.js +9 -6
- package/runtime/components/ParametersTable/{index_module.css → index.css} +19 -20
- package/runtime/components/ParametersTable/index.js +36 -67
- package/runtime/components/ParametersTable/index.module.js +9 -6
- package/runtime/components/SignatureBlock/{index_module.css → index.css} +5 -6
- package/runtime/components/SignatureBlock/index.js +30 -29
- package/runtime/components/SignatureBlock/index.module.js +9 -6
- package/runtime/components/SignatureCode/{index_module.css → index.css} +9 -10
- package/runtime/components/SignatureCode/index.js +40 -32
- package/runtime/components/SignatureCode/index.module.js +9 -6
- package/runtime/components/SignatureToolbar/{index_module.css → index.css} +18 -20
- package/runtime/components/SignatureToolbar/index.js +48 -51
- package/runtime/components/SignatureToolbar/index.module.js +13 -10
- package/runtime/components/buttons/ButtonGroup.js +13 -6
- package/runtime/components/buttons/CopyCodeButton.js +40 -38
- package/runtime/components/buttons/WrapSignatureButton.js +20 -16
- package/runtime/components/buttons/{index_module.css → index.css} +5 -6
- package/runtime/components/buttons/index.module.js +8 -5
- package/runtime/components/icons/CheckIcon/index.js +20 -17
- package/runtime/components/icons/CopyIcon/index.js +20 -17
- package/runtime/components/icons/UnwrapIcon/index.js +22 -18
- package/runtime/components/icons/WrapIcon/index.js +20 -17
- package/runtime/components/shared/_twoslash.css +3 -10
- package/runtime/components/shared/variables.css +0 -3
- package/runtime/hooks/useWrapToggle.js +32 -9
- package/runtime/index.d.ts +513 -173
- package/runtime/index.js +11 -9
- package/runtime/utils/decode-hast.js +31 -16
- package/runtime/utils/hast-renderer.js +21 -7
- package/schemas/config.js +199 -0
- package/schemas/index.js +5 -0
- package/schemas/opengraph.js +26 -0
- package/schemas/performance.js +19 -0
- package/serve.js +133 -0
- package/services/ConfigService.js +7 -0
- package/services/PathDerivationService.js +7 -0
- package/services/SnapshotService.js +7 -0
- package/services/TypeRegistryService.js +7 -0
- package/shiki-transformer.js +758 -0
- package/tsconfig-parser.js +127 -0
- package/tsdoc-metadata.json +11 -11
- package/twoslash-patterns.js +87 -0
- package/twoslash-transformer.js +316 -0
- package/type-reference-extractor.js +201 -0
- package/typescript-config.js +168 -0
- package/vfs-registry.js +121 -0
- package/0~llms-program.js +0 -344
- package/runtime/components/ApiLlmsPackageActions/index.module.js +0 -2
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { dirname, isAbsolute, resolve } from "node:path";
|
|
3
|
+
import ts from "typescript";
|
|
4
|
+
|
|
5
|
+
//#region src/tsconfig-parser.ts
|
|
6
|
+
/**
|
|
7
|
+
* Error thrown when tsconfig.json parsing fails.
|
|
8
|
+
*/
|
|
9
|
+
var TsConfigParseError = class extends Error {
|
|
10
|
+
configPath;
|
|
11
|
+
cause;
|
|
12
|
+
constructor(configPath, message, cause) {
|
|
13
|
+
super(`Failed to parse tsconfig at ${configPath}: ${message}`);
|
|
14
|
+
this.configPath = configPath;
|
|
15
|
+
this.cause = cause;
|
|
16
|
+
this.name = "TsConfigParseError";
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Parse a tsconfig.json file and extract compiler options relevant for type resolution.
|
|
21
|
+
*
|
|
22
|
+
* This function uses TypeScript's native config parsing which automatically handles:
|
|
23
|
+
* - `extends` chains (resolves and merges all extended configs)
|
|
24
|
+
* - Comments in JSON (JSONC support)
|
|
25
|
+
* - Relative path resolution
|
|
26
|
+
*
|
|
27
|
+
* @param configPath - Path to tsconfig.json (relative or absolute)
|
|
28
|
+
* @param projectRoot - Project root directory for resolving relative paths
|
|
29
|
+
* @returns Parsed compiler options
|
|
30
|
+
* @throws TsConfigParseError if the config cannot be read or parsed
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* ```ts
|
|
34
|
+
* const options = parseTsConfig("tsconfig.json", "/path/to/project");
|
|
35
|
+
* // Returns: { target: 99, module: 99, lib: ["ESNext", "DOM"], ... }
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
function parseTsConfig(configPath, projectRoot) {
|
|
39
|
+
return parseTsConfigWithMetadata(configPath, projectRoot).compilerOptions;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Parse a tsconfig.json file and return detailed metadata including extended paths.
|
|
43
|
+
*
|
|
44
|
+
* @param configPath - Path to tsconfig.json (relative or absolute)
|
|
45
|
+
* @param projectRoot - Project root directory for resolving relative paths
|
|
46
|
+
* @returns Parse result with compiler options and metadata
|
|
47
|
+
* @throws TsConfigParseError if the config cannot be read or parsed
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* ```ts
|
|
51
|
+
* const result = parseTsConfigWithMetadata("tsconfig.json", "/path/to/project");
|
|
52
|
+
* console.log(result.configPath); // Absolute path to resolved config
|
|
53
|
+
* console.log(result.extendedPaths); // ["base.json", "tsconfig.json"]
|
|
54
|
+
* console.log(result.compilerOptions); // Merged compiler options
|
|
55
|
+
* ```
|
|
56
|
+
*/
|
|
57
|
+
function parseTsConfigWithMetadata(configPath, projectRoot) {
|
|
58
|
+
const absolutePath = isAbsolute(configPath) ? configPath : resolve(projectRoot, configPath);
|
|
59
|
+
if (!existsSync(absolutePath)) throw new TsConfigParseError(absolutePath, "File not found");
|
|
60
|
+
const configFileContent = ts.readConfigFile(absolutePath, (path) => readFileSync(path, "utf-8"));
|
|
61
|
+
if (configFileContent.error) throw new TsConfigParseError(absolutePath, ts.flattenDiagnosticMessageText(configFileContent.error.messageText, "\n"), configFileContent.error);
|
|
62
|
+
const configDir = dirname(absolutePath);
|
|
63
|
+
const parsedConfig = ts.parseJsonConfigFileContent(configFileContent.config, ts.sys, configDir, void 0, absolutePath);
|
|
64
|
+
const significantErrors = parsedConfig.errors.filter((error) => {
|
|
65
|
+
const message = ts.flattenDiagnosticMessageText(error.messageText, "\n");
|
|
66
|
+
return error.code !== 18003 && !message.includes("No inputs were found");
|
|
67
|
+
});
|
|
68
|
+
if (significantErrors.length > 0) throw new TsConfigParseError(absolutePath, significantErrors.map((error) => ts.flattenDiagnosticMessageText(error.messageText, "\n")).join("; "), significantErrors);
|
|
69
|
+
const extendedPaths = [absolutePath];
|
|
70
|
+
collectExtendedPaths(configFileContent.config, configDir, extendedPaths);
|
|
71
|
+
const tsOptions = parsedConfig.options;
|
|
72
|
+
return {
|
|
73
|
+
compilerOptions: extractTypeResolutionOptions(tsOptions),
|
|
74
|
+
configPath: absolutePath,
|
|
75
|
+
extendedPaths
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Recursively collect extended config paths.
|
|
80
|
+
* @internal
|
|
81
|
+
*/
|
|
82
|
+
function collectExtendedPaths(config, baseDir, paths) {
|
|
83
|
+
if (!config || typeof config !== "object") return;
|
|
84
|
+
const extendsValue = config.extends;
|
|
85
|
+
if (typeof extendsValue === "string") {
|
|
86
|
+
const extendedPath = resolveExtendedPath(extendsValue, baseDir);
|
|
87
|
+
if (extendedPath && !paths.includes(extendedPath)) paths.unshift(extendedPath);
|
|
88
|
+
} else if (Array.isArray(extendsValue)) {
|
|
89
|
+
for (const ext of extendsValue) if (typeof ext === "string") {
|
|
90
|
+
const extendedPath = resolveExtendedPath(ext, baseDir);
|
|
91
|
+
if (extendedPath && !paths.includes(extendedPath)) paths.unshift(extendedPath);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Resolve an extended config path.
|
|
97
|
+
* @internal
|
|
98
|
+
*/
|
|
99
|
+
function resolveExtendedPath(extendsValue, baseDir) {
|
|
100
|
+
try {
|
|
101
|
+
if (extendsValue.startsWith(".")) return resolve(baseDir, extendsValue);
|
|
102
|
+
return extendsValue;
|
|
103
|
+
} catch {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Extract TypeResolutionCompilerOptions from full TypeScript CompilerOptions.
|
|
109
|
+
* @internal
|
|
110
|
+
*/
|
|
111
|
+
function extractTypeResolutionOptions(tsOptions) {
|
|
112
|
+
const options = {};
|
|
113
|
+
if (tsOptions.target !== void 0) options.target = tsOptions.target;
|
|
114
|
+
if (tsOptions.module !== void 0) options.module = tsOptions.module;
|
|
115
|
+
if (tsOptions.moduleResolution !== void 0) options.moduleResolution = tsOptions.moduleResolution;
|
|
116
|
+
if (tsOptions.lib !== void 0 && tsOptions.lib.length > 0) options.lib = tsOptions.lib;
|
|
117
|
+
if (tsOptions.strict !== void 0) options.strict = tsOptions.strict;
|
|
118
|
+
if (tsOptions.skipLibCheck !== void 0) options.skipLibCheck = tsOptions.skipLibCheck;
|
|
119
|
+
if (tsOptions.esModuleInterop !== void 0) options.esModuleInterop = tsOptions.esModuleInterop;
|
|
120
|
+
if (tsOptions.allowSyntheticDefaultImports !== void 0) options.allowSyntheticDefaultImports = tsOptions.allowSyntheticDefaultImports;
|
|
121
|
+
if (tsOptions.jsx !== void 0) options.jsx = tsOptions.jsx;
|
|
122
|
+
if (tsOptions.types !== void 0 && tsOptions.types.length > 0) options.types = tsOptions.types;
|
|
123
|
+
return options;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
//#endregion
|
|
127
|
+
export { TsConfigParseError, parseTsConfig };
|
package/tsdoc-metadata.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
// This file is read by tools that parse documentation comments conforming to the TSDoc standard.
|
|
2
|
-
// It should be published with your NPM package. It should not be tracked by Git.
|
|
3
|
-
{
|
|
4
|
-
"tsdocVersion": "0.12",
|
|
5
|
-
"toolPackages": [
|
|
6
|
-
{
|
|
7
|
-
"packageName": "@microsoft/api-extractor",
|
|
8
|
-
"packageVersion": "7.58.
|
|
9
|
-
}
|
|
10
|
-
]
|
|
11
|
-
}
|
|
1
|
+
// This file is read by tools that parse documentation comments conforming to the TSDoc standard.
|
|
2
|
+
// It should be published with your NPM package. It should not be tracked by Git.
|
|
3
|
+
{
|
|
4
|
+
"tsdocVersion": "0.12",
|
|
5
|
+
"toolPackages": [
|
|
6
|
+
{
|
|
7
|
+
"packageName": "@microsoft/api-extractor",
|
|
8
|
+
"packageVersion": "7.58.9"
|
|
9
|
+
}
|
|
10
|
+
]
|
|
11
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
//#region src/twoslash-patterns.ts
|
|
2
|
+
/**
|
|
3
|
+
* Twoslash directive detection patterns.
|
|
4
|
+
*
|
|
5
|
+
* These regexes mirror the upstream Twoslash source at:
|
|
6
|
+
* https://github.com/twoslashes/twoslash/blob/main/packages/twoslash/src/regexp.ts
|
|
7
|
+
*
|
|
8
|
+
* All patterns allow an optional space after `//` (e.g., both `// @noErrors`
|
|
9
|
+
* and `//@noErrors` are valid Twoslash syntax).
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* Config directives: boolean flags and key-value pairs.
|
|
13
|
+
*
|
|
14
|
+
* Upstream: `reConfigBoolean` + `reConfigValue` + `reFilenamesMakers`
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```
|
|
18
|
+
* // @noErrors
|
|
19
|
+
* //@strict
|
|
20
|
+
* // @errors: 2304
|
|
21
|
+
* // @target: ES2020
|
|
22
|
+
* // @filename: example.ts
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
const RE_CONFIG = /^\/\/\s?@\w+/;
|
|
26
|
+
/**
|
|
27
|
+
* Annotation markers: query, completion, and highlight markers.
|
|
28
|
+
*
|
|
29
|
+
* Upstream: `reAnnonateMarkers` — `/^\s*\/\/\s*\^(\?|\||\^+)( .*)?$/gm`
|
|
30
|
+
*
|
|
31
|
+
* These are positioned under code lines with `^` characters for alignment.
|
|
32
|
+
* After `line.trim()`, leading whitespace is removed but internal spaces
|
|
33
|
+
* between `//` and `^` are preserved.
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* ```
|
|
37
|
+
* // ^? — query (show type info)
|
|
38
|
+
* // ^? — query with alignment spaces
|
|
39
|
+
* // ^| — completion (show autocomplete)
|
|
40
|
+
* // ^^^ — highlight range
|
|
41
|
+
* // ^^^^ description text
|
|
42
|
+
* ```
|
|
43
|
+
*/
|
|
44
|
+
const RE_ANNOTATION = /^\/\/\s*\^[?|^]/;
|
|
45
|
+
/**
|
|
46
|
+
* Cut directives: control which code is visible in output.
|
|
47
|
+
*
|
|
48
|
+
* Upstream: `reCutBefore`, `reCutAfter`, `reCutStart`, `reCutEnd`
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* ```
|
|
52
|
+
* // ---cut---
|
|
53
|
+
* //---cut-before---
|
|
54
|
+
* // ---cut-after---
|
|
55
|
+
* // ---cut-start---
|
|
56
|
+
* // ---cut-end---
|
|
57
|
+
* ```
|
|
58
|
+
*/
|
|
59
|
+
const RE_CUT = /^\/\/\s?---cut/;
|
|
60
|
+
/**
|
|
61
|
+
* Test whether a trimmed line is any Twoslash directive.
|
|
62
|
+
*
|
|
63
|
+
* Covers all directive types: config flags, config values, filename markers,
|
|
64
|
+
* annotation markers (query/completion/highlight), and cut directives.
|
|
65
|
+
*
|
|
66
|
+
* @param trimmedLine - The line with leading/trailing whitespace removed
|
|
67
|
+
* @returns true if the line is a Twoslash directive
|
|
68
|
+
*/
|
|
69
|
+
function isTwoslashDirective(trimmedLine) {
|
|
70
|
+
return RE_CONFIG.test(trimmedLine) || RE_ANNOTATION.test(trimmedLine) || RE_CUT.test(trimmedLine);
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Classify a cut directive line.
|
|
74
|
+
*
|
|
75
|
+
* @param trimmedLine - The line with leading/trailing whitespace removed
|
|
76
|
+
* @returns The cut type, or null if not a cut directive
|
|
77
|
+
*/
|
|
78
|
+
function classifyCutDirective(trimmedLine) {
|
|
79
|
+
if (/^\/\/\s?---cut(-before)?---$/.test(trimmedLine)) return "cut-before";
|
|
80
|
+
if (/^\/\/\s?---cut-after---$/.test(trimmedLine)) return "cut-after";
|
|
81
|
+
if (/^\/\/\s?---cut-start---$/.test(trimmedLine)) return "cut-start";
|
|
82
|
+
if (/^\/\/\s?---cut-end---$/.test(trimmedLine)) return "cut-end";
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
//#endregion
|
|
87
|
+
export { classifyCutDirective, isTwoslashDirective };
|
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
import { BuildMetrics } from "./layers/ObservabilityLive.js";
|
|
2
|
+
import { DEFAULT_COMPILER_OPTIONS } from "./typescript-config.js";
|
|
3
|
+
import { Effect, Metric } from "effect";
|
|
4
|
+
import { rendererRich, transformerTwoslash } from "@shikijs/twoslash";
|
|
5
|
+
import { fromMarkdown } from "mdast-util-from-markdown";
|
|
6
|
+
import { toHast } from "mdast-util-to-hast";
|
|
7
|
+
|
|
8
|
+
//#region src/twoslash-transformer.ts
|
|
9
|
+
/* v8 ignore start -- Shiki/Twoslash integration, requires full highlighter setup for testing */
|
|
10
|
+
/**
|
|
11
|
+
* Module-level type routes map for resolving link references.
|
|
12
|
+
* This is set by TwoslashManager.setTypeRoutes() before initialization.
|
|
13
|
+
*/
|
|
14
|
+
let typeRoutes = /* @__PURE__ */ new Map();
|
|
15
|
+
/**
|
|
16
|
+
* Transform TSDoc link tag syntax to markdown links or plain text.
|
|
17
|
+
*
|
|
18
|
+
* Handles various TSDoc link formats:
|
|
19
|
+
* - Simple type reference
|
|
20
|
+
* - Type with pipe-separated display text
|
|
21
|
+
* - Type with space-separated display text
|
|
22
|
+
*
|
|
23
|
+
* Also handles multiline links where the content may be split across lines.
|
|
24
|
+
*
|
|
25
|
+
* @param text - Text containing TSDoc link references
|
|
26
|
+
* @returns Text with links transformed to markdown or plain text
|
|
27
|
+
*/
|
|
28
|
+
function transformTsDocLinks(text) {
|
|
29
|
+
return text.replace(/\{@link\s+([\s\S]*?)\}/g, (_match, content) => {
|
|
30
|
+
const normalized = content.replace(/\s+/g, " ").trim();
|
|
31
|
+
if (!normalized) return "";
|
|
32
|
+
const pipeIndex = normalized.indexOf("|");
|
|
33
|
+
let typeName;
|
|
34
|
+
let displayText;
|
|
35
|
+
if (pipeIndex !== -1) {
|
|
36
|
+
typeName = normalized.substring(0, pipeIndex).trim();
|
|
37
|
+
displayText = normalized.substring(pipeIndex + 1).trim();
|
|
38
|
+
} else {
|
|
39
|
+
const spaceIndex = normalized.indexOf(" ");
|
|
40
|
+
if (spaceIndex !== -1 && /^[A-Z]/.test(normalized)) {
|
|
41
|
+
typeName = normalized.substring(0, spaceIndex).trim();
|
|
42
|
+
displayText = normalized.substring(spaceIndex + 1).trim();
|
|
43
|
+
} else {
|
|
44
|
+
typeName = normalized;
|
|
45
|
+
displayText = normalized;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
const route = typeRoutes.get(typeName);
|
|
49
|
+
if (route) return `[${displayText}](${route})`;
|
|
50
|
+
return displayText;
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Process TSDoc documentation content for display in Twoslash hover popups.
|
|
55
|
+
*
|
|
56
|
+
* This function:
|
|
57
|
+
* - Transforms TSDoc link references to markdown links or plain text
|
|
58
|
+
* - Normalizes whitespace around links for proper inline display
|
|
59
|
+
* - Removes example blocks (including their code content)
|
|
60
|
+
* - Removes the remarks tag while keeping the body text
|
|
61
|
+
* - Removes modifier tags (public, internal, private, etc.)
|
|
62
|
+
* - Removes see, param, returns, throws tags (these are rendered separately)
|
|
63
|
+
*
|
|
64
|
+
* @param docs - Raw TSDoc documentation string
|
|
65
|
+
* @returns Cleaned documentation string ready for markdown rendering
|
|
66
|
+
*/
|
|
67
|
+
function processHoverDocs(docs) {
|
|
68
|
+
let cleaned = transformTsDocLinks(docs);
|
|
69
|
+
cleaned = cleaned.split(/\n\n+/).map((para) => para.replace(/\s+/g, " ").trim()).filter((para) => para.length > 0).join("\n\n");
|
|
70
|
+
cleaned = cleaned.replace(/@example[\s\S]*?(?=@[a-zA-Z]|$)/g, "");
|
|
71
|
+
cleaned = cleaned.replace(/@remarks\s*/g, "");
|
|
72
|
+
cleaned = cleaned.replace(/@(public|internal|private|protected|readonly|sealed|virtual|override)\s*/g, "");
|
|
73
|
+
cleaned = cleaned.replace(/@see\s+[^\n]*/g, "");
|
|
74
|
+
cleaned = cleaned.replace(/@(param|returns?|throws?)\s+[^\n]*/g, "");
|
|
75
|
+
cleaned = cleaned.replace(/\n{3,}/g, "\n\n");
|
|
76
|
+
return cleaned.trim();
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Add rp-link class to all anchor elements in a HAST tree.
|
|
80
|
+
* This enables RSPress link styling in hover popups.
|
|
81
|
+
*
|
|
82
|
+
* @param node - HAST node to process
|
|
83
|
+
*/
|
|
84
|
+
function addLinkClasses(node) {
|
|
85
|
+
if (node.type === "element") {
|
|
86
|
+
if (node.tagName === "a") {
|
|
87
|
+
const existing = node.properties?.class;
|
|
88
|
+
if (typeof existing === "string") node.properties = {
|
|
89
|
+
...node.properties,
|
|
90
|
+
class: `${existing} rp-link`
|
|
91
|
+
};
|
|
92
|
+
else node.properties = {
|
|
93
|
+
...node.properties,
|
|
94
|
+
class: "rp-link"
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
if (node.children) for (const child of node.children) addLinkClasses(child);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Render markdown content to HAST (Hypertext Abstract Syntax Tree) elements.
|
|
102
|
+
*
|
|
103
|
+
* This function converts markdown strings (from TSDoc comments) into HAST nodes
|
|
104
|
+
* that can be rendered in Twoslash hover popups. It uses mdast-util-from-markdown
|
|
105
|
+
* to parse the markdown and mdast-util-to-hast to convert to HAST.
|
|
106
|
+
*
|
|
107
|
+
* TSDoc link references are transformed to markdown links before parsing.
|
|
108
|
+
* Whitespace is normalized for proper inline display.
|
|
109
|
+
* Links are given the rp-link class for RSPress styling.
|
|
110
|
+
*
|
|
111
|
+
* @param markdown - Markdown string to render
|
|
112
|
+
* @returns Array of HAST ElementContent nodes
|
|
113
|
+
*/
|
|
114
|
+
function renderMarkdown(markdown) {
|
|
115
|
+
if (!markdown?.trim()) return [];
|
|
116
|
+
try {
|
|
117
|
+
let transformed = transformTsDocLinks(markdown);
|
|
118
|
+
transformed = transformed.split(/\n\n+/).map((para) => para.replace(/\s+/g, " ").trim()).filter((para) => para.length > 0).join("\n\n");
|
|
119
|
+
const hast = toHast(fromMarkdown(transformed));
|
|
120
|
+
if (hast && "children" in hast) {
|
|
121
|
+
const children = hast.children;
|
|
122
|
+
for (const child of children) addLinkClasses(child);
|
|
123
|
+
return children;
|
|
124
|
+
}
|
|
125
|
+
return [];
|
|
126
|
+
} catch {
|
|
127
|
+
return [{
|
|
128
|
+
type: "text",
|
|
129
|
+
value: markdown
|
|
130
|
+
}];
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Tags to hide entirely in hover popups.
|
|
135
|
+
* These are either redundant with the API documentation structure or add noise.
|
|
136
|
+
*/
|
|
137
|
+
const HIDDEN_TAGS = new Set([
|
|
138
|
+
"example",
|
|
139
|
+
"public",
|
|
140
|
+
"internal",
|
|
141
|
+
"private",
|
|
142
|
+
"protected",
|
|
143
|
+
"readonly",
|
|
144
|
+
"sealed",
|
|
145
|
+
"virtual",
|
|
146
|
+
"override"
|
|
147
|
+
]);
|
|
148
|
+
/**
|
|
149
|
+
* Render inline markdown for JSDoc tags with identifying CSS classes.
|
|
150
|
+
*
|
|
151
|
+
* This function wraps tag content in spans with tag-specific classes,
|
|
152
|
+
* allowing CSS to selectively show/hide or style specific tags.
|
|
153
|
+
*
|
|
154
|
+
* @param markdown - The tag content to render
|
|
155
|
+
* @param context - Context string like "tag:remarks", "tag:example", etc.
|
|
156
|
+
* @returns Array of HAST ElementContent nodes
|
|
157
|
+
*/
|
|
158
|
+
function renderMarkdownInline(markdown, context) {
|
|
159
|
+
const tagName = context.startsWith("tag:") ? context.slice(4) : "";
|
|
160
|
+
if (HIDDEN_TAGS.has(tagName)) return [{
|
|
161
|
+
type: "element",
|
|
162
|
+
tagName: "span",
|
|
163
|
+
properties: { class: `twoslash-tag-hidden twoslash-tag-${tagName}` },
|
|
164
|
+
children: []
|
|
165
|
+
}];
|
|
166
|
+
const children = renderMarkdown(markdown);
|
|
167
|
+
return [{
|
|
168
|
+
type: "element",
|
|
169
|
+
tagName: "span",
|
|
170
|
+
properties: { class: `twoslash-tag-content twoslash-tag-${tagName}` },
|
|
171
|
+
children
|
|
172
|
+
}];
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Singleton manager for the Twoslash transformer, enabling type-aware documentation.
|
|
176
|
+
*
|
|
177
|
+
* The TwoslashManager initializes and manages a Shiki transformer that provides
|
|
178
|
+
* TypeScript IntelliSense features (hover types, error highlighting, completions)
|
|
179
|
+
* in documentation code blocks. It uses a virtual file system (VFS) to provide
|
|
180
|
+
* type definitions without requiring actual file system access.
|
|
181
|
+
*
|
|
182
|
+
* **How it works:**
|
|
183
|
+
* 1. Plugin initializes the manager with a VFS containing all package type definitions
|
|
184
|
+
* 2. Code blocks marked with `twoslash` are processed by the transformer
|
|
185
|
+
* 3. TypeScript language services provide hover information and error checking
|
|
186
|
+
* 4. Results are rendered as HTML with interactive hover popups
|
|
187
|
+
*
|
|
188
|
+
* **VFS Integration:**
|
|
189
|
+
* The VFS is populated by {@link TypeRegistryService} with:
|
|
190
|
+
* - The documented package's own type definitions (from API Extractor)
|
|
191
|
+
* - External package types (fetched via type-registry-effect)
|
|
192
|
+
*
|
|
193
|
+
* **Error Handling:**
|
|
194
|
+
* TypeScript errors in code blocks are captured (not thrown) and:
|
|
195
|
+
* - Counted via Effect Metric (BuildMetrics.twoslashErrors)
|
|
196
|
+
* - Logged inline via console.error
|
|
197
|
+
* - Displayed in the rendered output as error annotations
|
|
198
|
+
*
|
|
199
|
+
* **Relationships:**
|
|
200
|
+
* - Initialized by {@link ApiExtractorPlugin} in the beforeBuild hook
|
|
201
|
+
* - Receives VFS from {@link TypeRegistryService}
|
|
202
|
+
* - The transformer is used by page generators for rendering code blocks
|
|
203
|
+
*
|
|
204
|
+
* @example
|
|
205
|
+
* ```ts
|
|
206
|
+
* const manager = TwoslashManager.getInstance();
|
|
207
|
+
* manager.initialize(vfs, undefined, logger);
|
|
208
|
+
*
|
|
209
|
+
* const transformer = manager.getTransformer();
|
|
210
|
+
* // Use transformer with Shiki highlighter
|
|
211
|
+
* ```
|
|
212
|
+
*
|
|
213
|
+
* @see {@link TypeRegistryService} for VFS generation
|
|
214
|
+
*/
|
|
215
|
+
var TwoslashManager = class TwoslashManager {
|
|
216
|
+
static instance = null;
|
|
217
|
+
/**
|
|
218
|
+
* Twoslash transformer instance
|
|
219
|
+
*/
|
|
220
|
+
transformer = null;
|
|
221
|
+
/**
|
|
222
|
+
* Private constructor to enforce singleton pattern
|
|
223
|
+
*/
|
|
224
|
+
constructor() {}
|
|
225
|
+
/**
|
|
226
|
+
* Get the singleton instance of TwoslashManager
|
|
227
|
+
*/
|
|
228
|
+
static getInstance() {
|
|
229
|
+
if (!TwoslashManager.instance) TwoslashManager.instance = new TwoslashManager();
|
|
230
|
+
return TwoslashManager.instance;
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Initialize the Twoslash transformer with a TypeScript environment cache.
|
|
234
|
+
* This enables type-aware documentation with hover information and IntelliSense.
|
|
235
|
+
*
|
|
236
|
+
* @param vfs - Virtual file system mapping file paths to .d.ts content
|
|
237
|
+
* @param _reserved - Reserved parameter (previously errorStatsCollector, now tracked via Effect Metrics)
|
|
238
|
+
* @param _reserved2 - Reserved parameter (previously logger, now uses console)
|
|
239
|
+
* @param tsEnvCache - TypeScript virtual environment cache for reusing language services
|
|
240
|
+
* @param compilerOptions - TypeScript compiler options for Twoslash (defaults to DEFAULT_COMPILER_OPTIONS)
|
|
241
|
+
*/
|
|
242
|
+
initialize(vfs, _reserved, _reserved2, tsEnvCache, compilerOptions) {
|
|
243
|
+
const extraFiles = {};
|
|
244
|
+
for (const [path, content] of vfs.entries()) extraFiles[path] = content;
|
|
245
|
+
const resolvedOptions = compilerOptions ?? DEFAULT_COMPILER_OPTIONS;
|
|
246
|
+
this.transformer = transformerTwoslash({
|
|
247
|
+
renderer: rendererRich({
|
|
248
|
+
processHoverInfo: (info) => {
|
|
249
|
+
return info.replace(/^\(([\w-]+)\)\s+/gm, "").replace(/\nimport .*$/gm, "").trim();
|
|
250
|
+
},
|
|
251
|
+
processHoverDocs,
|
|
252
|
+
renderMarkdown,
|
|
253
|
+
renderMarkdownInline
|
|
254
|
+
}),
|
|
255
|
+
...tsEnvCache != null ? { cache: tsEnvCache } : {},
|
|
256
|
+
twoslashOptions: {
|
|
257
|
+
extraFiles,
|
|
258
|
+
compilerOptions: resolvedOptions,
|
|
259
|
+
handbookOptions: { noErrorValidation: true }
|
|
260
|
+
},
|
|
261
|
+
explicitTrigger: true,
|
|
262
|
+
throws: false,
|
|
263
|
+
onTwoslashError: (error, _code) => {
|
|
264
|
+
Effect.runSync(Metric.increment(BuildMetrics.twoslashErrors));
|
|
265
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
266
|
+
console.error(`🔴 Twoslash error: ${errorMsg}`);
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Get the initialized Twoslash transformer.
|
|
272
|
+
* Returns null if not initialized.
|
|
273
|
+
*/
|
|
274
|
+
getTransformer() {
|
|
275
|
+
return this.transformer;
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Clear the Twoslash transformer (useful for testing or reinitializing)
|
|
279
|
+
*/
|
|
280
|
+
clear() {
|
|
281
|
+
this.transformer = null;
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Reset the singleton instance (useful for testing)
|
|
285
|
+
*/
|
|
286
|
+
static reset() {
|
|
287
|
+
TwoslashManager.instance = null;
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Set the type routes map for resolving link references in hover docs.
|
|
291
|
+
* This should be called before initialize() to enable type linking.
|
|
292
|
+
*
|
|
293
|
+
* @param routes - Map of type names to their documentation URLs
|
|
294
|
+
*/
|
|
295
|
+
static setTypeRoutes(routes) {
|
|
296
|
+
typeRoutes = routes;
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* Add routes to the existing type routes map.
|
|
300
|
+
* Useful for adding routes from multiple packages.
|
|
301
|
+
*
|
|
302
|
+
* @param routes - Map of type names to their documentation URLs
|
|
303
|
+
*/
|
|
304
|
+
static addTypeRoutes(routes) {
|
|
305
|
+
for (const [name, route] of routes) typeRoutes.set(name, route);
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Clear the type routes map (useful for testing)
|
|
309
|
+
*/
|
|
310
|
+
static clearTypeRoutes() {
|
|
311
|
+
typeRoutes.clear();
|
|
312
|
+
}
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
//#endregion
|
|
316
|
+
export { TwoslashManager };
|