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,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 };
|