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.
@@ -33,7 +33,7 @@
33
33
  * @example Basic usage
34
34
  * ```ts
35
35
  * const crossLinker = new ShikiCrossLinker();
36
- * crossLinker.reinitialize(routes, kinds, "my-api");
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, apiItemKinds, classMembersMap) {
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, kinds, apiScope) {
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), new Map(kinds), classMembersMap);
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(), /* @__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
- const kind = apiItemKinds.get(content);
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, apiItemKinds);
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
- const kind = apiItemKinds.get(content);
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, apiItemKinds) {
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, apiItemKinds);
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, apiItemKinds) {
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: classNames.join(" ")
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
@@ -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, toProgrammaticCompilerOptions };
421
+ export { TwoslashEnvironmentRegistry, addTypeRoutes, clearTypeRoutes };