@tsdoctor/pages 0.1.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/Nav.js ADDED
@@ -0,0 +1,148 @@
1
+ import { Schema } from "effect";
2
+
3
+ //#region src/Nav.ts
4
+ /**
5
+ * The per-API navigation tree — category groups of pages plus the index —
6
+ * as data, so a sidebar that is files in one framework and config in another
7
+ * is a pure rendering of the same value.
8
+ *
9
+ * @remarks
10
+ * The ordering is the one the RSPress adapter's `writeMetadata` produced when
11
+ * it wrote `_meta.json` files directly: groups in category insertion order,
12
+ * kept only when at least one page landed in them; pages within a group by
13
+ * `label.localeCompare`; the index page always present. `buildNav` is
14
+ * characterized against that behaviour, and the RSPress rendering of this
15
+ * tree is covered by the golden gate.
16
+ *
17
+ * @packageDocumentation
18
+ */
19
+ /**
20
+ * A page's place in the navigation tree, carried on the page itself.
21
+ *
22
+ * @public
23
+ */
24
+ var NavEntry = class extends Schema.Class("NavEntry")({
25
+ /** The category key the page was categorized under. */
26
+ categoryKey: Schema.String,
27
+ /** The sidebar label — the display name, qualified for a namespace member. */
28
+ label: Schema.String,
29
+ /** The file basename without extension (`foo` for `class/foo.mdx`). */
30
+ name: Schema.String,
31
+ /** The page route. */
32
+ route: Schema.String
33
+ }) {};
34
+ /**
35
+ * The per-category presentation facts a tree carries for its groups.
36
+ *
37
+ * @public
38
+ */
39
+ var NavCategory = class extends Schema.Class("NavCategory")({
40
+ /** The group label. */
41
+ displayName: Schema.String,
42
+ /** The folder the category's pages live in. */
43
+ folderName: Schema.String,
44
+ /** Whether the group can be collapsed; absent means the renderer's default. */
45
+ collapsible: Schema.optionalKey(Schema.Boolean),
46
+ /** Whether the group starts collapsed; absent means the renderer's default. */
47
+ collapsed: Schema.optionalKey(Schema.Boolean),
48
+ /** Heading depths surfaced in an overview; absent means the renderer's default. */
49
+ overviewHeaders: Schema.optionalKey(Schema.Array(Schema.Number))
50
+ }) {};
51
+ /**
52
+ * One page in a group.
53
+ *
54
+ * @public
55
+ */
56
+ var NavPage = class extends Schema.Class("NavPage")({
57
+ /** The sidebar label. */
58
+ label: Schema.String,
59
+ /** The file basename without extension. */
60
+ name: Schema.String,
61
+ /** The page route. */
62
+ route: Schema.String
63
+ }) {};
64
+ /**
65
+ * One category group with its pages, already sorted.
66
+ *
67
+ * @public
68
+ */
69
+ var NavGroup = class extends Schema.Class("NavGroup")({
70
+ /** The category key. */
71
+ key: Schema.String,
72
+ /** The category presentation facts. */
73
+ category: NavCategory,
74
+ /** The group's pages, sorted by label. */
75
+ pages: Schema.Array(NavPage)
76
+ }) {};
77
+ /**
78
+ * The navigation tree for one API.
79
+ *
80
+ * @public
81
+ */
82
+ var NavTree = class extends Schema.Class("NavTree")({
83
+ /** The API's base route; the index page lives at its root. */
84
+ baseRoute: Schema.String,
85
+ /** The index page. */
86
+ index: NavPage,
87
+ /** The category groups that received at least one page, in category order. */
88
+ groups: Schema.Array(NavGroup)
89
+ }) {};
90
+ /**
91
+ * The label of the index page every tree carries.
92
+ *
93
+ * @public
94
+ */
95
+ const NAV_INDEX_LABEL = "API Reference";
96
+ /**
97
+ * Sort pages the way the sidebar lists them: alphabetically by label.
98
+ *
99
+ * @public
100
+ */
101
+ function sortNavPages(pages) {
102
+ return [...pages].sort((a, b) => a.label.localeCompare(b.label));
103
+ }
104
+ /**
105
+ * Build the navigation tree for one API from its categories and its pages.
106
+ *
107
+ * @remarks
108
+ * Groups follow `categories`' insertion order and a category with no page
109
+ * is dropped rather than rendered empty. An entry whose category key names
110
+ * no configured category is dropped too — it could not have been generated
111
+ * into a folder — so the tree only ever describes pages that exist.
112
+ *
113
+ * @public
114
+ */
115
+ function buildNav(input) {
116
+ const byCategory = /* @__PURE__ */ new Map();
117
+ for (const entry of input.entries) {
118
+ const pages = byCategory.get(entry.categoryKey) ?? [];
119
+ pages.push(NavPage.make({
120
+ label: entry.label,
121
+ name: entry.name,
122
+ route: entry.route
123
+ }));
124
+ byCategory.set(entry.categoryKey, pages);
125
+ }
126
+ const groups = [];
127
+ for (const [key, category] of Object.entries(input.categories)) {
128
+ const pages = byCategory.get(key);
129
+ if (!pages || pages.length === 0) continue;
130
+ groups.push(NavGroup.make({
131
+ key,
132
+ category,
133
+ pages: sortNavPages(pages)
134
+ }));
135
+ }
136
+ return NavTree.make({
137
+ baseRoute: input.baseRoute,
138
+ index: NavPage.make({
139
+ label: NAV_INDEX_LABEL,
140
+ name: "index",
141
+ route: `${input.baseRoute}/index`
142
+ }),
143
+ groups
144
+ });
145
+ }
146
+
147
+ //#endregion
148
+ export { NAV_INDEX_LABEL, NavCategory, NavEntry, NavGroup, NavPage, NavTree, buildNav, sortNavPages };
package/Page.js ADDED
@@ -0,0 +1,80 @@
1
+ import { Block } from "./Blocks.js";
2
+ import { NavEntry } from "./Nav.js";
3
+ import { Schema } from "effect";
4
+
5
+ //#region src/Page.ts
6
+ /**
7
+ * The neutral head tag, as a schema — the same shape as `@tsdoctor/seo`'s
8
+ * `HeadTag` interface, so a value from `headTags` is accepted unchanged.
9
+ *
10
+ * @public
11
+ */
12
+ const HeadTag = Schema.Struct({
13
+ /** The element name. */
14
+ tag: Schema.Literals([
15
+ "meta",
16
+ "link",
17
+ "script"
18
+ ]),
19
+ /** The element attributes. */
20
+ attrs: Schema.Record(Schema.String, Schema.String),
21
+ /** Element content; only meaningful for `script`. */
22
+ body: Schema.optionalKey(Schema.String)
23
+ });
24
+ /**
25
+ * The kind of symbol a page documents — which builder produced it, and
26
+ * which component imports and block layout an emitter chooses.
27
+ *
28
+ * @public
29
+ */
30
+ const PageKind = Schema.Literals([
31
+ "class",
32
+ "interface",
33
+ "function",
34
+ "type-alias",
35
+ "enum",
36
+ "variable",
37
+ "namespace"
38
+ ]);
39
+ /**
40
+ * One generated API page.
41
+ *
42
+ * @public
43
+ */
44
+ var Page = class extends Schema.Class("Page")({
45
+ /** The kind of symbol the page documents. */
46
+ kind: PageKind,
47
+ /** The documented item's display name — the first title part. */
48
+ entityName: Schema.String,
49
+ /** The category's singular name (`Class`, `Function`) — the second title part. */
50
+ singularName: Schema.String,
51
+ /** The API's display name — the last title part, when the site names one. */
52
+ apiName: Schema.optionalKey(Schema.String),
53
+ /** The page description: the item's summary, or the fallback the generators used. */
54
+ description: Schema.String,
55
+ /** The page route. */
56
+ route: Schema.String,
57
+ /** Every `<head>` tag the page carries, from `@tsdoctor/seo`. */
58
+ headTags: Schema.Array(HeadTag),
59
+ /** The page body, in order. */
60
+ blocks: Schema.Array(Block),
61
+ /** The page's place in the navigation tree. */
62
+ nav: NavEntry
63
+ }) {
64
+ /**
65
+ * The structured page title: `{entityName} | {singularName} | API | {apiName}`,
66
+ * the last part omitted when the site names no API.
67
+ */
68
+ get title() {
69
+ const parts = [
70
+ this.entityName,
71
+ this.singularName,
72
+ "API"
73
+ ];
74
+ if (this.apiName !== void 0) parts.push(this.apiName);
75
+ return parts.join(" | ");
76
+ }
77
+ };
78
+
79
+ //#endregion
80
+ export { HeadTag, Page, PageKind };
package/README.md ADDED
@@ -0,0 +1,32 @@
1
+ # @tsdoctor/pages
2
+
3
+ [![npm](https://img.shields.io/npm/v/@tsdoctor%2Fpages?label=npm&color=cb3837)](https://www.npmjs.com/package/@tsdoctor/pages)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-4caf50.svg)](https://opensource.org/licenses/MIT)
5
+ [![Node.js %3E%3D24.11.0](https://img.shields.io/badge/Node.js-%3E%3D24.11.0-5fa04e.svg)](https://nodejs.org/)
6
+ [![TypeScript 6.0](https://img.shields.io/badge/TypeScript-6.0-3178c6.svg)](https://www.typescriptlang.org/)
7
+
8
+ The framework-neutral documentation page IR for static TypeScript API sites. A page is facts, an ordered list of typed doc blocks and its navigation entry; prose inside a block is `@effected/markdown` mdast, and code-bearing blocks carry a `display` / `source` pair. A framework adapter is an emitter over this IR.
9
+
10
+ ## What you get
11
+
12
+ - **Blocks** — the block vocabulary (`Title`, `Signature`, `MemberGroup`, `ParameterTable`, `EnumMemberTable`, `ExampleGroup`, …) as Effect `Schema.Class` variants discriminated on `kind`, plus the `Block` union.
13
+ - **`Page`** — the page record: title parts, description, route, `HeadTag[]`, blocks and nav entry.
14
+ - **`buildNav`** — one `NavTree` per API (category groups, pages, index), sorted deterministically.
15
+ - **`buildExample`, `codeText`, `prepareExampleCode`, `stripTwoslashDirectives`, `prependHiddenImports`, `formatExampleCode`** — display/source preparation for code blocks, with Prettier formatting behind a typed `ExampleFormatError`.
16
+ - **`renderMarkdown` / `renderMarkdownResult`** — the neutral plain-markdown emitter over the IR.
17
+ - **`parseLlmsTxtLine`, `filterLlmsTxt`, `generateStructuredLlmsTxt`, …** — pure text transforms over the llms.txt standard.
18
+ - **`apiScopeOf`, `unscopedName`, `normalizeBaseRoute`** — API scope naming helpers shared by every adapter.
19
+
20
+ ## Install
21
+
22
+ ```bash
23
+ npm install @tsdoctor/pages
24
+ # or
25
+ pnpm add @tsdoctor/pages
26
+ ```
27
+
28
+ Requires `effect` and `@effected/markdown` as peers.
29
+
30
+ ## License
31
+
32
+ MIT
package/Scope.js ADDED
@@ -0,0 +1,56 @@
1
+ //#region src/Scope.ts
2
+ /**
3
+ * API scope naming — the helpers that turn a package name and a base route
4
+ * into the identifiers every adapter must agree on.
5
+ *
6
+ * @remarks
7
+ * These sit beside the navigation tree rather than in an adapter because the
8
+ * scope string is load-bearing across frameworks: it keys Twoslash cache
9
+ * generations and names the per-package llms files. Two adapters deriving it
10
+ * differently would silently miss each other's caches. The multiVersion /
11
+ * i18n output-directory layout is NOT here — that is a framework's product
12
+ * policy and stays adapter-side.
13
+ *
14
+ * @packageDocumentation
15
+ */
16
+ /**
17
+ * Extract the unscoped name from a possibly scoped package name:
18
+ * `@scope/pkg` becomes `pkg`, `pkg` stays `pkg`.
19
+ *
20
+ * @public
21
+ */
22
+ function unscopedName(packageName) {
23
+ return packageName.startsWith("@") ? packageName.split("/")[1] ?? packageName : packageName;
24
+ }
25
+ /**
26
+ * Normalize a base route: ensure a leading slash, strip a trailing slash,
27
+ * and keep the root as `/`.
28
+ *
29
+ * @public
30
+ */
31
+ function normalizeBaseRoute(route) {
32
+ const withSlash = route.startsWith("/") ? route : `/${route}`;
33
+ const stripped = withSlash.endsWith("/") ? withSlash.slice(0, -1) : withSlash;
34
+ return stripped === "" ? "/" : stripped;
35
+ }
36
+ /**
37
+ * The API scope key derived from a base route: its first path segment,
38
+ * falling back to the package name so a single-API site mounted at `/`
39
+ * still gets a non-empty scope.
40
+ *
41
+ * @remarks
42
+ * Load-bearing and previously duplicated. Config resolution registers each
43
+ * API's Twoslash environment under this key and the build program looks it
44
+ * up by the same key; if two derivations disagree, every lookup misses and
45
+ * per-scope type-checking degrades to build-wide with no error and nothing
46
+ * visibly wrong in the output. One definition matters more than the
47
+ * duplication was costing.
48
+ *
49
+ * @public
50
+ */
51
+ function apiScopeOf(baseRoute, packageName) {
52
+ return baseRoute.replace(/^\//, "").split("/")[0] || packageName;
53
+ }
54
+
55
+ //#endregion
56
+ export { apiScopeOf, normalizeBaseRoute, unscopedName };
@@ -0,0 +1,63 @@
1
+ //#region src/TwoslashDirectives.ts
2
+ /**
3
+ * Twoslash directive detection — the regexes that decide which lines of a
4
+ * code block are notation rather than code.
5
+ *
6
+ * @remarks
7
+ * These mirror the upstream Twoslash source
8
+ * (`twoslashes/twoslash`, `packages/twoslash/src/regexp.ts`). All patterns
9
+ * allow an optional space after `//`, so both `// @noErrors` and `//@noErrors`
10
+ * are recognized, as Twoslash itself does. They live in the IR package
11
+ * because the display/source split depends on them and both adapters must
12
+ * strip the same lines.
13
+ *
14
+ * @packageDocumentation
15
+ */
16
+ /**
17
+ * Config directives: boolean flags and key-value pairs.
18
+ *
19
+ * Upstream: `reConfigBoolean` + `reConfigValue` + `reFilenamesMakers` —
20
+ * `// @noErrors`, `//@strict`, `// @errors: 2304`, `// @filename: example.ts`.
21
+ */
22
+ const RE_CONFIG = /^\/\/\s?@\w+/;
23
+ /**
24
+ * Annotation markers: query, completion, and highlight markers.
25
+ *
26
+ * Upstream: `reAnnonateMarkers` — `/^\s*\/\/\s*\^(\?|\||\^+)( .*)?$/gm`. After
27
+ * `line.trim()` leading whitespace is gone but the spaces between `//` and
28
+ * `^` are preserved, so `// ^?` still matches.
29
+ */
30
+ const RE_ANNOTATION = /^\/\/\s*\^[?|^]/;
31
+ /**
32
+ * Cut directives: `// ---cut---`, `//---cut-before---`, `// ---cut-after---`,
33
+ * `// ---cut-start---`, `// ---cut-end---`.
34
+ */
35
+ const RE_CUT = /^\/\/\s?---cut/;
36
+ /**
37
+ * Test whether a trimmed line is any Twoslash directive — a config flag or
38
+ * value, a filename marker, an annotation marker or a cut directive.
39
+ *
40
+ * @param trimmedLine - The line with leading and trailing whitespace removed
41
+ * @returns `true` if the line is a Twoslash directive
42
+ * @public
43
+ */
44
+ function isTwoslashDirective(trimmedLine) {
45
+ return RE_CONFIG.test(trimmedLine) || RE_ANNOTATION.test(trimmedLine) || RE_CUT.test(trimmedLine);
46
+ }
47
+ /**
48
+ * Classify a cut directive line.
49
+ *
50
+ * @param trimmedLine - The line with leading and trailing whitespace removed
51
+ * @returns The cut form, or `null` if the line is not a cut directive
52
+ * @public
53
+ */
54
+ function classifyCutDirective(trimmedLine) {
55
+ if (/^\/\/\s?---cut(-before)?---$/.test(trimmedLine)) return "cut-before";
56
+ if (/^\/\/\s?---cut-after---$/.test(trimmedLine)) return "cut-after";
57
+ if (/^\/\/\s?---cut-start---$/.test(trimmedLine)) return "cut-start";
58
+ if (/^\/\/\s?---cut-end---$/.test(trimmedLine)) return "cut-end";
59
+ return null;
60
+ }
61
+
62
+ //#endregion
63
+ export { classifyCutDirective, isTwoslashDirective };
package/WorkItems.js ADDED
@@ -0,0 +1,162 @@
1
+ import { ApiItems, EntryPoints, Routes, SyntheticBases } from "@tsdoctor/model";
2
+
3
+ //#region src/WorkItems.ts
4
+ /**
5
+ * Cross-link priority by API item kind (lower = higher priority). When a bare
6
+ * name maps to multiple pages (the const+type companion pattern), the bare
7
+ * cross-link resolves to the higher-priority kind — value declarations win
8
+ * over type-only declarations, so `Foo` links to the importable schema, not
9
+ * the type.
10
+ */
11
+ const CROSS_LINK_KIND_PRIORITY = {
12
+ Class: 0,
13
+ Function: 1,
14
+ Variable: 2,
15
+ Enum: 3,
16
+ Interface: 4,
17
+ TypeAlias: 5,
18
+ Namespace: 6
19
+ };
20
+ /**
21
+ * Lower number = higher priority for which page a bare cross-link name
22
+ * resolves to.
23
+ *
24
+ * @public
25
+ */
26
+ function crossLinkKindPriority(kind) {
27
+ return CROSS_LINK_KIND_PRIORITY[kind] ?? 100;
28
+ }
29
+ /**
30
+ * Prepare the flat list of work items to build and the cross-link maps.
31
+ *
32
+ * @remarks
33
+ * Resolves entry points into deduplicated items, detects synthetic base
34
+ * declarations (excluded from categorization, collision detection and work
35
+ * items — the owner class page renders them inline), categorizes, detects
36
+ * route collisions on the lowercased `folder/name` route, builds the route
37
+ * map with bare names owned by the highest-priority kind and member routes
38
+ * from the model's anchors, adds namespace member routes (qualified always,
39
+ * unqualified PascalCase when unambiguous), routes synthetic base names to
40
+ * the owner's `#base-class` anchor, and flattens everything into work items.
41
+ *
42
+ * @public
43
+ */
44
+ function prepareWorkItems(input) {
45
+ const { apiPackage, categories, baseRoute } = input;
46
+ const resolvedItems = EntryPoints.resolve(apiPackage);
47
+ const syntheticBases = SyntheticBases.detect(resolvedItems.map((r) => r.item));
48
+ const docItems = syntheticBases.bases.size ? resolvedItems.filter((r) => !syntheticBases.bases.has(r.item)) : resolvedItems;
49
+ const resolvedLookup = /* @__PURE__ */ new Map();
50
+ for (const resolved of docItems) resolvedLookup.set(`${resolved.item.displayName}::${resolved.item.kind}`, resolved);
51
+ const { items, uncategorized } = ApiItems.categorize(docItems, categories);
52
+ const namespaceMembers = ApiItems.namespaceMembers(docItems);
53
+ const categoryFor = (item) => Object.entries(categories).find(([, config]) => config.itemKinds?.includes(item.kind));
54
+ const candidates = [];
55
+ for (const [categoryKey, categoryConfig] of Object.entries(categories)) for (const item of items[categoryKey] || []) candidates.push(new Routes.RouteCandidate({
56
+ id: `${item.displayName}::${item.kind}`,
57
+ displayName: item.displayName,
58
+ folder: categoryConfig.folderName,
59
+ baseName: item.displayName.toLowerCase(),
60
+ kind: String(item.kind),
61
+ canonicalRef: item.canonicalReference?.toString() ?? item.displayName
62
+ }));
63
+ for (const nsMember of namespaceMembers) {
64
+ const nsCategory = categoryFor(nsMember.item);
65
+ if (!nsCategory) continue;
66
+ candidates.push(new Routes.RouteCandidate({
67
+ id: nsMember.qualifiedName,
68
+ displayName: nsMember.qualifiedName,
69
+ folder: nsCategory[1].folderName,
70
+ baseName: nsMember.qualifiedName.toLowerCase(),
71
+ kind: String(nsMember.item.kind),
72
+ canonicalRef: nsMember.item.canonicalReference?.toString() ?? nsMember.qualifiedName
73
+ }));
74
+ }
75
+ const collisions = Routes.detectCollisions(candidates);
76
+ const routes = /* @__PURE__ */ new Map();
77
+ const kinds = /* @__PURE__ */ new Map();
78
+ const routeOwnerPriority = /* @__PURE__ */ new Map();
79
+ for (const [categoryKey, categoryConfig] of Object.entries(categories)) for (const item of items[categoryKey] || []) {
80
+ const itemRoute = `${baseRoute}/${categoryConfig.folderName}/${item.displayName.toLowerCase()}`;
81
+ const priority = crossLinkKindPriority(String(item.kind));
82
+ const existingPriority = routeOwnerPriority.get(item.displayName);
83
+ if (existingPriority === void 0 || priority < existingPriority) {
84
+ routes.set(item.displayName, itemRoute);
85
+ kinds.set(item.displayName, item.kind);
86
+ routeOwnerPriority.set(item.displayName, priority);
87
+ }
88
+ if (item.kind === "Class" || item.kind === "Interface") {
89
+ const itemWithMembers = item;
90
+ const anchors = ApiItems.memberAnchors(itemWithMembers);
91
+ const byCanonicalRef = new Map(itemWithMembers.members.map((member) => [member.canonicalReference?.toString() ?? member.displayName, member]));
92
+ for (const [routeKey, memberId] of ApiItems.memberRouteKeys(itemWithMembers)) {
93
+ const member = byCanonicalRef.get(memberId);
94
+ if (!member) continue;
95
+ const anchor = anchors.get(memberId) ?? Routes.memberAnchor(member.displayName);
96
+ routes.set(routeKey, `${itemRoute}#${anchor}`);
97
+ kinds.set(routeKey, member.kind);
98
+ }
99
+ }
100
+ }
101
+ const unqualifiedNameCounts = /* @__PURE__ */ new Map();
102
+ for (const nsMember of namespaceMembers) {
103
+ const name = nsMember.item.displayName;
104
+ unqualifiedNameCounts.set(name, (unqualifiedNameCounts.get(name) || 0) + 1);
105
+ }
106
+ for (const nsMember of namespaceMembers) {
107
+ const category = categoryFor(nsMember.item);
108
+ if (!category) continue;
109
+ const qualifiedRoute = `${baseRoute}/${category[1].folderName}/${nsMember.qualifiedName.toLowerCase()}`;
110
+ routes.set(nsMember.qualifiedName, qualifiedRoute);
111
+ kinds.set(nsMember.qualifiedName, nsMember.item.kind);
112
+ const displayName = nsMember.item.displayName;
113
+ if (/^[A-Z]/.test(displayName) && (unqualifiedNameCounts.get(displayName) || 0) <= 1 && !routes.has(displayName)) {
114
+ routes.set(displayName, qualifiedRoute);
115
+ kinds.set(displayName, nsMember.item.kind);
116
+ }
117
+ }
118
+ for (const [baseItem, syntheticBase] of syntheticBases.bases) {
119
+ const baseName = baseItem.displayName;
120
+ if (routes.has(baseName)) continue;
121
+ const owner = syntheticBase.ownerClasses[0];
122
+ const ownerRoute = owner ? routes.get(owner.displayName) : void 0;
123
+ if (!ownerRoute) continue;
124
+ routes.set(baseName, `${ownerRoute}#${SyntheticBases.BASE_CLASS_ANCHOR}`);
125
+ kinds.set(baseName, baseItem.kind);
126
+ }
127
+ const workItems = [];
128
+ for (const [categoryKey, categoryConfig] of Object.entries(categories)) for (const item of items[categoryKey] || []) {
129
+ const resolved = resolvedLookup.get(`${item.displayName}::${item.kind}`);
130
+ const syntheticBase = syntheticBases.baseByOwner.get(item);
131
+ const memberAnchors = item.kind === "Class" || item.kind === "Interface" ? ApiItems.memberAnchors(item) : void 0;
132
+ workItems.push({
133
+ item,
134
+ categoryKey,
135
+ categoryConfig,
136
+ ...resolved?.availableFrom != null ? { availableFrom: resolved.availableFrom } : {},
137
+ ...syntheticBase != null ? { syntheticBase } : {},
138
+ ...memberAnchors != null ? { memberAnchors } : {}
139
+ });
140
+ }
141
+ for (const nsMember of namespaceMembers) {
142
+ const category = categoryFor(nsMember.item);
143
+ if (category) workItems.push({
144
+ item: nsMember.item,
145
+ categoryKey: category[0],
146
+ categoryConfig: category[1],
147
+ namespaceMember: nsMember
148
+ });
149
+ }
150
+ return {
151
+ workItems,
152
+ crossLinkData: {
153
+ routes,
154
+ kinds
155
+ },
156
+ uncategorized,
157
+ collisions
158
+ };
159
+ }
160
+
161
+ //#endregion
162
+ export { crossLinkKindPriority, prepareWorkItems };