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.
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 +64 -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,758 @@
1
+ //#region src/shiki-transformer.ts
2
+ /**
3
+ * A Shiki transformer that adds cross-links to type references in code blocks.
4
+ *
5
+ * This class creates a Shiki transformer that identifies TypeScript type names
6
+ * in syntax-highlighted code blocks and wraps them in anchor tags linking to
7
+ * their API documentation pages. It handles both standalone type references and
8
+ * class member references (e.g., `ClassName.methodName`).
9
+ *
10
+ * **How it works:**
11
+ * 1. The transformer is initialized with route and kind maps from {@link MarkdownCrossLinker}
12
+ * 2. During Shiki rendering, it walks the HAST tree looking for type names
13
+ * 3. When a match is found, it wraps the text node in an anchor tag with the route
14
+ * 4. Semantic CSS classes are added based on the API item kind (class, interface, etc.)
15
+ *
16
+ * **API Scoping:**
17
+ * - Routes are stored per API scope (e.g., "claude-binary-plugin", "rslib-builder")
18
+ * - Each API only links to types within its own scope
19
+ * - The current API scope is set via `setApiScope()` before rendering each file
20
+ *
21
+ * **Relationships:**
22
+ * - Initialized by {@link ApiExtractorPlugin} during the beforeBuild hook
23
+ * - Receives route data from {@link MarkdownCrossLinker.initialize}
24
+ * - Used alongside {@link TwoslashManager} for type-aware code blocks
25
+ * - Works with the hide-cut transformer for member signatures
26
+ *
27
+ * **Features:**
28
+ * - Matches class/interface members within their declaration context
29
+ * - Handles Twoslash-wrapped spans with popup containers
30
+ * - Preserves whitespace and formatting around linked text
31
+ * - Adds semantic CSS classes for styling (api-token-class, api-token-interface, etc.)
32
+ *
33
+ * @example Basic usage
34
+ * ```ts
35
+ * const crossLinker = new ShikiCrossLinker();
36
+ * crossLinker.reinitialize(routes, kinds, "my-api");
37
+ * crossLinker.setApiScope("my-api");
38
+ * const transformer = crossLinker.createTransformer();
39
+ *
40
+ * // Use with Shiki
41
+ * const html = await codeToHtml(code, {
42
+ * lang: "typescript",
43
+ * transformers: [transformer]
44
+ * });
45
+ * ```
46
+ *
47
+ * @see {@link MarkdownCrossLinker} for the markdown equivalent
48
+ * @see {@link TwoslashManager} for type-aware documentation features
49
+ */
50
+ var ShikiCrossLinker = class {
51
+ /** Map of API scopes to their route maps (API item name to route) */
52
+ apiItemRoutesByScope = /* @__PURE__ */ new Map();
53
+ /** Map of API scopes to their kind maps (API item name to kind) */
54
+ apiItemKindsByScope = /* @__PURE__ */ new Map();
55
+ /** Map of API scopes to their class members maps (class name to member names) */
56
+ classMembersMapByScope = /* @__PURE__ */ new Map();
57
+ /**
58
+ * Current API scope being processed (e.g., "claude-binary-plugin")
59
+ */
60
+ currentApiScope = null;
61
+ /**
62
+ * Creates a new ShikiCrossLinker instance. Call reinitialize() with routes, kinds,
63
+ * and API scope before using the transformer.
64
+ */
65
+ constructor(routes, kinds, apiScope) {
66
+ if (routes && kinds && apiScope) this.reinitialize(routes, kinds, apiScope);
67
+ }
68
+ /**
69
+ * Initialize or reinitialize the cross-link maps with new data for a specific API scope.
70
+ * This allows the same transformer instance to be used across multiple API packages,
71
+ * with each API's routes stored separately and scoped to prevent cross-API linking.
72
+ *
73
+ * @param routes - Map of API item names to their documentation routes
74
+ * @param kinds - Map of API item names to their kinds (Class, Interface, etc.)
75
+ * @param apiScope - The API scope identifier (e.g., "claude-binary-plugin", "rslib-builder")
76
+ */
77
+ reinitialize(routes, kinds, apiScope) {
78
+ this.apiItemRoutesByScope.set(apiScope, new Map(routes));
79
+ this.apiItemKindsByScope.set(apiScope, new Map(kinds));
80
+ const classMembersMap = /* @__PURE__ */ new Map();
81
+ for (const [name] of routes.entries()) if (name.includes(".")) {
82
+ const dotIndex = name.indexOf(".");
83
+ const className = name.substring(0, dotIndex);
84
+ const memberName = name.substring(dotIndex + 1);
85
+ if (!classMembersMap.has(className)) classMembersMap.set(className, []);
86
+ const members = classMembersMap.get(className);
87
+ if (members && !members.includes(memberName)) members.push(memberName);
88
+ }
89
+ for (const members of classMembersMap.values()) members.sort((a, b) => b.length - a.length);
90
+ this.classMembersMapByScope.set(apiScope, classMembersMap);
91
+ this.currentApiScope = apiScope;
92
+ }
93
+ /**
94
+ * Set the current API scope for cross-linking.
95
+ * This should be called before rendering each file to ensure links are scoped correctly.
96
+ *
97
+ * @param apiScope - The API scope identifier (e.g., "claude-binary-plugin")
98
+ */
99
+ setApiScope(apiScope) {
100
+ this.currentApiScope = apiScope;
101
+ }
102
+ /**
103
+ * Get the routes map for the current API scope
104
+ */
105
+ getRoutesForCurrentScope() {
106
+ if (!this.currentApiScope) return /* @__PURE__ */ new Map();
107
+ return this.apiItemRoutesByScope.get(this.currentApiScope) || /* @__PURE__ */ new Map();
108
+ }
109
+ /**
110
+ * Get the kinds map for the current API scope
111
+ */
112
+ getKindsForCurrentScope() {
113
+ if (!this.currentApiScope) return /* @__PURE__ */ new Map();
114
+ return this.apiItemKindsByScope.get(this.currentApiScope) || /* @__PURE__ */ new Map();
115
+ }
116
+ /**
117
+ * Get the class members map for the current API scope
118
+ */
119
+ getClassMembersForCurrentScope() {
120
+ if (!this.currentApiScope) return /* @__PURE__ */ new Map();
121
+ return this.classMembersMapByScope.get(this.currentApiScope) || /* @__PURE__ */ new Map();
122
+ }
123
+ /**
124
+ * Get the routes map for a specific API scope
125
+ */
126
+ getRoutesForScope(scope) {
127
+ if (!scope) return /* @__PURE__ */ new Map();
128
+ return this.apiItemRoutesByScope.get(scope) || /* @__PURE__ */ new Map();
129
+ }
130
+ /**
131
+ * Get the kinds map for a specific API scope
132
+ */
133
+ getKindsForScope(scope) {
134
+ if (!scope) return /* @__PURE__ */ new Map();
135
+ return this.apiItemKindsByScope.get(scope) || /* @__PURE__ */ new Map();
136
+ }
137
+ /**
138
+ * Get the class members map for a specific API scope
139
+ */
140
+ getClassMembersForScope(scope) {
141
+ if (!scope) return /* @__PURE__ */ new Map();
142
+ return this.classMembersMapByScope.get(scope) || /* @__PURE__ */ new Map();
143
+ }
144
+ /**
145
+ * Create a Shiki transformer that adds cross-links to type references in code blocks.
146
+ *
147
+ * **DEPRECATED:** This method now returns a no-op transformer. Cross-linking has been
148
+ * moved to post-processing via {@link transformHast} to avoid interfering with Twoslash
149
+ * popup positioning. The Twoslash transformer calculates popup positions based on the
150
+ * original span structure, and modifying spans during the Shiki pipeline caused popups
151
+ * to appear offset from their intended positions.
152
+ *
153
+ * @param _apiScope - Unused, kept for API compatibility
154
+ * @returns A no-op Shiki transformer
155
+ * @deprecated Use {@link transformHast} after Shiki processing completes instead
156
+ */
157
+ createTransformer(_apiScope) {
158
+ return { name: "api-docs-cross-linker" };
159
+ }
160
+ /**
161
+ * Transform a finalized HAST tree to add cross-links to type references.
162
+ *
163
+ * This method should be called AFTER Shiki (including Twoslash) has fully processed
164
+ * the code block. This ensures Twoslash popup containers are already in their correct
165
+ * positions before we add anchor links.
166
+ *
167
+ * @param hast - The finalized HAST root node from Shiki
168
+ * @param apiScope - Optional API scope to use for lookups. If not provided, uses currentApiScope.
169
+ * @returns The transformed HAST with cross-links added
170
+ *
171
+ * @example
172
+ * ```ts
173
+ * const hast = await highlighter.codeToHast(code, { transformers: [twoslashTransformer] });
174
+ * const linkedHast = crossLinker.transformHast(hast, "my-api");
175
+ * ```
176
+ */
177
+ transformHast(hast, apiScope) {
178
+ const effectiveScope = apiScope ?? this.currentApiScope;
179
+ return this.transformRootWithScope(hast, effectiveScope);
180
+ }
181
+ /**
182
+ * Transform the root node of the syntax tree with explicit scope
183
+ */
184
+ transformRootWithScope(node, scope) {
185
+ const apiItemRoutes = this.getRoutesForScope(scope);
186
+ const apiItemKinds = this.getKindsForScope(scope);
187
+ const classMembersMap = this.getClassMembersForScope(scope);
188
+ const scopeStack = [];
189
+ const preElement = node.children.find((child) => child.type === "element" && child.tagName === "pre");
190
+ if (preElement?.type !== "element") return node;
191
+ const codeElement = preElement.children.find((child) => child.type === "element" && child.tagName === "code");
192
+ if (codeElement?.type !== "element") return node;
193
+ for (const lineElement of codeElement.children) {
194
+ if (lineElement.type !== "element" || lineElement.tagName !== "span") continue;
195
+ const getText = (node) => {
196
+ if (node.type === "text") return node.value;
197
+ if (node.type === "element") return node.children.map(getText).join("");
198
+ return "";
199
+ };
200
+ const lineText = lineElement.children.map(getText).join("");
201
+ const currentScope = scopeStack.length > 0 ? scopeStack[scopeStack.length - 1] : null;
202
+ if (currentScope) {
203
+ const members = classMembersMap.get(currentScope);
204
+ if (members) for (const spanElement of lineElement.children) {
205
+ if (spanElement.type !== "element" || spanElement.tagName !== "span") continue;
206
+ if (spanElement.children?.length !== 1) continue;
207
+ const textNode = spanElement.children[0];
208
+ if (textNode.type !== "text") continue;
209
+ const rawContent = textNode.value;
210
+ const content = rawContent.trim();
211
+ if (!content) continue;
212
+ if (members.includes(content)) {
213
+ const fullMemberName = `${currentScope}.${content}`;
214
+ const memberRoute = apiItemRoutes.get(fullMemberName);
215
+ if (memberRoute) {
216
+ const memberKind = apiItemKinds.get(fullMemberName);
217
+ const memberSemanticClass = memberKind ? this.getSemanticClass(memberKind) : null;
218
+ const leadingSpace = rawContent.match(/^\s*/)?.[0] || "";
219
+ const trailingSpace = rawContent.match(/\s*$/)?.[0] || "";
220
+ const classNames = ["api-type-link"];
221
+ if (memberSemanticClass) classNames.push(memberSemanticClass);
222
+ const newChildren = [];
223
+ if (leadingSpace) newChildren.push({
224
+ type: "text",
225
+ value: leadingSpace
226
+ });
227
+ newChildren.push({
228
+ type: "element",
229
+ tagName: "a",
230
+ properties: {
231
+ href: memberRoute,
232
+ class: classNames.join(" ")
233
+ },
234
+ children: [{
235
+ type: "text",
236
+ value: content
237
+ }]
238
+ });
239
+ if (trailingSpace) newChildren.push({
240
+ type: "text",
241
+ value: trailingSpace
242
+ });
243
+ spanElement.children = newChildren;
244
+ spanElement.properties = {
245
+ ...spanElement.properties,
246
+ "data-api-processed": "true"
247
+ };
248
+ }
249
+ }
250
+ }
251
+ }
252
+ const classMatch = lineText.match(/(?:class|interface|namespace)\s+(\w+)\s*(?:<[^>]*>)?\s*(?:extends|implements)?[^{]*\{/);
253
+ if (classMatch) {
254
+ if ((lineText.match(/\{/g) || []).length > (lineText.match(/\}/g) || []).length) scopeStack.push(classMatch[1]);
255
+ }
256
+ const openBraces = (lineText.match(/\{/g) || []).length;
257
+ const excessCloses = (lineText.match(/\}/g) || []).length - openBraces;
258
+ for (let i = 0; i < excessCloses && scopeStack.length > 0; i++) scopeStack.pop();
259
+ }
260
+ const findTwoslashSpans = (element) => {
261
+ const results = [];
262
+ if (element.properties?.class && String(element.properties.class).includes("twoslash-hover")) results.push(element);
263
+ if (element.children) {
264
+ for (const child of element.children) if (child.type === "element") results.push(...findTwoslashSpans(child));
265
+ }
266
+ return results;
267
+ };
268
+ const twoslashSpans = findTwoslashSpans(codeElement);
269
+ for (const twoslashSpan of twoslashSpans) {
270
+ if (twoslashSpan.properties?.["data-api-processed"] === "true") continue;
271
+ const methodInfo = this.extractMethodInfoFromTwoslashTooltip(twoslashSpan);
272
+ if (!methodInfo) continue;
273
+ const { className, methodName } = methodInfo;
274
+ const fullMemberName = `${className}.${methodName}`;
275
+ const memberRoute = apiItemRoutes.get(fullMemberName);
276
+ if (!memberRoute) continue;
277
+ const memberKind = apiItemKinds.get(fullMemberName);
278
+ const memberSemanticClass = memberKind ? this.getSemanticClass(memberKind) : null;
279
+ const memberClassNames = ["api-type-link"];
280
+ if (memberSemanticClass) memberClassNames.push(memberSemanticClass);
281
+ const textContent = this.extractTextFromTwoslash(twoslashSpan);
282
+ if (!textContent) continue;
283
+ this.wrapTwoslashTextInAnchor(twoslashSpan, textContent.trim(), memberRoute, memberClassNames);
284
+ twoslashSpan.properties = {
285
+ ...twoslashSpan.properties,
286
+ "data-api-processed": "true"
287
+ };
288
+ }
289
+ const typeNames = Array.from(apiItemRoutes.keys()).filter((name) => !name.includes(".")).sort((a, b) => b.length - a.length);
290
+ if (typeNames.length > 0) {
291
+ for (const twoslashSpan of twoslashSpans) {
292
+ if (twoslashSpan.properties?.["data-api-processed"] === "true") continue;
293
+ const text = this.extractTextFromTwoslash(twoslashSpan);
294
+ if (!text) continue;
295
+ const content = text.trim();
296
+ const route = apiItemRoutes.get(content);
297
+ if (!route) continue;
298
+ const kind = apiItemKinds.get(content);
299
+ const semanticClass = kind ? this.getSemanticClass(kind) : null;
300
+ const classNames = ["api-type-link"];
301
+ if (semanticClass) classNames.push(semanticClass);
302
+ this.wrapTwoslashTextInAnchor(twoslashSpan, content, route, classNames);
303
+ twoslashSpan.properties = {
304
+ ...twoslashSpan.properties,
305
+ "data-api-processed": "true"
306
+ };
307
+ }
308
+ const escapedNames = typeNames.map((n) => n.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
309
+ const typePattern = new RegExp(`\\b(${escapedNames.join("|")})\\b`, "g");
310
+ for (const lineElement of codeElement.children) {
311
+ if (lineElement.type !== "element" || lineElement.tagName !== "span") continue;
312
+ this.linkTypeReferencesInLine(lineElement, typePattern, apiItemRoutes, apiItemKinds);
313
+ }
314
+ }
315
+ return node;
316
+ }
317
+ /**
318
+ * Transform the root node of the syntax tree
319
+ */
320
+ transformRoot(node) {
321
+ const apiItemRoutes = this.getRoutesForCurrentScope();
322
+ const apiItemKinds = this.getKindsForCurrentScope();
323
+ const classMembersMap = this.getClassMembersForCurrentScope();
324
+ const scopeStack = [];
325
+ const preElement = node.children.find((child) => child.type === "element" && child.tagName === "pre");
326
+ if (preElement?.type !== "element") return node;
327
+ const codeElement = preElement.children.find((child) => child.type === "element" && child.tagName === "code");
328
+ if (codeElement?.type !== "element") return node;
329
+ for (const lineElement of codeElement.children) {
330
+ if (lineElement.type !== "element" || lineElement.tagName !== "span") continue;
331
+ const getText = (node) => {
332
+ if (node.type === "text") return node.value;
333
+ if (node.type === "element") return node.children.map(getText).join("");
334
+ return "";
335
+ };
336
+ const lineText = lineElement.children.map(getText).join("");
337
+ const currentScope = scopeStack.length > 0 ? scopeStack[scopeStack.length - 1] : null;
338
+ if (currentScope) {
339
+ const members = classMembersMap.get(currentScope);
340
+ if (members) for (const spanElement of lineElement.children) {
341
+ if (spanElement.type !== "element" || spanElement.tagName !== "span") continue;
342
+ if (spanElement.children?.length !== 1) continue;
343
+ const textNode = spanElement.children[0];
344
+ if (textNode.type !== "text") continue;
345
+ const rawContent = textNode.value;
346
+ const content = rawContent.trim();
347
+ if (!content) continue;
348
+ if (members.includes(content)) {
349
+ const fullMemberName = `${currentScope}.${content}`;
350
+ const memberRoute = apiItemRoutes.get(fullMemberName);
351
+ if (memberRoute) {
352
+ const memberKind = apiItemKinds.get(fullMemberName);
353
+ const memberSemanticClass = memberKind ? this.getSemanticClass(memberKind) : null;
354
+ const leadingSpace = rawContent.match(/^\s*/)?.[0] || "";
355
+ const trailingSpace = rawContent.match(/\s*$/)?.[0] || "";
356
+ const classNames = ["api-type-link"];
357
+ if (memberSemanticClass) classNames.push(memberSemanticClass);
358
+ const newChildren = [];
359
+ if (leadingSpace) newChildren.push({
360
+ type: "text",
361
+ value: leadingSpace
362
+ });
363
+ newChildren.push({
364
+ type: "element",
365
+ tagName: "a",
366
+ properties: {
367
+ href: memberRoute,
368
+ class: classNames.join(" ")
369
+ },
370
+ children: [{
371
+ type: "text",
372
+ value: content
373
+ }]
374
+ });
375
+ if (trailingSpace) newChildren.push({
376
+ type: "text",
377
+ value: trailingSpace
378
+ });
379
+ spanElement.children = newChildren;
380
+ spanElement.properties = {
381
+ ...spanElement.properties,
382
+ "data-api-processed": "true"
383
+ };
384
+ }
385
+ }
386
+ }
387
+ }
388
+ const classMatch = lineText.match(/(?:class|interface|namespace)\s+(\w+)\s*(?:<[^>]*>)?\s*(?:extends|implements)?[^{]*\{/);
389
+ if (classMatch) {
390
+ if ((lineText.match(/\{/g) || []).length > (lineText.match(/\}/g) || []).length) scopeStack.push(classMatch[1]);
391
+ }
392
+ const openBraces = (lineText.match(/\{/g) || []).length;
393
+ const excessCloses = (lineText.match(/\}/g) || []).length - openBraces;
394
+ for (let i = 0; i < excessCloses && scopeStack.length > 0; i++) scopeStack.pop();
395
+ }
396
+ const findTwoslashSpans = (element) => {
397
+ const results = [];
398
+ if (element.properties?.class && String(element.properties.class).includes("twoslash-hover")) results.push(element);
399
+ if (element.children) {
400
+ for (const child of element.children) if (child.type === "element") results.push(...findTwoslashSpans(child));
401
+ }
402
+ return results;
403
+ };
404
+ const twoslashSpans = findTwoslashSpans(codeElement);
405
+ for (const twoslashSpan of twoslashSpans) {
406
+ if (twoslashSpan.properties?.["data-api-processed"] === "true") continue;
407
+ const methodInfo = this.extractMethodInfoFromTwoslashTooltip(twoslashSpan);
408
+ if (!methodInfo) continue;
409
+ const { className, methodName } = methodInfo;
410
+ const fullMemberName = `${className}.${methodName}`;
411
+ const memberRoute = apiItemRoutes.get(fullMemberName);
412
+ if (!memberRoute) continue;
413
+ const memberKind = apiItemKinds.get(fullMemberName);
414
+ const memberSemanticClass = memberKind ? this.getSemanticClass(memberKind) : null;
415
+ const memberClassNames = ["api-type-link"];
416
+ if (memberSemanticClass) memberClassNames.push(memberSemanticClass);
417
+ const textContent = this.extractTextFromTwoslash(twoslashSpan);
418
+ if (!textContent) continue;
419
+ this.wrapTwoslashTextInAnchor(twoslashSpan, textContent.trim(), memberRoute, memberClassNames);
420
+ twoslashSpan.properties = {
421
+ ...twoslashSpan.properties,
422
+ "data-api-processed": "true"
423
+ };
424
+ }
425
+ const typeNames = Array.from(apiItemRoutes.keys()).filter((name) => !name.includes(".")).sort((a, b) => b.length - a.length);
426
+ if (typeNames.length > 0) {
427
+ for (const twoslashSpan of twoslashSpans) {
428
+ if (twoslashSpan.properties?.["data-api-processed"] === "true") continue;
429
+ const text = this.extractTextFromTwoslash(twoslashSpan);
430
+ if (!text) continue;
431
+ const content = text.trim();
432
+ const route = apiItemRoutes.get(content);
433
+ if (!route) continue;
434
+ const kind = apiItemKinds.get(content);
435
+ const semanticClass = kind ? this.getSemanticClass(kind) : null;
436
+ const classNames = ["api-type-link"];
437
+ if (semanticClass) classNames.push(semanticClass);
438
+ this.wrapTwoslashTextInAnchor(twoslashSpan, content, route, classNames);
439
+ twoslashSpan.properties = {
440
+ ...twoslashSpan.properties,
441
+ "data-api-processed": "true"
442
+ };
443
+ }
444
+ const escapedNames = typeNames.map((n) => n.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
445
+ const typePattern = new RegExp(`\\b(${escapedNames.join("|")})\\b`, "g");
446
+ for (const lineElement of codeElement.children) {
447
+ if (lineElement.type !== "element" || lineElement.tagName !== "span") continue;
448
+ this.linkTypeReferencesInLine(lineElement, typePattern, apiItemRoutes, apiItemKinds);
449
+ }
450
+ }
451
+ return node;
452
+ }
453
+ /**
454
+ * Transform a line element
455
+ */
456
+ transformLine(node) {
457
+ const apiItemRoutes = this.getRoutesForCurrentScope();
458
+ const apiItemKinds = this.getKindsForCurrentScope();
459
+ const classMembersMap = this.getClassMembersForCurrentScope();
460
+ if (!node.children) return;
461
+ for (let i = 0; i < node.children.length; i++) {
462
+ const child = node.children[i];
463
+ if (child.type !== "element" || child.tagName !== "span") continue;
464
+ if (child.properties?.["data-api-processed"] === "true") continue;
465
+ const content = this.extractTextFromTwoslash(child);
466
+ if (!content) continue;
467
+ const trimmedContent = content.trim();
468
+ if (!apiItemRoutes.get(trimmedContent) || !classMembersMap.has(trimmedContent)) continue;
469
+ const nextChild = node.children[i + 1];
470
+ if (nextChild?.type !== "element" || nextChild.tagName !== "span") continue;
471
+ const nextContent = this.extractTextFromTwoslash(nextChild);
472
+ if (!nextContent) continue;
473
+ const nextValue = nextContent.trim();
474
+ if (!nextValue.startsWith(".")) continue;
475
+ const methodSpanIndex = nextValue === "." ? i + 2 : i + 1;
476
+ let methodSpan = null;
477
+ let methodText = null;
478
+ if (nextValue === ".") {
479
+ const possibleMethodSpan = node.children[i + 2];
480
+ if (possibleMethodSpan && possibleMethodSpan.type === "element") {
481
+ methodSpan = possibleMethodSpan;
482
+ methodText = this.extractTextFromTwoslash(methodSpan);
483
+ }
484
+ } else if (nextValue.startsWith(".")) {
485
+ methodSpan = nextChild;
486
+ methodText = nextValue.substring(1);
487
+ }
488
+ if (!methodText || !methodSpan) continue;
489
+ const matchedMember = classMembersMap.get(trimmedContent)?.find((member) => methodText.startsWith(member));
490
+ if (!matchedMember) continue;
491
+ const fullMemberName = `${trimmedContent}.${matchedMember}`;
492
+ const memberRoute = apiItemRoutes.get(fullMemberName);
493
+ if (!memberRoute) continue;
494
+ const memberKind = apiItemKinds.get(fullMemberName);
495
+ const memberSemanticClass = memberKind ? this.getSemanticClass(memberKind) : null;
496
+ const memberClassNames = ["api-type-link"];
497
+ if (memberSemanticClass) memberClassNames.push(memberSemanticClass);
498
+ if (methodSpan.properties?.class && String(methodSpan.properties.class).includes("twoslash-hover")) this.wrapTwoslashTextInAnchor(methodSpan, methodText.trim(), memberRoute, memberClassNames);
499
+ else {
500
+ const textNode = methodSpan.children.find((c) => c.type === "text");
501
+ if (textNode && textNode.type === "text") {
502
+ const leadingSpace = textNode.value.match(/^\s*/)?.[0] || "";
503
+ const trailingSpace = textNode.value.match(/\s*$/)?.[0] || "";
504
+ const newChildren = [];
505
+ if (leadingSpace) newChildren.push({
506
+ type: "text",
507
+ value: leadingSpace
508
+ });
509
+ newChildren.push({
510
+ type: "element",
511
+ tagName: "a",
512
+ properties: {
513
+ href: memberRoute,
514
+ class: memberClassNames.join(" ")
515
+ },
516
+ children: [{
517
+ type: "text",
518
+ value: methodText.trim()
519
+ }]
520
+ });
521
+ if (trailingSpace) newChildren.push({
522
+ type: "text",
523
+ value: trailingSpace
524
+ });
525
+ methodSpan.children = newChildren;
526
+ }
527
+ }
528
+ child.properties = {
529
+ ...child.properties,
530
+ "data-api-processed": "true"
531
+ };
532
+ methodSpan.properties = {
533
+ ...methodSpan.properties,
534
+ "data-api-processed": "true"
535
+ };
536
+ if (methodSpanIndex > i + 1) i = methodSpanIndex;
537
+ else i++;
538
+ }
539
+ }
540
+ /**
541
+ * Transform a span element
542
+ */
543
+ transformSpan(node, _line, _col) {
544
+ const apiItemRoutes = this.getRoutesForCurrentScope();
545
+ const apiItemKinds = this.getKindsForCurrentScope();
546
+ if (node.properties?.["data-api-processed"] === "true") return;
547
+ const firstChild = node.children?.[0];
548
+ if (firstChild && firstChild.type === "element" && firstChild.tagName === "a") return;
549
+ if (firstChild && firstChild.type === "element" && firstChild.properties?.class && String(firstChild.properties.class).includes("twoslash-hover") && firstChild.type === "element") {
550
+ const text = this.extractTextFromTwoslash(firstChild);
551
+ if (!text) return;
552
+ const content = text.trim();
553
+ if (!content) return;
554
+ const route = apiItemRoutes.get(content);
555
+ if (route) {
556
+ const kind = apiItemKinds.get(content);
557
+ const semanticClass = kind ? this.getSemanticClass(kind) : null;
558
+ const classNames = ["api-type-link", "rp-link"];
559
+ if (semanticClass) classNames.push(semanticClass);
560
+ this.wrapTwoslashTextInAnchor(firstChild, content, route, classNames);
561
+ node.properties = {
562
+ ...node.properties,
563
+ "data-api-processed": "true"
564
+ };
565
+ }
566
+ return;
567
+ }
568
+ const textChild = node.children[0];
569
+ if (textChild?.type !== "text") return;
570
+ const rawContent = textChild.value;
571
+ const content = rawContent.trim();
572
+ if (!content) return;
573
+ const route = apiItemRoutes.get(content);
574
+ if (route) {
575
+ const kind = apiItemKinds.get(content);
576
+ const semanticClass = kind ? this.getSemanticClass(kind) : null;
577
+ const leadingSpace = rawContent.match(/^\s*/)?.[0] || "";
578
+ const trailingSpace = rawContent.match(/\s*$/)?.[0] || "";
579
+ const classNames = ["api-type-link", "rp-link"];
580
+ if (semanticClass) classNames.push(semanticClass);
581
+ const newChildren = [];
582
+ if (leadingSpace) newChildren.push({
583
+ type: "text",
584
+ value: leadingSpace
585
+ });
586
+ newChildren.push({
587
+ type: "element",
588
+ tagName: "a",
589
+ properties: {
590
+ href: route,
591
+ class: classNames.join(" ")
592
+ },
593
+ children: [{
594
+ type: "text",
595
+ value: content
596
+ }]
597
+ });
598
+ if (trailingSpace) newChildren.push({
599
+ type: "text",
600
+ value: trailingSpace
601
+ });
602
+ node.children = newChildren;
603
+ }
604
+ }
605
+ /**
606
+ * Helper to extract text content from potentially Twoslash-wrapped elements
607
+ * Twoslash wraps identifiers in: `<span class="twoslash-hover"><span class="twoslash-popup-container">...</span>text</span>`
608
+ */
609
+ extractTextFromTwoslash(element) {
610
+ if (element.type !== "element") return null;
611
+ if (element.tagName === "span" && element.properties?.class && String(element.properties.class).includes("twoslash-hover")) {
612
+ for (const child of element.children) if (child.type === "text") return child.value;
613
+ return null;
614
+ }
615
+ if (element.children && element.children.length === 1 && element.children[0].type === "text") return element.children[0].value;
616
+ const anchor = element.children.find((c) => c.type === "element" && c.tagName === "a");
617
+ if (anchor && anchor.type === "element") {
618
+ const text = anchor.children.find((c) => c.type === "text");
619
+ if (text && text.type === "text") return text.value;
620
+ }
621
+ return null;
622
+ }
623
+ /**
624
+ * Helper to wrap text in Twoslash hover span with an anchor
625
+ */
626
+ wrapTwoslashTextInAnchor(element, text, href, classNames) {
627
+ for (let i = 0; i < element.children.length; i++) {
628
+ const child = element.children[i];
629
+ if (child.type === "text" && child.value.trim() === text.trim()) {
630
+ element.children[i] = {
631
+ type: "element",
632
+ tagName: "a",
633
+ properties: {
634
+ href,
635
+ class: classNames.join(" ")
636
+ },
637
+ children: [{
638
+ type: "text",
639
+ value: text
640
+ }]
641
+ };
642
+ return;
643
+ }
644
+ }
645
+ }
646
+ /**
647
+ * Helper to extract class and method information from Twoslash tooltip.
648
+ * Returns an object with className and methodName if found, null otherwise.
649
+ */
650
+ extractMethodInfoFromTwoslashTooltip(element) {
651
+ if (element.tagName !== "span") return null;
652
+ if (!(element.properties?.class && String(element.properties.class).includes("twoslash-hover"))) return null;
653
+ const popupContainer = element.children.find((c) => c.type === "element" && c.properties?.class?.toString().includes("twoslash-popup-container"));
654
+ if (popupContainer?.type !== "element") return null;
655
+ const codeElement = popupContainer.children.find((c) => c.type === "element" && c.tagName === "code");
656
+ if (codeElement?.type !== "element") return null;
657
+ const getText = (node) => {
658
+ if (node.type === "text") return node.value;
659
+ if (node.type === "element") return node.children.map(getText).join("");
660
+ return "";
661
+ };
662
+ const match = codeElement.children.map(getText).join("").match(/^(?:\([^)]+\)\s+)?(?:(?:function|interface|class|enum|type|namespace|const|let|var)\s+)?([A-Z]\w+)\.(\w+)[(:]/);
663
+ if (match) return {
664
+ className: match[1],
665
+ methodName: match[2]
666
+ };
667
+ return null;
668
+ }
669
+ /**
670
+ * Link type references in regular text nodes within a line element.
671
+ * Iterates child spans, skipping already-processed and Twoslash-containing spans,
672
+ * and splits text nodes at type name boundaries.
673
+ */
674
+ linkTypeReferencesInLine(lineElement, typePattern, apiItemRoutes, apiItemKinds) {
675
+ for (const child of lineElement.children) {
676
+ if (child.type !== "element" || child.tagName !== "span") continue;
677
+ if (child.properties?.["data-api-processed"] === "true") continue;
678
+ if (child.children.some((c) => c.type === "element" && c.properties?.class && String(c.properties.class).includes("twoslash"))) continue;
679
+ let modified = false;
680
+ const newChildren = [];
681
+ for (const textChild of child.children) {
682
+ if (textChild.type !== "text") {
683
+ newChildren.push(textChild);
684
+ continue;
685
+ }
686
+ const fragments = this.splitTextAtTypeReferences(textChild.value, typePattern, apiItemRoutes, apiItemKinds);
687
+ if (fragments.length === 1 && fragments[0].type === "text") newChildren.push(textChild);
688
+ else {
689
+ newChildren.push(...fragments);
690
+ modified = true;
691
+ }
692
+ }
693
+ if (modified) {
694
+ child.children = newChildren;
695
+ child.properties = {
696
+ ...child.properties,
697
+ "data-api-processed": "true"
698
+ };
699
+ }
700
+ }
701
+ }
702
+ /**
703
+ * Split a text string at type reference boundaries, returning an array of
704
+ * text nodes and anchor elements for matched type names.
705
+ */
706
+ splitTextAtTypeReferences(text, typePattern, apiItemRoutes, apiItemKinds) {
707
+ typePattern.lastIndex = 0;
708
+ const result = [];
709
+ let lastIndex = 0;
710
+ for (let match = typePattern.exec(text); match !== null; match = typePattern.exec(text)) {
711
+ const matchedName = match[1];
712
+ const route = apiItemRoutes.get(matchedName);
713
+ if (!route) continue;
714
+ if (match.index > lastIndex) result.push({
715
+ type: "text",
716
+ value: text.slice(lastIndex, match.index)
717
+ });
718
+ const kind = apiItemKinds.get(matchedName);
719
+ const semanticClass = kind ? this.getSemanticClass(kind) : null;
720
+ const classNames = ["api-type-link"];
721
+ if (semanticClass) classNames.push(semanticClass);
722
+ result.push({
723
+ type: "element",
724
+ tagName: "a",
725
+ properties: {
726
+ href: route,
727
+ class: classNames.join(" ")
728
+ },
729
+ children: [{
730
+ type: "text",
731
+ value: matchedName
732
+ }]
733
+ });
734
+ lastIndex = match.index + match[0].length;
735
+ }
736
+ if (result.length === 0) return [{
737
+ type: "text",
738
+ value: text
739
+ }];
740
+ if (lastIndex < text.length) result.push({
741
+ type: "text",
742
+ value: text.slice(lastIndex)
743
+ });
744
+ return result;
745
+ }
746
+ /**
747
+ * Get the semantic CSS class name for an API item kind.
748
+ *
749
+ * @deprecated Semantic token colors are now handled by Shiki's theme CSS variables.
750
+ * This method always returns null - only api-type-link is used for underline styling.
751
+ */
752
+ getSemanticClass(_kind) {
753
+ return null;
754
+ }
755
+ };
756
+
757
+ //#endregion
758
+ export { ShikiCrossLinker };