@tsdoctor/model 0.1.0 → 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/ApiItems.js +111 -0
- package/CrossLinker.js +81 -0
- package/EntryPoints.js +76 -0
- package/Model.js +90 -0
- package/README.md +55 -52
- package/Render.js +182 -0
- package/Routes.js +110 -0
- package/Signature.js +100 -0
- package/StructuredData.js +18 -0
- package/SyntheticBases.js +83 -0
- package/Tsdoc.js +250 -0
- package/_virtual/_rolldown/runtime.js +18 -0
- package/index.d.ts +477 -66
- package/index.js +11 -6
- package/internal/prose.js +18 -0
- package/internal/text.js +11 -0
- package/package.json +11 -1
- package/cross-linker.js +0 -37
- package/formatter.js +0 -70
- package/model-loader.js +0 -24
- package/render.js +0 -118
- package/tsdoc.js +0 -158
package/Render.js
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { __exportAll } from "./_virtual/_rolldown/runtime.js";
|
|
2
|
+
import { deprecation, examples, params, returns, summary } from "./Tsdoc.js";
|
|
3
|
+
import { CrossLinker } from "./CrossLinker.js";
|
|
4
|
+
import { phrasingFromMarkdown } from "./internal/prose.js";
|
|
5
|
+
import { format } from "./Signature.js";
|
|
6
|
+
import { ApiItemContainerMixin } from "@microsoft/api-extractor-model";
|
|
7
|
+
import { Blockquote, Code, Heading, InlineCode, List, ListItem, Markdown, Paragraph, Root, Strong, Text } from "@effected/markdown";
|
|
8
|
+
import { Result } from "effect";
|
|
9
|
+
|
|
10
|
+
//#region src/Render.ts
|
|
11
|
+
var Render_exports = /* @__PURE__ */ __exportAll({
|
|
12
|
+
docs: () => docs,
|
|
13
|
+
isEmittable: () => isEmittable,
|
|
14
|
+
item: () => item,
|
|
15
|
+
tree: () => tree
|
|
16
|
+
});
|
|
17
|
+
const KIND_SLUG = {
|
|
18
|
+
Class: "class",
|
|
19
|
+
Interface: "interface",
|
|
20
|
+
Function: "function",
|
|
21
|
+
TypeAlias: "type",
|
|
22
|
+
Variable: "variable",
|
|
23
|
+
Enum: "enum",
|
|
24
|
+
Namespace: "namespace"
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* The default emit rule for {@link docs}: drop compiler-synthetic forgotten
|
|
28
|
+
* exports — items the model retains only because API Extractor ran with
|
|
29
|
+
* `includeForgottenExports: true` (e.g. the `*_base` classes TypeScript hoists
|
|
30
|
+
* for Effect class mixins). Those carry `isExported === false` on
|
|
31
|
+
* `ApiExportedMixin`. Every other item, including any lacking the flag, is
|
|
32
|
+
* kept.
|
|
33
|
+
*
|
|
34
|
+
* @public
|
|
35
|
+
*/
|
|
36
|
+
const isEmittable = (item) => item.isExported !== false;
|
|
37
|
+
const signatureOf = (item) => {
|
|
38
|
+
const declared = item;
|
|
39
|
+
return declared.excerpt?.text ? format(declared.excerpt).trim() : "";
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* Render one API item's markdown body as flow nodes — the pre-serialization
|
|
43
|
+
* form of {@link item}.
|
|
44
|
+
*
|
|
45
|
+
* @alpha
|
|
46
|
+
*/
|
|
47
|
+
function tree(apiItem, opts) {
|
|
48
|
+
const link = (text) => opts.crossLinker ? opts.crossLinker.link(text) : text;
|
|
49
|
+
const prose = (text) => phrasingFromMarkdown(link(text));
|
|
50
|
+
const nodes = [new Heading({
|
|
51
|
+
depth: 1,
|
|
52
|
+
children: [new Text({ value: apiItem.displayName })]
|
|
53
|
+
})];
|
|
54
|
+
const deprecation$1 = deprecation(apiItem);
|
|
55
|
+
if (deprecation$1) nodes.push(new Blockquote({ children: [new Paragraph({ children: [
|
|
56
|
+
new Strong({ children: [new Text({ value: "Deprecated:" })] }),
|
|
57
|
+
new Text({ value: " " }),
|
|
58
|
+
...prose(deprecation$1.message)
|
|
59
|
+
] })] }));
|
|
60
|
+
const summary$1 = summary(apiItem);
|
|
61
|
+
if (summary$1) nodes.push(new Paragraph({ children: [...prose(summary$1)] }));
|
|
62
|
+
const signature = signatureOf(apiItem);
|
|
63
|
+
if (signature) nodes.push(new Code({
|
|
64
|
+
value: signature,
|
|
65
|
+
lang: "ts"
|
|
66
|
+
}));
|
|
67
|
+
const params$1 = params(apiItem);
|
|
68
|
+
if (params$1.length > 0) {
|
|
69
|
+
nodes.push(new Heading({
|
|
70
|
+
depth: 2,
|
|
71
|
+
children: [new Text({ value: "Parameters" })]
|
|
72
|
+
}));
|
|
73
|
+
nodes.push(new List({
|
|
74
|
+
ordered: false,
|
|
75
|
+
spread: false,
|
|
76
|
+
children: params$1.map((p) => {
|
|
77
|
+
const children = [new InlineCode({ value: p.name })];
|
|
78
|
+
if (p.type) children.push(new Text({ value: " " }), new InlineCode({ value: p.type }));
|
|
79
|
+
if (p.description) children.push(new Text({ value: " — " }), ...prose(p.description));
|
|
80
|
+
return new ListItem({
|
|
81
|
+
spread: false,
|
|
82
|
+
children: [new Paragraph({ children })]
|
|
83
|
+
});
|
|
84
|
+
})
|
|
85
|
+
}));
|
|
86
|
+
}
|
|
87
|
+
const returns$1 = returns(apiItem);
|
|
88
|
+
if (returns$1) {
|
|
89
|
+
nodes.push(new Heading({
|
|
90
|
+
depth: 2,
|
|
91
|
+
children: [new Text({ value: "Returns" })]
|
|
92
|
+
}));
|
|
93
|
+
nodes.push(new Paragraph({ children: [...prose(returns$1.description)] }));
|
|
94
|
+
}
|
|
95
|
+
const members = apiItem.members;
|
|
96
|
+
if (Array.isArray(members) && members.length > 0 && ApiItemContainerMixin.isBaseClassOf(apiItem)) {
|
|
97
|
+
nodes.push(new Heading({
|
|
98
|
+
depth: 2,
|
|
99
|
+
children: [new Text({ value: "Members" })]
|
|
100
|
+
}));
|
|
101
|
+
for (const m of members) {
|
|
102
|
+
nodes.push(new Heading({
|
|
103
|
+
depth: 3,
|
|
104
|
+
children: [new Text({ value: m.displayName })]
|
|
105
|
+
}));
|
|
106
|
+
const sig = signatureOf(m);
|
|
107
|
+
if (sig) nodes.push(new Code({
|
|
108
|
+
value: sig,
|
|
109
|
+
lang: "ts"
|
|
110
|
+
}));
|
|
111
|
+
const mSummary = summary(m);
|
|
112
|
+
if (mSummary) nodes.push(new Paragraph({ children: [...prose(mSummary)] }));
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
const examples$1 = examples(apiItem);
|
|
116
|
+
if (examples$1.length > 0) {
|
|
117
|
+
nodes.push(new Heading({
|
|
118
|
+
depth: 2,
|
|
119
|
+
children: [new Text({ value: "Examples" })]
|
|
120
|
+
}));
|
|
121
|
+
for (const ex of examples$1) nodes.push(new Code({
|
|
122
|
+
value: ex.code.replace(/\n$/, ""),
|
|
123
|
+
lang: ex.language
|
|
124
|
+
}));
|
|
125
|
+
}
|
|
126
|
+
return nodes;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Render one API item to a markdown body string (no frontmatter).
|
|
130
|
+
*
|
|
131
|
+
* @public
|
|
132
|
+
*/
|
|
133
|
+
function item(apiItem, opts) {
|
|
134
|
+
const root = new Root({ children: [...tree(apiItem, opts)] });
|
|
135
|
+
const trimmed = Result.getOrThrow(Markdown.stringifyResult(root)).trim();
|
|
136
|
+
return trimmed ? `${trimmed}\n` : "\n";
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Walk a package's first entry point and assemble one RenderedDoc per
|
|
140
|
+
* top-level member.
|
|
141
|
+
*
|
|
142
|
+
* @public
|
|
143
|
+
*/
|
|
144
|
+
function docs(apiPackage, opts) {
|
|
145
|
+
const entryPoint = apiPackage.entryPoints[0];
|
|
146
|
+
if (!entryPoint) return [];
|
|
147
|
+
const keep = opts.filter ?? isEmittable;
|
|
148
|
+
const pairs = [];
|
|
149
|
+
for (const member of entryPoint.members) {
|
|
150
|
+
const kind = KIND_SLUG[member.kind];
|
|
151
|
+
if (kind === void 0) continue;
|
|
152
|
+
if (!keep(member)) continue;
|
|
153
|
+
pairs.push({
|
|
154
|
+
item: member,
|
|
155
|
+
ref: {
|
|
156
|
+
name: member.displayName,
|
|
157
|
+
kind,
|
|
158
|
+
slug: member.displayName.toLowerCase()
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
const crossLinker = opts.routeFor ? CrossLinker.fromRefs(pairs.map((p) => p.ref), opts.routeFor) : void 0;
|
|
163
|
+
return pairs.map(({ item: member, ref }) => {
|
|
164
|
+
const body = item(member, {
|
|
165
|
+
packageName: opts.packageName,
|
|
166
|
+
...crossLinker ? { crossLinker } : {}
|
|
167
|
+
});
|
|
168
|
+
const meta = {
|
|
169
|
+
...ref,
|
|
170
|
+
summary: summary(member),
|
|
171
|
+
packageName: opts.packageName
|
|
172
|
+
};
|
|
173
|
+
const frontmatter = opts.frontmatter ? opts.frontmatter(meta) : "";
|
|
174
|
+
return {
|
|
175
|
+
...meta,
|
|
176
|
+
markdown: frontmatter + body
|
|
177
|
+
};
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
//#endregion
|
|
182
|
+
export { Render_exports, docs, isEmittable, item, tree };
|
package/Routes.js
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { __exportAll } from "./_virtual/_rolldown/runtime.js";
|
|
2
|
+
import { Schema } from "effect";
|
|
3
|
+
|
|
4
|
+
//#region src/Routes.ts
|
|
5
|
+
/**
|
|
6
|
+
* Output-route computation: collision detection over route candidates and the
|
|
7
|
+
* canonical anchor-id sanitizer. Detection is pure; the typed
|
|
8
|
+
* {@link RouteCollisionError} is the artifact a consumer fails its build with.
|
|
9
|
+
*
|
|
10
|
+
* @packageDocumentation
|
|
11
|
+
*/
|
|
12
|
+
var Routes_exports = /* @__PURE__ */ __exportAll({
|
|
13
|
+
RouteCandidate: () => RouteCandidate,
|
|
14
|
+
RouteCollisionError: () => RouteCollisionError,
|
|
15
|
+
detectCollisions: () => detectCollisions,
|
|
16
|
+
sanitizeId: () => sanitizeId
|
|
17
|
+
});
|
|
18
|
+
/**
|
|
19
|
+
* A candidate output route for collision detection. All-string and
|
|
20
|
+
* serializable — candidates cross the error boundary inside
|
|
21
|
+
* {@link RouteCollisionError} and may be persisted by consumer diagnostics.
|
|
22
|
+
*
|
|
23
|
+
* @public
|
|
24
|
+
*/
|
|
25
|
+
var RouteCandidate = class extends Schema.Class("RouteCandidate")({
|
|
26
|
+
/** Stable identity (e.g. `"displayName::kind"` or a namespace qualified name). */
|
|
27
|
+
id: Schema.String,
|
|
28
|
+
/** Human-readable name for error messages (original, non-lowercased). */
|
|
29
|
+
displayName: Schema.String,
|
|
30
|
+
/** Category folder name, e.g. `"variable"`. */
|
|
31
|
+
folder: Schema.String,
|
|
32
|
+
/** Lowercased sanitized last path segment, e.g. `"foo"` — the value used in the route. */
|
|
33
|
+
baseName: Schema.String,
|
|
34
|
+
/** API item kind string, e.g. `"Variable"`. */
|
|
35
|
+
kind: Schema.String,
|
|
36
|
+
/** canonicalReference string, used for deterministic ordering. */
|
|
37
|
+
canonicalRef: Schema.String
|
|
38
|
+
}) {};
|
|
39
|
+
/**
|
|
40
|
+
* Group candidates by their final route (`${folder}/${baseName}`) and return
|
|
41
|
+
* the groups with more than one distinct item. The route key is the lowercased
|
|
42
|
+
* path the file is written to, so detection matches generation (and what a
|
|
43
|
+
* case-insensitive filesystem would merge). Companion pairs (same name,
|
|
44
|
+
* different folders) land under different keys and are never collisions.
|
|
45
|
+
*
|
|
46
|
+
* Output is deterministic: collisions ordered by route, items within a
|
|
47
|
+
* collision ordered by canonicalReference.
|
|
48
|
+
*
|
|
49
|
+
* @public
|
|
50
|
+
*/
|
|
51
|
+
function detectCollisions(candidates) {
|
|
52
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
53
|
+
for (const candidate of candidates) {
|
|
54
|
+
const key = `${candidate.folder}/${candidate.baseName}`;
|
|
55
|
+
const group = byKey.get(key) ?? [];
|
|
56
|
+
group.push(candidate);
|
|
57
|
+
byKey.set(key, group);
|
|
58
|
+
}
|
|
59
|
+
const collisions = [];
|
|
60
|
+
for (const [route, group] of byKey) if (group.length > 1) {
|
|
61
|
+
const items = [...group].sort((a, b) => a.canonicalRef < b.canonicalRef ? -1 : a.canonicalRef > b.canonicalRef ? 1 : 0);
|
|
62
|
+
collisions.push({
|
|
63
|
+
route,
|
|
64
|
+
items
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
collisions.sort((a, b) => a.route < b.route ? -1 : a.route > b.route ? 1 : 0);
|
|
68
|
+
return collisions;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Two or more distinct API items resolve to the same documentation route — a
|
|
72
|
+
* naming or category-configuration problem the build must fail on. The
|
|
73
|
+
* `message` names every colliding item with its kind and canonical reference,
|
|
74
|
+
* plus remediation guidance.
|
|
75
|
+
*
|
|
76
|
+
* @public
|
|
77
|
+
*/
|
|
78
|
+
var RouteCollisionError = class extends Schema.TaggedError()("RouteCollisionError", {
|
|
79
|
+
baseRoute: Schema.String,
|
|
80
|
+
collisions: Schema.Array(Schema.Struct({
|
|
81
|
+
route: Schema.String,
|
|
82
|
+
items: Schema.Array(RouteCandidate)
|
|
83
|
+
}))
|
|
84
|
+
}) {
|
|
85
|
+
get message() {
|
|
86
|
+
const lines = [];
|
|
87
|
+
for (const collision of this.collisions) {
|
|
88
|
+
lines.push(`Route collision: ${collision.items.length} API items resolve to the same documentation path "${this.baseRoute}/${collision.route}":`);
|
|
89
|
+
for (const item of collision.items) lines.push(` - ${item.displayName} (${item.kind}) [${item.canonicalRef}]`);
|
|
90
|
+
}
|
|
91
|
+
lines.push("");
|
|
92
|
+
lines.push("Item names must be unique per category folder. Paths are lowercased, so names differing only in case collide. Rename one of the items, or configure categories so they map to different folders.");
|
|
93
|
+
return lines.join("\n");
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
/**
|
|
97
|
+
* Sanitize a display name into a valid HTML anchor id: lowercase,
|
|
98
|
+
* spaces/underscores → hyphens, other specials stripped, optional prefix for
|
|
99
|
+
* disambiguation. The ONE canonical implementation — anchor generation and
|
|
100
|
+
* cross-link routes must agree on it by construction.
|
|
101
|
+
*
|
|
102
|
+
* @public
|
|
103
|
+
*/
|
|
104
|
+
function sanitizeId(displayName, prefix = "") {
|
|
105
|
+
const sanitized = displayName.toLowerCase().replace(/[\s_]+/g, "-").replace(/[^a-z0-9-]/g, "").replace(/^-+|-+$/g, "");
|
|
106
|
+
return prefix ? `${prefix}-${sanitized}` : sanitized;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
//#endregion
|
|
110
|
+
export { RouteCandidate, RouteCollisionError, Routes_exports, detectCollisions, sanitizeId };
|
package/Signature.js
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { __exportAll } from "./_virtual/_rolldown/runtime.js";
|
|
2
|
+
import { escapeRegExp } from "./internal/text.js";
|
|
3
|
+
|
|
4
|
+
//#region src/Signature.ts
|
|
5
|
+
var Signature_exports = /* @__PURE__ */ __exportAll({
|
|
6
|
+
format: () => format,
|
|
7
|
+
linkReferences: () => linkReferences,
|
|
8
|
+
stripExportDeclare: () => stripExportDeclare
|
|
9
|
+
});
|
|
10
|
+
/**
|
|
11
|
+
* Strip `export` / `declare` modifiers from a declaration text.
|
|
12
|
+
*
|
|
13
|
+
* @public
|
|
14
|
+
*/
|
|
15
|
+
function stripExportDeclare(text) {
|
|
16
|
+
let result = text.trim().replace(/^export\s+declare\s+/i, "").replace(/^export\s+/i, "").replace(/^declare\s+/i, "");
|
|
17
|
+
result = result.replace(/\bexport\s+declare\s+/gi, "").replace(/\bexport\s+/gi, "").replace(/\bdeclare\s+/gi, "");
|
|
18
|
+
return result;
|
|
19
|
+
}
|
|
20
|
+
function needsSpaceBefore(prevText, currentText) {
|
|
21
|
+
if (/\s$/.test(prevText)) return false;
|
|
22
|
+
if (/^\s/.test(currentText)) return false;
|
|
23
|
+
if (currentText.trim().startsWith("<")) return false;
|
|
24
|
+
if (currentText.trim().match(/^[,;]/)) return false;
|
|
25
|
+
if (prevText.trim().endsWith(",")) return true;
|
|
26
|
+
if (currentText.trim() === "=" || currentText.trim().startsWith("=")) return true;
|
|
27
|
+
if (prevText.trim().endsWith("=")) return true;
|
|
28
|
+
if (currentText.trim() === "|" || currentText.trim() === "&") return true;
|
|
29
|
+
if (prevText.trim() === "|" || prevText.trim() === "&") return true;
|
|
30
|
+
if (prevText.trim() === "{" && currentText.trim() !== "}") return true;
|
|
31
|
+
if (currentText.trim() === "}" && prevText.trim() !== "{") return true;
|
|
32
|
+
if (prevText.trim().match(/^[[(]$/)) return false;
|
|
33
|
+
if (currentText.trim().match(/^[\])]$/)) return false;
|
|
34
|
+
if (prevText.trim().endsWith(":")) return true;
|
|
35
|
+
if (prevText.trim().endsWith("?:")) return true;
|
|
36
|
+
if (currentText.trim().startsWith(":") && !prevText.trim().match(/[,;:?]$/)) return false;
|
|
37
|
+
if (currentText.trim().startsWith("{") && /[a-zA-Z0-9_>]$/.test(prevText.trim())) return true;
|
|
38
|
+
const prevEndsAlnum = /[a-zA-Z0-9_>]$/.test(prevText.trim());
|
|
39
|
+
const currStartsAlnum = /^[a-zA-Z0-9_<]/.test(currentText.trim());
|
|
40
|
+
return prevEndsAlnum && currStartsAlnum;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Format an API Extractor `Excerpt` into a clean type signature string,
|
|
44
|
+
* wrapping long top-level unions/intersections.
|
|
45
|
+
*
|
|
46
|
+
* @public
|
|
47
|
+
*/
|
|
48
|
+
function format(excerpt, options) {
|
|
49
|
+
const maxLineLength = options?.maxLineLength ?? 80;
|
|
50
|
+
const indent = options?.indent ?? " ";
|
|
51
|
+
if (!excerpt.spannedTokens || excerpt.spannedTokens.length === 0) return stripExportDeclare(excerpt.text);
|
|
52
|
+
const tokens = excerpt.spannedTokens;
|
|
53
|
+
let currentLine = "";
|
|
54
|
+
const lines = [];
|
|
55
|
+
let bracketDepth = 0;
|
|
56
|
+
let lastTokenText = "";
|
|
57
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
58
|
+
let tokenText = tokens[i].text;
|
|
59
|
+
if (i === 0) tokenText = stripExportDeclare(tokenText);
|
|
60
|
+
if (tokenText.trim() === "") continue;
|
|
61
|
+
if (tokenText === "{" || tokenText === "[" || tokenText === "(") bracketDepth++;
|
|
62
|
+
else if (tokenText === "}" || tokenText === "]" || tokenText === ")") bracketDepth--;
|
|
63
|
+
const isOperator = tokenText.trim() === "|" || tokenText.trim() === "&";
|
|
64
|
+
if (lastTokenText && needsSpaceBefore(lastTokenText, tokenText)) currentLine += " ";
|
|
65
|
+
currentLine += tokenText;
|
|
66
|
+
lastTokenText = tokenText;
|
|
67
|
+
if (isOperator && bracketDepth === 0 && currentLine.length > maxLineLength && i < tokens.length - 1) {
|
|
68
|
+
lines.push(currentLine.trimEnd());
|
|
69
|
+
currentLine = indent;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
if (currentLine.trim()) lines.push(currentLine.trimEnd());
|
|
73
|
+
if (lines.length <= 1) return lines.length === 1 ? lines[0].trimStart() : "";
|
|
74
|
+
return stripExportDeclare(lines.join("\n"));
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Inject markdown cross-links into already-formatted signature text. Reference
|
|
78
|
+
* tokens in the excerpt whose canonical reference appears in
|
|
79
|
+
* `routesByCanonicalRef` have their display text wrapped in a markdown link.
|
|
80
|
+
*
|
|
81
|
+
* @public
|
|
82
|
+
*/
|
|
83
|
+
function linkReferences(text, excerpt, routesByCanonicalRef) {
|
|
84
|
+
if (!excerpt.spannedTokens || routesByCanonicalRef.size === 0) return text;
|
|
85
|
+
const typeReferences = /* @__PURE__ */ new Map();
|
|
86
|
+
for (const token of excerpt.spannedTokens) if (token.kind === "Reference" && token.canonicalReference) {
|
|
87
|
+
const canonicalRef = token.canonicalReference.toString();
|
|
88
|
+
const route = routesByCanonicalRef.get(canonicalRef);
|
|
89
|
+
if (route && token.text) typeReferences.set(token.text.trim(), route);
|
|
90
|
+
}
|
|
91
|
+
let result = text;
|
|
92
|
+
for (const [typeName, route] of typeReferences.entries()) {
|
|
93
|
+
const regex = new RegExp(`\\b${escapeRegExp(typeName)}\\b`, "g");
|
|
94
|
+
result = result.replace(regex, `[${typeName}](${route})`);
|
|
95
|
+
}
|
|
96
|
+
return result;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
//#endregion
|
|
100
|
+
export { Signature_exports, format, linkReferences, stripExportDeclare };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { __exportAll } from "./_virtual/_rolldown/runtime.js";
|
|
2
|
+
|
|
3
|
+
//#region src/StructuredData.ts
|
|
4
|
+
var StructuredData_exports = /* @__PURE__ */ __exportAll({ derive: () => derive });
|
|
5
|
+
/**
|
|
6
|
+
* Derive schema.org structured data for a documented package.
|
|
7
|
+
*
|
|
8
|
+
* @remarks
|
|
9
|
+
* Not implemented yet — this is the phase-4 seam. Calling it throws.
|
|
10
|
+
*
|
|
11
|
+
* @alpha
|
|
12
|
+
*/
|
|
13
|
+
function derive(_apiPackage, _manifest) {
|
|
14
|
+
throw new Error("@tsdoctor/model: StructuredData.derive is a phase-4 seam and is not implemented yet");
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
//#endregion
|
|
18
|
+
export { StructuredData_exports, derive };
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { __exportAll } from "./_virtual/_rolldown/runtime.js";
|
|
2
|
+
import { ApiExportedMixin, ApiItemKind, ExcerptTokenKind } from "@microsoft/api-extractor-model";
|
|
3
|
+
|
|
4
|
+
//#region src/SyntheticBases.ts
|
|
5
|
+
var SyntheticBases_exports = /* @__PURE__ */ __exportAll({
|
|
6
|
+
BASE_CLASS_ANCHOR: () => BASE_CLASS_ANCHOR,
|
|
7
|
+
detect: () => detect
|
|
8
|
+
});
|
|
9
|
+
/**
|
|
10
|
+
* Anchor id of the inline "Base Class" section rendered on the owner class
|
|
11
|
+
* page. Must match the slug RSPress derives from the `## Base Class` heading
|
|
12
|
+
* emitted by the owner-class page.
|
|
13
|
+
*
|
|
14
|
+
* @public
|
|
15
|
+
*/
|
|
16
|
+
const BASE_CLASS_ANCHOR = "base-class";
|
|
17
|
+
const EMPTY_DETECTION = {
|
|
18
|
+
bases: /* @__PURE__ */ new Map(),
|
|
19
|
+
baseByOwner: /* @__PURE__ */ new Map()
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Strip the trailing meaning (`:class`, `:var`, `:function(1)`, ...) from a
|
|
23
|
+
* canonical reference string so the reference token in an extends clause
|
|
24
|
+
* (`example!~Person_base`) matches the declaration's canonical reference
|
|
25
|
+
* (`example!~Person_base:var`).
|
|
26
|
+
*/
|
|
27
|
+
function stripMeaning(canonicalRef) {
|
|
28
|
+
return canonicalRef.replace(/:[a-z]+(\(\d+\))?$/i, "");
|
|
29
|
+
}
|
|
30
|
+
/** True when the item carries ApiExportedMixin and is NOT exported from its entry point. */
|
|
31
|
+
function isUnexported(item) {
|
|
32
|
+
return ApiExportedMixin.isBaseClassOf(item) && !item.isExported;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Detect synthetic base declarations among top-level API items.
|
|
36
|
+
*
|
|
37
|
+
* An item qualifies when it is unexported (hoisted into the model only because
|
|
38
|
+
* something references it) AND at least one class's extends clause references
|
|
39
|
+
* its canonical symbol. Unexported items with no class referencing them
|
|
40
|
+
* (genuine forgotten exports) are left alone, as are extends references whose
|
|
41
|
+
* target is absent from the model.
|
|
42
|
+
*
|
|
43
|
+
* @public
|
|
44
|
+
*/
|
|
45
|
+
function detect(items) {
|
|
46
|
+
const unexportedByRef = /* @__PURE__ */ new Map();
|
|
47
|
+
for (const item of items) {
|
|
48
|
+
if (!isUnexported(item)) continue;
|
|
49
|
+
const ref = item.canonicalReference?.toString();
|
|
50
|
+
if (ref) unexportedByRef.set(stripMeaning(ref), item);
|
|
51
|
+
}
|
|
52
|
+
if (unexportedByRef.size === 0) return EMPTY_DETECTION;
|
|
53
|
+
const owners = /* @__PURE__ */ new Map();
|
|
54
|
+
const baseByOwner = /* @__PURE__ */ new Map();
|
|
55
|
+
for (const item of items) {
|
|
56
|
+
if (item.kind !== ApiItemKind.Class) continue;
|
|
57
|
+
const apiClass = item;
|
|
58
|
+
const extendsType = apiClass.extendsType;
|
|
59
|
+
if (!extendsType) continue;
|
|
60
|
+
for (const token of extendsType.excerpt.spannedTokens) {
|
|
61
|
+
if (token.kind !== ExcerptTokenKind.Reference || !token.canonicalReference) continue;
|
|
62
|
+
const base = unexportedByRef.get(stripMeaning(token.canonicalReference.toString()));
|
|
63
|
+
if (!base || base === item || baseByOwner.has(item)) continue;
|
|
64
|
+
baseByOwner.set(item, base);
|
|
65
|
+
const ownerList = owners.get(base);
|
|
66
|
+
if (ownerList) ownerList.push(apiClass);
|
|
67
|
+
else owners.set(base, [apiClass]);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
if (owners.size === 0) return EMPTY_DETECTION;
|
|
71
|
+
const bases = /* @__PURE__ */ new Map();
|
|
72
|
+
for (const [baseItem, ownerClasses] of owners) bases.set(baseItem, {
|
|
73
|
+
baseItem,
|
|
74
|
+
ownerClasses
|
|
75
|
+
});
|
|
76
|
+
return {
|
|
77
|
+
bases,
|
|
78
|
+
baseByOwner
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
//#endregion
|
|
83
|
+
export { BASE_CLASS_ANCHOR, SyntheticBases_exports, detect };
|