rspress-plugin-api-extractor 0.1.2 → 0.2.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.
Files changed (109) hide show
  1. package/README.md +2 -2
  2. package/api-extracted-package.js +426 -0
  3. package/build-program.js +120 -0
  4. package/build-stages.js +664 -0
  5. package/category-resolver.js +50 -0
  6. package/code-post-processor.js +38 -0
  7. package/config-helpers.js +106 -0
  8. package/config-utils.js +258 -0
  9. package/content-hash.js +79 -0
  10. package/errors.js +29 -0
  11. package/formatter.js +69 -0
  12. package/hide-cut-transformer.js +96 -0
  13. package/index.d.ts +901 -831
  14. package/index.js +4 -6128
  15. package/layers/ConfigServiceLive.js +377 -0
  16. package/layers/ObservabilityLive.js +136 -0
  17. package/layers/PathDerivationServiceLive.js +16 -0
  18. package/layers/SnapshotServiceLive.js +94 -0
  19. package/layers/TypeRegistryServiceLive.js +46 -0
  20. package/llms-processing.js +270 -0
  21. package/llms-program.js +262 -0
  22. package/loader.js +186 -0
  23. package/markdown/cross-linker.js +156 -0
  24. package/markdown/helpers.js +364 -0
  25. package/markdown/index.js +11 -0
  26. package/markdown/page-generators/class-page.js +357 -0
  27. package/markdown/page-generators/enum-page.js +152 -0
  28. package/markdown/page-generators/function-page.js +127 -0
  29. package/markdown/page-generators/index-pages.js +27 -0
  30. package/markdown/page-generators/interface-page.js +307 -0
  31. package/markdown/page-generators/namespace-page.js +280 -0
  32. package/markdown/page-generators/type-alias-page.js +110 -0
  33. package/markdown/page-generators/variable-page.js +110 -0
  34. package/markdown/shiki-utils.js +48 -0
  35. package/migrations/001_create_snapshots.js +25 -0
  36. package/model-loader.js +95 -0
  37. package/multi-entry-resolver.js +70 -0
  38. package/og-resolver.js +271 -0
  39. package/package.json +63 -73
  40. package/path-derivation.js +48 -0
  41. package/plugin.js +218 -0
  42. package/prettier-formatter.js +73 -0
  43. package/public/tsconfig/rspress.json +44 -0
  44. package/remark-api-codeblocks.js +130 -0
  45. package/remark-with-api.js +172 -0
  46. package/route-collisions.js +52 -0
  47. package/runtime/components/ApiExample/index.js +29 -25
  48. package/runtime/components/ApiLlmsPackageActions/index.js +252 -323
  49. package/runtime/components/ApiLlmsViewOptions/index.js +269 -340
  50. package/runtime/components/ApiMember/index.js +49 -47
  51. package/runtime/components/ApiSignature/index.js +32 -28
  52. package/runtime/components/EnumMembersTable/{index_module.css → index.css} +18 -19
  53. package/runtime/components/EnumMembersTable/index.js +36 -67
  54. package/runtime/components/EnumMembersTable/index.module.js +9 -6
  55. package/runtime/components/ExampleBlock/{index_module.css → index.css} +2 -3
  56. package/runtime/components/ExampleBlock/index.js +23 -28
  57. package/runtime/components/ExampleBlock/index.module.js +8 -5
  58. package/runtime/components/MarkdownContent/index.js +26 -18
  59. package/runtime/components/MarkdownText/index.js +28 -22
  60. package/runtime/components/MemberSignature/{index_module.css → index.css} +5 -6
  61. package/runtime/components/MemberSignature/index.js +46 -46
  62. package/runtime/components/MemberSignature/index.module.js +9 -6
  63. package/runtime/components/ParametersTable/{index_module.css → index.css} +19 -20
  64. package/runtime/components/ParametersTable/index.js +36 -67
  65. package/runtime/components/ParametersTable/index.module.js +9 -6
  66. package/runtime/components/SignatureBlock/{index_module.css → index.css} +5 -6
  67. package/runtime/components/SignatureBlock/index.js +30 -29
  68. package/runtime/components/SignatureBlock/index.module.js +9 -6
  69. package/runtime/components/SignatureCode/{index_module.css → index.css} +9 -10
  70. package/runtime/components/SignatureCode/index.js +40 -32
  71. package/runtime/components/SignatureCode/index.module.js +9 -6
  72. package/runtime/components/SignatureToolbar/{index_module.css → index.css} +18 -20
  73. package/runtime/components/SignatureToolbar/index.js +48 -51
  74. package/runtime/components/SignatureToolbar/index.module.js +13 -10
  75. package/runtime/components/buttons/ButtonGroup.js +13 -6
  76. package/runtime/components/buttons/CopyCodeButton.js +40 -38
  77. package/runtime/components/buttons/WrapSignatureButton.js +20 -16
  78. package/runtime/components/buttons/{index_module.css → index.css} +5 -6
  79. package/runtime/components/buttons/index.module.js +8 -5
  80. package/runtime/components/icons/CheckIcon/index.js +20 -17
  81. package/runtime/components/icons/CopyIcon/index.js +20 -17
  82. package/runtime/components/icons/UnwrapIcon/index.js +22 -18
  83. package/runtime/components/icons/WrapIcon/index.js +20 -17
  84. package/runtime/components/shared/_twoslash.css +3 -10
  85. package/runtime/components/shared/variables.css +0 -3
  86. package/runtime/hooks/useWrapToggle.js +32 -9
  87. package/runtime/index.d.ts +513 -173
  88. package/runtime/index.js +11 -9
  89. package/runtime/utils/decode-hast.js +31 -16
  90. package/runtime/utils/hast-renderer.js +21 -7
  91. package/schemas/config.js +199 -0
  92. package/schemas/index.js +5 -0
  93. package/schemas/opengraph.js +26 -0
  94. package/schemas/performance.js +19 -0
  95. package/serve.js +133 -0
  96. package/services/ConfigService.js +7 -0
  97. package/services/PathDerivationService.js +7 -0
  98. package/services/SnapshotService.js +7 -0
  99. package/services/TypeRegistryService.js +7 -0
  100. package/shiki-transformer.js +758 -0
  101. package/tsconfig-parser.js +127 -0
  102. package/tsdoc-metadata.json +11 -11
  103. package/twoslash-patterns.js +87 -0
  104. package/twoslash-transformer.js +316 -0
  105. package/type-reference-extractor.js +201 -0
  106. package/typescript-config.js +168 -0
  107. package/vfs-registry.js +121 -0
  108. package/0~llms-program.js +0 -344
  109. 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 };
@@ -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.7"
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 };