rspress-plugin-api-extractor 0.13.3 → 0.15.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/BuildEnv.js +0 -1
- package/build-program.js +5 -5
- package/build-stages.js +98 -282
- package/config-helpers.js +1 -1
- package/emit/mdx.js +311 -0
- package/emit/meta.js +62 -0
- package/index.d.ts +6 -70
- package/layers/build-metrics.js +1 -2
- package/layers/config-resolution.js +5 -8
- package/layers/type-environment.js +5 -4
- package/llms-program.js +3 -3
- package/markdown/helpers.js +10 -177
- package/observability/sinks/console-sink.js +1 -3
- package/observability/sinks/metrics-sink.js +0 -2
- package/package.json +9 -7
- package/path-derivation.js +1 -29
- package/plugin.js +3 -10
- package/prettier-formatter.js +27 -59
- package/remark-with-api.js +3 -2
- package/schemas/config.js +3 -29
- package/schemas/observability.js +8 -23
- package/schemas/performance.js +1 -7
- package/services/TwoslashCacheService.js +18 -14
- package/services/TypeRegistryService.js +4 -4
- package/shiki-transformer.js +12 -51
- package/twoslash-transformer.js +16 -49
- package/api-extracted-package.js +0 -471
- package/code-post-processor.js +0 -38
- package/frontmatter.js +0 -176
- package/llms-processing.js +0 -270
- package/markdown/page-generators/class-page.js +0 -364
- package/markdown/page-generators/enum-page.js +0 -152
- package/markdown/page-generators/function-page.js +0 -128
- package/markdown/page-generators/index-pages.js +0 -25
- package/markdown/page-generators/interface-page.js +0 -311
- package/markdown/page-generators/namespace-page.js +0 -278
- package/markdown/page-generators/type-alias-page.js +0 -111
- package/markdown/page-generators/variable-page.js +0 -111
- package/markdown/prose-linker.js +0 -22
- package/tsconfig-parser.js +0 -115
- package/twoslash-cache.js +0 -174
- package/twoslash-patterns.js +0 -87
- package/type-reference-extractor.js +0 -199
- package/typescript-config.js +0 -170
package/schemas/config.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { PerformanceConfig } from "./performance.js";
|
|
2
1
|
import { ObservabilityConfig } from "./observability.js";
|
|
3
2
|
import { Effect, Schema } from "effect";
|
|
4
3
|
import { OpenGraphImageConfig } from "@tsdoctor/seo";
|
|
@@ -10,24 +9,9 @@ import { ApiItemKind } from "@microsoft/api-extractor-model";
|
|
|
10
9
|
* an async loader function, or a URL.
|
|
11
10
|
*/
|
|
12
11
|
const ModelInput = Schema.declare((input) => typeof input === "string" || typeof input === "function" || input instanceof URL);
|
|
13
|
-
/**
|
|
14
|
-
* Verbosity level for plugin build output.
|
|
15
|
-
*
|
|
16
|
-
* @public
|
|
17
|
-
*/
|
|
18
|
-
const LogLevel = Schema.Literals([
|
|
19
|
-
"none",
|
|
20
|
-
"info",
|
|
21
|
-
"verbose",
|
|
22
|
-
"debug",
|
|
23
|
-
"warn",
|
|
24
|
-
"error"
|
|
25
|
-
]);
|
|
26
12
|
const ExternalPackageSpec = Schema.Struct({
|
|
27
13
|
name: Schema.String,
|
|
28
|
-
version: Schema.String
|
|
29
|
-
tsconfig: Schema.optional(ModelInput),
|
|
30
|
-
compilerOptions: Schema.optional(Schema.Unknown)
|
|
14
|
+
version: Schema.String
|
|
31
15
|
});
|
|
32
16
|
const AutoDetectDependencies = Schema.Struct({
|
|
33
17
|
dependencies: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(true))),
|
|
@@ -68,8 +52,6 @@ const CategoryConfig = Schema.Struct({
|
|
|
68
52
|
folderName: Schema.String,
|
|
69
53
|
/** API item kinds included in this category. */
|
|
70
54
|
itemKinds: Schema.optional(Schema.mutable(Schema.Array(ApiItemKindSchema))),
|
|
71
|
-
/** TSDoc modifier tag that marks items for this category. */
|
|
72
|
-
tsdocModifier: Schema.optional(Schema.String),
|
|
73
55
|
/** Whether the sidebar section is collapsible. Defaults to `true`. */
|
|
74
56
|
collapsible: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(true))),
|
|
75
57
|
/** Whether the sidebar section starts collapsed. Defaults to `true`. */
|
|
@@ -190,11 +172,7 @@ const VersionConfig = Schema.Struct({
|
|
|
190
172
|
/** Open Graph image configuration for this version. */
|
|
191
173
|
ogImage: Schema.optional(OpenGraphImageConfig),
|
|
192
174
|
/** LLMs integration options for this version. */
|
|
193
|
-
llmsPlugin: Schema.optional(LlmsPlugin)
|
|
194
|
-
/** Path to a `tsconfig.json` for this version. */
|
|
195
|
-
tsconfig: Schema.optional(ModelInput),
|
|
196
|
-
/** TypeScript compiler options for Twoslash. */
|
|
197
|
-
compilerOptions: Schema.optional(Schema.Unknown)
|
|
175
|
+
llmsPlugin: Schema.optional(LlmsPlugin)
|
|
198
176
|
});
|
|
199
177
|
/** Union for the versions record value: can be a path/function OR a full VersionConfig */
|
|
200
178
|
const VersionValue = Schema.Union([ModelInput, VersionConfig]);
|
|
@@ -312,13 +290,9 @@ const PluginOptions = Schema.Struct({
|
|
|
312
290
|
errors: Schema.optional(ErrorConfig),
|
|
313
291
|
/** LLMs integration options, or `false` to disable. */
|
|
314
292
|
llmsPlugin: Schema.optional(Schema.Union([Schema.Boolean, LlmsPlugin])),
|
|
315
|
-
/** Verbosity level for plugin build output. @deprecated Use `observability.logLevel`. */
|
|
316
|
-
logLevel: Schema.optional(LogLevel),
|
|
317
|
-
/** Performance tuning options. @deprecated Use `observability.thresholds`. */
|
|
318
|
-
performance: Schema.optional(PerformanceConfig),
|
|
319
293
|
/** Unified observability configuration (logLevel, trace artifact, thresholds). */
|
|
320
294
|
observability: Schema.optional(ObservabilityConfig)
|
|
321
295
|
});
|
|
322
296
|
|
|
323
297
|
//#endregion
|
|
324
|
-
export { AutoDetectDependencies, CategoryConfig, DEFAULT_CATEGORIES, ErrorConfig, ExternalPackageSpec, LlmsPlugin,
|
|
298
|
+
export { AutoDetectDependencies, CategoryConfig, DEFAULT_CATEGORIES, ErrorConfig, ExternalPackageSpec, LlmsPlugin, ModelInput, MultiApiConfig, PluginOptions, SingleApiConfig, SourceConfig, ThemeConfig, VersionConfig };
|
package/schemas/observability.js
CHANGED
|
@@ -22,7 +22,6 @@ const DEFAULT_THRESHOLDS = {
|
|
|
22
22
|
slowPageGeneration: 500,
|
|
23
23
|
slowApiLoad: 1e3,
|
|
24
24
|
slowFileOperation: 50,
|
|
25
|
-
slowHttpRequest: 2e3,
|
|
26
25
|
slowDbOperation: 100
|
|
27
26
|
};
|
|
28
27
|
function normalizeLevel(value) {
|
|
@@ -31,21 +30,11 @@ function normalizeLevel(value) {
|
|
|
31
30
|
return value;
|
|
32
31
|
}
|
|
33
32
|
function resolveObservability(input) {
|
|
34
|
-
const
|
|
35
|
-
if (input.logLevel !== void 0) deprecations.push({
|
|
36
|
-
key: "logLevel",
|
|
37
|
-
replacement: "observability.logLevel"
|
|
38
|
-
});
|
|
39
|
-
if (input.performance !== void 0) deprecations.push({
|
|
40
|
-
key: "performance",
|
|
41
|
-
replacement: "observability.thresholds"
|
|
42
|
-
});
|
|
43
|
-
const level = normalizeLevel(input.envLogLevel) ?? normalizeLevel(input.observability?.logLevel) ?? normalizeLevel(input.logLevel) ?? "info";
|
|
33
|
+
const level = normalizeLevel(input.envLogLevel) ?? normalizeLevel(input.observability?.logLevel) ?? "info";
|
|
44
34
|
const traceOpt = input.observability?.trace;
|
|
45
35
|
const tracePath = typeof traceOpt === "string" ? traceOpt : traceOpt === true ? `${input.cwd}/.api-docs/build/trace-${input.buildId}.jsonl` : null;
|
|
46
36
|
const merged = {
|
|
47
37
|
...DEFAULT_THRESHOLDS,
|
|
48
|
-
...input.performance?.thresholds ?? {},
|
|
49
38
|
...input.observability?.thresholds ?? {}
|
|
50
39
|
};
|
|
51
40
|
const thresholds = {
|
|
@@ -53,22 +42,18 @@ function resolveObservability(input) {
|
|
|
53
42
|
slowPageGeneration: merged.slowPageGeneration ?? DEFAULT_THRESHOLDS.slowPageGeneration,
|
|
54
43
|
slowApiLoad: merged.slowApiLoad ?? DEFAULT_THRESHOLDS.slowApiLoad,
|
|
55
44
|
slowFileOperation: merged.slowFileOperation ?? DEFAULT_THRESHOLDS.slowFileOperation,
|
|
56
|
-
slowHttpRequest: merged.slowHttpRequest ?? DEFAULT_THRESHOLDS.slowHttpRequest,
|
|
57
45
|
slowDbOperation: merged.slowDbOperation ?? DEFAULT_THRESHOLDS.slowDbOperation
|
|
58
46
|
};
|
|
59
47
|
const pi = input.observability?.progressInterval;
|
|
60
48
|
const seconds = pi === false ? null : typeof pi === "number" ? pi : 10;
|
|
61
49
|
const progressIntervalMs = seconds !== null && Number.isFinite(seconds) && seconds > 0 ? seconds * 1e3 : null;
|
|
62
|
-
return {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
},
|
|
70
|
-
deprecations
|
|
71
|
-
};
|
|
50
|
+
return { resolved: {
|
|
51
|
+
logLevel: level,
|
|
52
|
+
json: level === "debug",
|
|
53
|
+
tracePath,
|
|
54
|
+
progressIntervalMs,
|
|
55
|
+
thresholds
|
|
56
|
+
} };
|
|
72
57
|
}
|
|
73
58
|
|
|
74
59
|
//#endregion
|
package/schemas/performance.js
CHANGED
|
@@ -6,14 +6,8 @@ const PerformanceThresholds = Schema.Struct({
|
|
|
6
6
|
slowPageGeneration: Schema.Number.pipe(Schema.withDecodingDefault(Effect.succeed(500))),
|
|
7
7
|
slowApiLoad: Schema.Number.pipe(Schema.withDecodingDefault(Effect.succeed(1e3))),
|
|
8
8
|
slowFileOperation: Schema.Number.pipe(Schema.withDecodingDefault(Effect.succeed(50))),
|
|
9
|
-
slowHttpRequest: Schema.Number.pipe(Schema.withDecodingDefault(Effect.succeed(2e3))),
|
|
10
9
|
slowDbOperation: Schema.Number.pipe(Schema.withDecodingDefault(Effect.succeed(100)))
|
|
11
10
|
});
|
|
12
|
-
const PerformanceConfig = Schema.Struct({
|
|
13
|
-
thresholds: Schema.optional(PerformanceThresholds),
|
|
14
|
-
showInsights: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(true))),
|
|
15
|
-
trackDetailedMetrics: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(false)))
|
|
16
|
-
});
|
|
17
11
|
|
|
18
12
|
//#endregion
|
|
19
|
-
export {
|
|
13
|
+
export { PerformanceThresholds };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { AppDirsLive, PlatformLive } from "../layers/xdg.js";
|
|
2
|
-
import { decodeTwoslashCache, encodeTwoslashCache, makeTwoslashCache, twoslashBlobKey } from "../twoslash-cache.js";
|
|
3
2
|
import { Context, Effect, Layer, Option, Path } from "effect";
|
|
3
|
+
import { decodeTwoslashCache, encodeTwoslashCache, makeTwoslashCache, twoslashBlobKey } from "@tsdoctor/vfs";
|
|
4
4
|
import { Cache } from "@effected/store";
|
|
5
5
|
import { AppDirs } from "@effected/xdg";
|
|
6
6
|
|
|
@@ -20,23 +20,29 @@ var TwoslashCacheService = class TwoslashCacheService extends Context.Service()(
|
|
|
20
20
|
* @remarks
|
|
21
21
|
* Failure is absorbed at TWO levels, and both are load-bearing. Inside the
|
|
22
22
|
* service, a failed read or write degrades that one operation. Around the
|
|
23
|
-
* layer, a failed CONSTRUCTION — no HOME for XDG, an unwritable cache
|
|
24
|
-
* directory, a corrupt database — degrades to
|
|
23
|
+
* cache layer, a failed CONSTRUCTION — no HOME for XDG, an unwritable cache
|
|
24
|
+
* directory, a corrupt database — degrades to a cache that always misses,
|
|
25
|
+
* via `Cache.degrading` (see {@link CacheLive}).
|
|
25
26
|
*
|
|
26
|
-
* The second is why
|
|
27
|
-
*
|
|
28
|
-
*
|
|
27
|
+
* The second is why anything wraps this at all. While the sqlite layer was
|
|
28
|
+
* provided inside each method, a construction failure surfaced as that
|
|
29
|
+
* method's failure and the in-method handler swallowed it. Hoisting
|
|
29
30
|
* acquisition to layer construction moved the failure to `ManagedRuntime`
|
|
30
31
|
* build time, where it would abort the entire build — breaking the contract
|
|
31
32
|
* this service documents, that an unreachable cache must never fail a build
|
|
32
|
-
* that would otherwise succeed.
|
|
33
|
-
*
|
|
33
|
+
* that would otherwise succeed.
|
|
34
|
+
*
|
|
35
|
+
* There is no separate degraded implementation of this service any more.
|
|
36
|
+
* Degrading one level down, at the `Cache`, means the ordinary
|
|
37
|
+
* implementation running over an always-missing cache IS the degraded
|
|
38
|
+
* behaviour, so a second implementation would only be a way for the two to
|
|
39
|
+
* disagree.
|
|
34
40
|
*
|
|
35
41
|
* `Layer.suspend` because the composition below is declared after this class:
|
|
36
42
|
* a static initializer runs while the module body is still evaluating, so
|
|
37
43
|
* naming those consts directly throws at import time with a clean typecheck.
|
|
38
44
|
*/
|
|
39
|
-
static layer = Layer.suspend(() => CacheBackedLive
|
|
45
|
+
static layer = Layer.suspend(() => CacheBackedLive);
|
|
40
46
|
/**
|
|
41
47
|
* An always-cold in-memory double.
|
|
42
48
|
*
|
|
@@ -50,6 +56,7 @@ var TwoslashCacheService = class TwoslashCacheService extends Context.Service()(
|
|
|
50
56
|
* the render path's SHAPE rather than merely dropping its persistence.
|
|
51
57
|
*/
|
|
52
58
|
static makeTest = (overrides = {}) => ({
|
|
59
|
+
degraded: overrides.degraded ?? false,
|
|
53
60
|
load: overrides.load ?? (() => Effect.succeed(/* @__PURE__ */ new Map())),
|
|
54
61
|
save: overrides.save ?? (() => Effect.void),
|
|
55
62
|
open: overrides.open ?? (() => Effect.succeed(makeTwoslashCache())),
|
|
@@ -117,6 +124,7 @@ function withGeneration(base) {
|
|
|
117
124
|
const CacheBackedLive = Layer.effect(TwoslashCacheService, Effect.gen(function* () {
|
|
118
125
|
const cache = yield* Cache;
|
|
119
126
|
return withGeneration({
|
|
127
|
+
degraded: cache.degraded,
|
|
120
128
|
load: (envHash) => cache.get(twoslashBlobKey(envHash)).pipe(Effect.map((entry) => Option.isSome(entry) ? decodeTwoslashCache(entry.value.value) : /* @__PURE__ */ new Map()), Effect.catch(() => Effect.succeed(/* @__PURE__ */ new Map()))),
|
|
121
129
|
save: (envHash, entries) => cache.set({
|
|
122
130
|
key: twoslashBlobKey(envHash),
|
|
@@ -124,7 +132,7 @@ const CacheBackedLive = Layer.effect(TwoslashCacheService, Effect.gen(function*
|
|
|
124
132
|
tags: ["twoslash"]
|
|
125
133
|
}).pipe(Effect.catch(() => Effect.void))
|
|
126
134
|
});
|
|
127
|
-
})).pipe(Layer.provide(CacheLive));
|
|
135
|
+
})).pipe(Layer.provide(Cache.degrading(CacheLive)));
|
|
128
136
|
/**
|
|
129
137
|
* A cache that holds nothing, for when the real one cannot be opened.
|
|
130
138
|
*
|
|
@@ -132,10 +140,6 @@ const CacheBackedLive = Layer.effect(TwoslashCacheService, Effect.gen(function*
|
|
|
132
140
|
* `load` returns empty and `save` discards, which is precisely the behaviour
|
|
133
141
|
* before this cache existed: type-check everything, persist nothing.
|
|
134
142
|
*/
|
|
135
|
-
const DegradedLive = Layer.succeed(TwoslashCacheService, withGeneration({
|
|
136
|
-
load: () => Effect.succeed(/* @__PURE__ */ new Map()),
|
|
137
|
-
save: () => Effect.void
|
|
138
|
-
}));
|
|
139
143
|
|
|
140
144
|
//#endregion
|
|
141
145
|
export { TwoslashCacheService };
|
|
@@ -3,11 +3,11 @@ import { emit } from "../observability/EventBus.js";
|
|
|
3
3
|
import { resolveExternalPackageVersions } from "../config-utils.js";
|
|
4
4
|
import { TypeRegistryError } from "../errors.js";
|
|
5
5
|
import { AppDirsLive, PlatformLive } from "../layers/xdg.js";
|
|
6
|
-
import { Context, Duration, Effect, Layer, Path } from "effect";
|
|
6
|
+
import { Cause, Context, Duration, Effect, Layer, Path } from "effect";
|
|
7
7
|
import { NodeHttpClient } from "@effect/platform-node";
|
|
8
|
-
import { PackageFetcher, PackageSpec, RegistryObserver, TypeCache, TypeRegistry } from "@tsdoctor/registry";
|
|
9
8
|
import { Cache } from "@effected/store";
|
|
10
9
|
import { AppDirs } from "@effected/xdg";
|
|
10
|
+
import { PackageFetcher, PackageSpec, RegistryObserver, TypeCache, TypeRegistry } from "@tsdoctor/registry";
|
|
11
11
|
|
|
12
12
|
//#region src/services/TypeRegistryService.ts
|
|
13
13
|
var TypeRegistryService = class TypeRegistryService extends Context.Service()("rspress-plugin-api-extractor/TypeRegistryService") {
|
|
@@ -27,7 +27,7 @@ var TypeRegistryService = class TypeRegistryService extends Context.Service()("r
|
|
|
27
27
|
* static initializer runs while the module body is still evaluating, so naming
|
|
28
28
|
* those consts directly throws at import time with a clean typecheck.
|
|
29
29
|
*/
|
|
30
|
-
static layer = Layer.suspend(() => RegistryBackedLive.pipe(Layer.catchCause(() => DegradedLive)));
|
|
30
|
+
static layer = Layer.suspend(() => RegistryBackedLive.pipe(Layer.catchCause((cause) => Cause.hasInterrupts(cause) ? Layer.effectContext(Effect.failCause(Cause.interrupt([...Cause.interruptors(cause)][0]))) : DegradedLive)));
|
|
31
31
|
/**
|
|
32
32
|
* An in-memory double: no network, no XDG cache, no sqlite.
|
|
33
33
|
*
|
|
@@ -132,7 +132,7 @@ const MetadataCacheLive = Layer.unwrap(Effect.gen(function* () {
|
|
|
132
132
|
const path = yield* Path.Path;
|
|
133
133
|
const cacheDir = yield* appDirs.ensureCache;
|
|
134
134
|
return Cache.layerSqlite({ filename: path.join(cacheDir, "metadata.sqlite") });
|
|
135
|
-
})).pipe(Layer.provide(Layer.mergeAll(AppDirsLive, PlatformLive)));
|
|
135
|
+
})).pipe(Layer.provide(Layer.mergeAll(AppDirsLive, PlatformLive)), Cache.degrading);
|
|
136
136
|
/**
|
|
137
137
|
* The full registry runtime: TypeRegistry over an XDG-rooted TypeCache and the
|
|
138
138
|
* jsDelivr PackageFetcher, with the observer that forwards registry events to
|
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,19 +1,18 @@
|
|
|
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
|
|
11
10
|
/* v8 ignore start -- Shiki/Twoslash integration, requires full highlighter setup for testing */
|
|
12
11
|
/**
|
|
13
12
|
* Module-level type routes map for resolving link references.
|
|
14
|
-
*
|
|
13
|
+
* Populated per build by `addTypeRoutes` and reset by `clearTypeRoutes`.
|
|
15
14
|
*/
|
|
16
|
-
|
|
15
|
+
const typeRoutes = /* @__PURE__ */ new Map();
|
|
17
16
|
/**
|
|
18
17
|
* Transform TSDoc link tag syntax to markdown links or plain text.
|
|
19
18
|
*
|
|
@@ -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
|
*
|
|
@@ -441,6 +396,18 @@ var TwoslashEnvironmentRegistry = class {
|
|
|
441
396
|
this.handleTwoslashError(error, code, file);
|
|
442
397
|
}
|
|
443
398
|
};
|
|
399
|
+
/**
|
|
400
|
+
* Cross-link routes used to turn type names in hover docs into links.
|
|
401
|
+
*
|
|
402
|
+
* @remarks
|
|
403
|
+
* These were `static` members of the old singleton, but they are cross-link
|
|
404
|
+
* DATA, not type-checking state — they only ever read and wrote the
|
|
405
|
+
* module-level `typeRoutes` map above, and they share their concern with
|
|
406
|
+
* the prose cross-linker rather than with the environment registry. They
|
|
407
|
+
* are deliberately NOT part of {@link TwoslashEnvironments}: folding them in
|
|
408
|
+
* would widen the service's surface with state that has nothing to do with
|
|
409
|
+
* compiler configurations.
|
|
410
|
+
*/
|
|
444
411
|
/** Merge routes in, so a multi-API build accumulates every scope's names. */
|
|
445
412
|
function addTypeRoutes(routes) {
|
|
446
413
|
for (const [name, route] of routes) typeRoutes.set(name, route);
|
|
@@ -463,4 +430,4 @@ function clearTypeRoutes() {
|
|
|
463
430
|
}
|
|
464
431
|
|
|
465
432
|
//#endregion
|
|
466
|
-
export { TwoslashEnvironmentRegistry, addTypeRoutes, clearTypeRoutes
|
|
433
|
+
export { TwoslashEnvironmentRegistry, addTypeRoutes, clearTypeRoutes };
|