rspress-plugin-api-extractor 0.13.2 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build-program.js +1 -1
- package/build-stages.js +1 -2
- package/index.d.ts +5 -28
- package/layers/config-resolution.js +3 -4
- package/layers/type-environment.js +5 -3
- package/markdown/helpers.js +1 -2
- package/markdown/page-generators/class-page.js +1 -2
- package/markdown/page-generators/function-page.js +1 -2
- package/markdown/page-generators/index-pages.js +1 -1
- package/markdown/page-generators/interface-page.js +1 -2
- package/markdown/page-generators/namespace-page.js +1 -2
- package/markdown/page-generators/type-alias-page.js +1 -2
- package/markdown/page-generators/variable-page.js +1 -2
- package/observability/sinks/console-sink.js +1 -2
- package/package.json +9 -8
- package/plugin.js +1 -9
- package/schemas/config.js +2 -11
- package/schemas/observability.js +8 -21
- package/schemas/performance.js +1 -6
- package/services/TwoslashCacheService.js +17 -13
- package/services/TypeRegistryService.js +4 -4
- package/shiki-transformer.js +12 -51
- package/twoslash-transformer.js +2 -47
- package/api-extracted-package.js +0 -471
- package/frontmatter.js +0 -176
- package/tsconfig-parser.js +0 -115
- package/type-reference-extractor.js +0 -199
- package/typescript-config.js +0 -170
package/shiki-transformer.js
CHANGED
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
* @example Basic usage
|
|
34
34
|
* ```ts
|
|
35
35
|
* const crossLinker = new ShikiCrossLinker();
|
|
36
|
-
* crossLinker.
|
|
36
|
+
* const crossLinker = ShikiCrossLinker.fromRoutes(routes, "my-api");
|
|
37
37
|
* crossLinker.setApiScope("my-api");
|
|
38
38
|
*
|
|
39
39
|
* // Cross-link the finalized HAST, after Shiki and Twoslash have run
|
|
@@ -46,16 +46,13 @@
|
|
|
46
46
|
var ShikiCrossLinker = class ShikiCrossLinker {
|
|
47
47
|
/** API item name to route, for THIS scope. */
|
|
48
48
|
apiItemRoutes;
|
|
49
|
-
/** API item name to kind (Class, Interface, …), for THIS scope. */
|
|
50
|
-
apiItemKinds;
|
|
51
49
|
/** Parent name to its member names, longest first, for THIS scope. */
|
|
52
50
|
classMembersMap;
|
|
53
51
|
/** The scope this linker links for. Read-only after construction. */
|
|
54
52
|
apiScope;
|
|
55
|
-
constructor(apiScope, apiItemRoutes,
|
|
53
|
+
constructor(apiScope, apiItemRoutes, classMembersMap) {
|
|
56
54
|
this.apiScope = apiScope;
|
|
57
55
|
this.apiItemRoutes = apiItemRoutes;
|
|
58
|
-
this.apiItemKinds = apiItemKinds;
|
|
59
56
|
this.classMembersMap = classMembersMap;
|
|
60
57
|
}
|
|
61
58
|
/**
|
|
@@ -74,7 +71,7 @@ var ShikiCrossLinker = class ShikiCrossLinker {
|
|
|
74
71
|
* property of the instance, which cannot be pointed at another package's
|
|
75
72
|
* routes at all.
|
|
76
73
|
*/
|
|
77
|
-
static fromRoutes(routes,
|
|
74
|
+
static fromRoutes(routes, apiScope) {
|
|
78
75
|
const classMembersMap = /* @__PURE__ */ new Map();
|
|
79
76
|
for (const [name] of routes.entries()) {
|
|
80
77
|
const dotIndex = name.indexOf(".");
|
|
@@ -86,10 +83,10 @@ var ShikiCrossLinker = class ShikiCrossLinker {
|
|
|
86
83
|
else if (!members.includes(memberName)) members.push(memberName);
|
|
87
84
|
}
|
|
88
85
|
for (const members of classMembersMap.values()) members.sort((a, b) => b.length - a.length);
|
|
89
|
-
return new ShikiCrossLinker(apiScope, new Map(routes),
|
|
86
|
+
return new ShikiCrossLinker(apiScope, new Map(routes), classMembersMap);
|
|
90
87
|
}
|
|
91
88
|
/** A linker that links nothing — for a scope with no documented routes. */
|
|
92
|
-
static empty = new ShikiCrossLinker("", /* @__PURE__ */ new Map(), /* @__PURE__ */ new Map()
|
|
89
|
+
static empty = new ShikiCrossLinker("", /* @__PURE__ */ new Map(), /* @__PURE__ */ new Map());
|
|
93
90
|
/**
|
|
94
91
|
* Transform a finalized HAST tree to add cross-links to type references.
|
|
95
92
|
*
|
|
@@ -118,7 +115,6 @@ var ShikiCrossLinker = class ShikiCrossLinker {
|
|
|
118
115
|
}
|
|
119
116
|
transformRoot(node) {
|
|
120
117
|
const apiItemRoutes = this.apiItemRoutes;
|
|
121
|
-
const apiItemKinds = this.apiItemKinds;
|
|
122
118
|
const classMembersMap = this.classMembersMap;
|
|
123
119
|
const scopeStack = [];
|
|
124
120
|
const preElement = node.children.find((child) => child.type === "element" && child.tagName === "pre");
|
|
@@ -148,12 +144,9 @@ var ShikiCrossLinker = class ShikiCrossLinker {
|
|
|
148
144
|
const fullMemberName = `${currentScope}.${content}`;
|
|
149
145
|
const memberRoute = apiItemRoutes.get(fullMemberName);
|
|
150
146
|
if (memberRoute) {
|
|
151
|
-
const memberKind = apiItemKinds.get(fullMemberName);
|
|
152
|
-
const memberSemanticClass = memberKind ? this.getSemanticClass(memberKind) : null;
|
|
153
147
|
const leadingSpace = rawContent.match(/^\s*/)?.[0] || "";
|
|
154
148
|
const trailingSpace = rawContent.match(/\s*$/)?.[0] || "";
|
|
155
149
|
const classNames = ["api-type-link"];
|
|
156
|
-
if (memberSemanticClass) classNames.push(memberSemanticClass);
|
|
157
150
|
const newChildren = [];
|
|
158
151
|
if (leadingSpace) newChildren.push({
|
|
159
152
|
type: "text",
|
|
@@ -209,10 +202,7 @@ var ShikiCrossLinker = class ShikiCrossLinker {
|
|
|
209
202
|
const fullMemberName = `${className}.${methodName}`;
|
|
210
203
|
const memberRoute = apiItemRoutes.get(fullMemberName);
|
|
211
204
|
if (!memberRoute) continue;
|
|
212
|
-
const memberKind = apiItemKinds.get(fullMemberName);
|
|
213
|
-
const memberSemanticClass = memberKind ? this.getSemanticClass(memberKind) : null;
|
|
214
205
|
const memberClassNames = ["api-type-link"];
|
|
215
|
-
if (memberSemanticClass) memberClassNames.push(memberSemanticClass);
|
|
216
206
|
const textContent = this.extractTextFromTwoslash(twoslashSpan);
|
|
217
207
|
if (!textContent) continue;
|
|
218
208
|
this.wrapTwoslashTextInAnchor(twoslashSpan, textContent.trim(), memberRoute, memberClassNames);
|
|
@@ -230,11 +220,7 @@ var ShikiCrossLinker = class ShikiCrossLinker {
|
|
|
230
220
|
const content = text.trim();
|
|
231
221
|
const route = apiItemRoutes.get(content);
|
|
232
222
|
if (!route) continue;
|
|
233
|
-
|
|
234
|
-
const semanticClass = kind ? this.getSemanticClass(kind) : null;
|
|
235
|
-
const classNames = ["api-type-link"];
|
|
236
|
-
if (semanticClass) classNames.push(semanticClass);
|
|
237
|
-
this.wrapTwoslashTextInAnchor(twoslashSpan, content, route, classNames);
|
|
223
|
+
this.wrapTwoslashTextInAnchor(twoslashSpan, content, route, ["api-type-link"]);
|
|
238
224
|
twoslashSpan.properties = {
|
|
239
225
|
...twoslashSpan.properties,
|
|
240
226
|
"data-api-processed": "true"
|
|
@@ -244,7 +230,7 @@ var ShikiCrossLinker = class ShikiCrossLinker {
|
|
|
244
230
|
const typePattern = new RegExp(`\\b(${escapedNames.join("|")})\\b`, "g");
|
|
245
231
|
for (const lineElement of codeElement.children) {
|
|
246
232
|
if (lineElement.type !== "element" || lineElement.tagName !== "span") continue;
|
|
247
|
-
this.linkTypeReferencesInLine(lineElement, typePattern, apiItemRoutes
|
|
233
|
+
this.linkTypeReferencesInLine(lineElement, typePattern, apiItemRoutes);
|
|
248
234
|
}
|
|
249
235
|
}
|
|
250
236
|
return node;
|
|
@@ -254,7 +240,6 @@ var ShikiCrossLinker = class ShikiCrossLinker {
|
|
|
254
240
|
*/
|
|
255
241
|
transformLine(node) {
|
|
256
242
|
const apiItemRoutes = this.apiItemRoutes;
|
|
257
|
-
const apiItemKinds = this.apiItemKinds;
|
|
258
243
|
const classMembersMap = this.classMembersMap;
|
|
259
244
|
if (!node.children) return;
|
|
260
245
|
for (let i = 0; i < node.children.length; i++) {
|
|
@@ -290,10 +275,7 @@ var ShikiCrossLinker = class ShikiCrossLinker {
|
|
|
290
275
|
const fullMemberName = `${trimmedContent}.${matchedMember}`;
|
|
291
276
|
const memberRoute = apiItemRoutes.get(fullMemberName);
|
|
292
277
|
if (!memberRoute) continue;
|
|
293
|
-
const memberKind = apiItemKinds.get(fullMemberName);
|
|
294
|
-
const memberSemanticClass = memberKind ? this.getSemanticClass(memberKind) : null;
|
|
295
278
|
const memberClassNames = ["api-type-link"];
|
|
296
|
-
if (memberSemanticClass) memberClassNames.push(memberSemanticClass);
|
|
297
279
|
if (methodSpan.properties?.class && String(methodSpan.properties.class).includes("twoslash-hover")) this.wrapTwoslashTextInAnchor(methodSpan, methodText.trim(), memberRoute, memberClassNames);
|
|
298
280
|
else {
|
|
299
281
|
const textNode = methodSpan.children.find((c) => c.type === "text");
|
|
@@ -341,7 +323,6 @@ var ShikiCrossLinker = class ShikiCrossLinker {
|
|
|
341
323
|
*/
|
|
342
324
|
transformSpan(node, _line, _col) {
|
|
343
325
|
const apiItemRoutes = this.apiItemRoutes;
|
|
344
|
-
const apiItemKinds = this.apiItemKinds;
|
|
345
326
|
if (node.properties?.["data-api-processed"] === "true") return;
|
|
346
327
|
const firstChild = node.children?.[0];
|
|
347
328
|
if (firstChild && firstChild.type === "element" && firstChild.tagName === "a") return;
|
|
@@ -352,11 +333,7 @@ var ShikiCrossLinker = class ShikiCrossLinker {
|
|
|
352
333
|
if (!content) return;
|
|
353
334
|
const route = apiItemRoutes.get(content);
|
|
354
335
|
if (route) {
|
|
355
|
-
|
|
356
|
-
const semanticClass = kind ? this.getSemanticClass(kind) : null;
|
|
357
|
-
const classNames = ["api-type-link", "rp-link"];
|
|
358
|
-
if (semanticClass) classNames.push(semanticClass);
|
|
359
|
-
this.wrapTwoslashTextInAnchor(firstChild, content, route, classNames);
|
|
336
|
+
this.wrapTwoslashTextInAnchor(firstChild, content, route, ["api-type-link", "rp-link"]);
|
|
360
337
|
node.properties = {
|
|
361
338
|
...node.properties,
|
|
362
339
|
"data-api-processed": "true"
|
|
@@ -371,12 +348,9 @@ var ShikiCrossLinker = class ShikiCrossLinker {
|
|
|
371
348
|
if (!content) return;
|
|
372
349
|
const route = apiItemRoutes.get(content);
|
|
373
350
|
if (route) {
|
|
374
|
-
const kind = apiItemKinds.get(content);
|
|
375
|
-
const semanticClass = kind ? this.getSemanticClass(kind) : null;
|
|
376
351
|
const leadingSpace = rawContent.match(/^\s*/)?.[0] || "";
|
|
377
352
|
const trailingSpace = rawContent.match(/\s*$/)?.[0] || "";
|
|
378
353
|
const classNames = ["api-type-link", "rp-link"];
|
|
379
|
-
if (semanticClass) classNames.push(semanticClass);
|
|
380
354
|
const newChildren = [];
|
|
381
355
|
if (leadingSpace) newChildren.push({
|
|
382
356
|
type: "text",
|
|
@@ -470,7 +444,7 @@ var ShikiCrossLinker = class ShikiCrossLinker {
|
|
|
470
444
|
* Iterates child spans, skipping already-processed and Twoslash-containing spans,
|
|
471
445
|
* and splits text nodes at type name boundaries.
|
|
472
446
|
*/
|
|
473
|
-
linkTypeReferencesInLine(lineElement, typePattern, apiItemRoutes
|
|
447
|
+
linkTypeReferencesInLine(lineElement, typePattern, apiItemRoutes) {
|
|
474
448
|
for (const child of lineElement.children) {
|
|
475
449
|
if (child.type !== "element" || child.tagName !== "span") continue;
|
|
476
450
|
if (child.properties?.["data-api-processed"] === "true") continue;
|
|
@@ -482,7 +456,7 @@ var ShikiCrossLinker = class ShikiCrossLinker {
|
|
|
482
456
|
newChildren.push(textChild);
|
|
483
457
|
continue;
|
|
484
458
|
}
|
|
485
|
-
const fragments = this.splitTextAtTypeReferences(textChild.value, typePattern, apiItemRoutes
|
|
459
|
+
const fragments = this.splitTextAtTypeReferences(textChild.value, typePattern, apiItemRoutes);
|
|
486
460
|
if (fragments.length === 1 && fragments[0].type === "text") newChildren.push(textChild);
|
|
487
461
|
else {
|
|
488
462
|
newChildren.push(...fragments);
|
|
@@ -502,7 +476,7 @@ var ShikiCrossLinker = class ShikiCrossLinker {
|
|
|
502
476
|
* Split a text string at type reference boundaries, returning an array of
|
|
503
477
|
* text nodes and anchor elements for matched type names.
|
|
504
478
|
*/
|
|
505
|
-
splitTextAtTypeReferences(text, typePattern, apiItemRoutes
|
|
479
|
+
splitTextAtTypeReferences(text, typePattern, apiItemRoutes) {
|
|
506
480
|
typePattern.lastIndex = 0;
|
|
507
481
|
const result = [];
|
|
508
482
|
let lastIndex = 0;
|
|
@@ -514,16 +488,12 @@ var ShikiCrossLinker = class ShikiCrossLinker {
|
|
|
514
488
|
type: "text",
|
|
515
489
|
value: text.slice(lastIndex, match.index)
|
|
516
490
|
});
|
|
517
|
-
const kind = apiItemKinds.get(matchedName);
|
|
518
|
-
const semanticClass = kind ? this.getSemanticClass(kind) : null;
|
|
519
|
-
const classNames = ["api-type-link"];
|
|
520
|
-
if (semanticClass) classNames.push(semanticClass);
|
|
521
491
|
result.push({
|
|
522
492
|
type: "element",
|
|
523
493
|
tagName: "a",
|
|
524
494
|
properties: {
|
|
525
495
|
href: route,
|
|
526
|
-
class:
|
|
496
|
+
class: ["api-type-link"].join(" ")
|
|
527
497
|
},
|
|
528
498
|
children: [{
|
|
529
499
|
type: "text",
|
|
@@ -542,15 +512,6 @@ var ShikiCrossLinker = class ShikiCrossLinker {
|
|
|
542
512
|
});
|
|
543
513
|
return result;
|
|
544
514
|
}
|
|
545
|
-
/**
|
|
546
|
-
* Get the semantic CSS class name for an API item kind.
|
|
547
|
-
*
|
|
548
|
-
* @deprecated Semantic token colors are now handled by Shiki's theme CSS variables.
|
|
549
|
-
* This method always returns null - only api-type-link is used for underline styling.
|
|
550
|
-
*/
|
|
551
|
-
getSemanticClass(_kind) {
|
|
552
|
-
return null;
|
|
553
|
-
}
|
|
554
515
|
};
|
|
555
516
|
|
|
556
517
|
//#endregion
|
package/twoslash-transformer.js
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import { PluginEvent } from "./observability/events.js";
|
|
2
2
|
import { emitSync, syncBuildId } from "./observability/sync-emitter.js";
|
|
3
|
-
import { DEFAULT_COMPILER_OPTIONS } from "./typescript-config.js";
|
|
4
3
|
import { Result } from "effect";
|
|
5
4
|
import { Markdown, Mdast } from "@effected/markdown";
|
|
6
|
-
import { TsEnumCodec } from "@effected/tsconfig-json";
|
|
7
5
|
import { rendererRich, transformerTwoslash } from "@shikijs/twoslash";
|
|
6
|
+
import { DEFAULT_COMPILER_OPTIONS, toProgrammaticCompilerOptions } from "@tsdoctor/vfs";
|
|
8
7
|
import { toHast } from "mdast-util-to-hast";
|
|
9
8
|
|
|
10
9
|
//#region src/twoslash-transformer.ts
|
|
@@ -181,47 +180,6 @@ function renderMarkdownInline(markdown, context) {
|
|
|
181
180
|
}];
|
|
182
181
|
}
|
|
183
182
|
/**
|
|
184
|
-
* Singleton manager for the Twoslash transformer, enabling type-aware documentation.
|
|
185
|
-
*
|
|
186
|
-
* The TwoslashManager initializes and manages a Shiki transformer that provides
|
|
187
|
-
* TypeScript IntelliSense features (hover types, error highlighting, completions)
|
|
188
|
-
* in documentation code blocks. It uses a virtual file system (VFS) to provide
|
|
189
|
-
* type definitions without requiring actual file system access.
|
|
190
|
-
*
|
|
191
|
-
* **How it works:**
|
|
192
|
-
* 1. Plugin initializes the manager with a VFS containing all package type definitions
|
|
193
|
-
* 2. Code blocks marked with `twoslash` are processed by the transformer
|
|
194
|
-
* 3. TypeScript language services provide hover information and error checking
|
|
195
|
-
* 4. Results are rendered as HTML with interactive hover popups
|
|
196
|
-
*
|
|
197
|
-
* **VFS Integration:**
|
|
198
|
-
* The VFS is populated by {@link TypeRegistryService} with:
|
|
199
|
-
* - The documented package's own type definitions (from API Extractor)
|
|
200
|
-
* - External package types (fetched via @tsdoctor/registry)
|
|
201
|
-
*
|
|
202
|
-
* **Error Handling:**
|
|
203
|
-
* TypeScript errors in code blocks are captured (not thrown) and:
|
|
204
|
-
* - Counted via Effect Metric (BuildMetrics.twoslashErrors)
|
|
205
|
-
* - Logged inline via console.error
|
|
206
|
-
* - Displayed in the rendered output as error annotations
|
|
207
|
-
*
|
|
208
|
-
* **Relationships:**
|
|
209
|
-
* - Initialized by {@link ApiExtractorPlugin} in the beforeBuild hook
|
|
210
|
-
* - Receives VFS from {@link TypeRegistryService}
|
|
211
|
-
* - The transformer is used by page generators for rendering code blocks
|
|
212
|
-
*
|
|
213
|
-
* @example
|
|
214
|
-
* ```ts
|
|
215
|
-
* const manager = TwoslashManager.getInstance();
|
|
216
|
-
* manager.initialize(vfs, undefined, logger);
|
|
217
|
-
*
|
|
218
|
-
* const transformer = manager.getTransformer();
|
|
219
|
-
* // Use transformer with Shiki highlighter
|
|
220
|
-
* ```
|
|
221
|
-
*
|
|
222
|
-
* @see {@link TypeRegistryService} for VFS generation
|
|
223
|
-
*/
|
|
224
|
-
/**
|
|
225
183
|
* Fingerprint a compiler configuration so environments can be deduped and code
|
|
226
184
|
* blocks routed to the right one. Keys are sorted, so two configurations that
|
|
227
185
|
* differ only in property order share an environment.
|
|
@@ -244,9 +202,6 @@ function renderMarkdownInline(markdown, context) {
|
|
|
244
202
|
* environment, not the raw default), so a synthetic test compiling each
|
|
245
203
|
* resolution path through the real compiler is the only verification there is.
|
|
246
204
|
*/
|
|
247
|
-
function toProgrammaticCompilerOptions(options) {
|
|
248
|
-
return TsEnumCodec.encodeCompilerOptions(options);
|
|
249
|
-
}
|
|
250
205
|
/**
|
|
251
206
|
* Fingerprint a compiler configuration, for keying the environment map.
|
|
252
207
|
*
|
|
@@ -463,4 +418,4 @@ function clearTypeRoutes() {
|
|
|
463
418
|
}
|
|
464
419
|
|
|
465
420
|
//#endregion
|
|
466
|
-
export { TwoslashEnvironmentRegistry, addTypeRoutes, clearTypeRoutes
|
|
421
|
+
export { TwoslashEnvironmentRegistry, addTypeRoutes, clearTypeRoutes };
|