@doc-kit/core 0.0.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.
Files changed (75) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/LICENSE +21 -0
  3. package/README.md +36 -0
  4. package/package.json +88 -0
  5. package/shiki.config.mjs +50 -0
  6. package/src/caching.mjs +183 -0
  7. package/src/generators/ast/README.md +8 -0
  8. package/src/generators/ast/constants.mjs +1 -0
  9. package/src/generators/ast/generate.mjs +125 -0
  10. package/src/generators/ast/index.mjs +20 -0
  11. package/src/generators/ast/types.d.ts +7 -0
  12. package/src/generators/ast-js/README.md +8 -0
  13. package/src/generators/ast-js/generate.mjs +56 -0
  14. package/src/generators/ast-js/index.mjs +24 -0
  15. package/src/generators/ast-js/types.d.ts +5 -0
  16. package/src/generators/index.mjs +48 -0
  17. package/src/generators/json-simple/README.md +9 -0
  18. package/src/generators/json-simple/generate.mjs +33 -0
  19. package/src/generators/json-simple/index.mjs +23 -0
  20. package/src/generators/json-simple/types.d.ts +6 -0
  21. package/src/generators/loader.mjs +96 -0
  22. package/src/generators/metadata/README.md +8 -0
  23. package/src/generators/metadata/constants.mjs +68 -0
  24. package/src/generators/metadata/generate.mjs +40 -0
  25. package/src/generators/metadata/index.mjs +21 -0
  26. package/src/generators/metadata/maps/builtin.json +16 -0
  27. package/src/generators/metadata/maps/mdn.json +1114 -0
  28. package/src/generators/metadata/types.d.ts +145 -0
  29. package/src/generators/metadata/utils/parse.mjs +160 -0
  30. package/src/generators/metadata/utils/resolveTypes.mjs +204 -0
  31. package/src/generators/metadata/utils/slugger.mjs +39 -0
  32. package/src/generators/metadata/utils/transformers.mjs +109 -0
  33. package/src/generators/metadata/utils/visitors.mjs +134 -0
  34. package/src/generators/metadata/utils/yaml.mjs +58 -0
  35. package/src/generators/types.d.ts +129 -0
  36. package/src/generators.mjs +145 -0
  37. package/src/logger/constants.mjs +34 -0
  38. package/src/logger/index.mjs +27 -0
  39. package/src/logger/logger.mjs +177 -0
  40. package/src/logger/transports/console.mjs +54 -0
  41. package/src/logger/transports/github.mjs +42 -0
  42. package/src/logger/transports/index.mjs +11 -0
  43. package/src/logger/types.d.ts +29 -0
  44. package/src/logger/utils/colors.mjs +18 -0
  45. package/src/logger/utils/time.mjs +21 -0
  46. package/src/parsers/markdown.mjs +49 -0
  47. package/src/parsers/types.d.ts +7 -0
  48. package/src/threading/chunk-worker.mjs +23 -0
  49. package/src/threading/index.mjs +27 -0
  50. package/src/threading/parallel.mjs +140 -0
  51. package/src/utils/array.mjs +7 -0
  52. package/src/utils/configuration/index.mjs +263 -0
  53. package/src/utils/configuration/templates.mjs +21 -0
  54. package/src/utils/configuration/types.d.ts +64 -0
  55. package/src/utils/file.mjs +20 -0
  56. package/src/utils/generators.mjs +71 -0
  57. package/src/utils/highlighter.mjs +198 -0
  58. package/src/utils/html-minifier.mjs +12 -0
  59. package/src/utils/loaders.mjs +48 -0
  60. package/src/utils/misc.mjs +72 -0
  61. package/src/utils/queries/constants.mjs +3 -0
  62. package/src/utils/queries/index.mjs +93 -0
  63. package/src/utils/queries/utils.mjs +45 -0
  64. package/src/utils/remark.mjs +97 -0
  65. package/src/utils/signature/constants.mjs +12 -0
  66. package/src/utils/signature/parseList.mjs +133 -0
  67. package/src/utils/signature/parseSignature.mjs +208 -0
  68. package/src/utils/signature/types.d.ts +66 -0
  69. package/src/utils/swc.mjs +34 -0
  70. package/src/utils/type-annotations/hast.mjs +115 -0
  71. package/src/utils/type-annotations/mdast.mjs +75 -0
  72. package/src/utils/type-annotations/remark.mjs +24 -0
  73. package/src/utils/type-annotations/syntax.mjs +188 -0
  74. package/src/utils/unist.mjs +75 -0
  75. package/src/utils/url.mjs +27 -0
@@ -0,0 +1,145 @@
1
+ import type { Position } from 'unist';
2
+ import type { Root, Node, Data, Blockquote, Heading } from 'mdast';
3
+
4
+ export type Generator = GeneratorMetadata<
5
+ {},
6
+ Generate<Array<Root>, AsyncGenerator<MetadataEntry>>,
7
+ ProcessChunk<Root, MetadataEntry, Record<string, string>>
8
+ >;
9
+
10
+ /**
11
+ * Utility type to augment AST nodes with typed data properties.
12
+ *
13
+ * This ensures type safety when working with nodes that have been
14
+ * enhanced with additional metadata during processing.
15
+ *
16
+ * @template T - The base AST node type
17
+ * @template J - The data structure to attach
18
+ */
19
+ type NodeWithData<T extends Node, J extends Data> = T & {
20
+ data: J;
21
+ };
22
+
23
+ export interface ChangeEntry {
24
+ // The Node.js version or versions where said change was introduced simultaneously
25
+ version: string | Array<string>;
26
+ // The GitHub PR URL of said change
27
+ 'pr-url': string | undefined;
28
+ // The description of said change
29
+ description: string;
30
+ }
31
+
32
+ /**
33
+ * YAML frontmatter properties commonly found in API documentation.
34
+ *
35
+ * These properties provide metadata about API elements including
36
+ * versioning, deprecation status, stability indicators, and
37
+ * additional documentation context.
38
+ */
39
+ export interface YAMLProperties {
40
+ /** Source position information for the YAML block */
41
+ yamlPosition?: Position;
42
+
43
+ // === Core Metadata ===
44
+ /** Type classification of the API element */
45
+ type?: string;
46
+ /** Link to the source code implementation */
47
+ source_link?: string;
48
+ /** Human-readable description for LLM processing */
49
+ llm_description?: string;
50
+ /** Associated tags for categorization */
51
+ tags?: Array<string>;
52
+
53
+ /** Version when this API was first added */
54
+ added?: string;
55
+ /** Version when this API was first added */ // TODO(@avivkeller): Merge this with `added`
56
+ introduced_in?: string;
57
+ /** Version when this API was deprecated */
58
+ deprecated?: string;
59
+ /** Version when this API was removed */
60
+ removed?: string;
61
+
62
+ /** N-API version requirement (legacy format) */
63
+ napiVersion?: string;
64
+
65
+ /** Array of change records with version info */
66
+ changes?: Array<ChangeEntry>;
67
+
68
+ /** Allow for additional YAML properties not explicitly defined */
69
+ [key: string]: unknown;
70
+ }
71
+
72
+ export type HeadingType =
73
+ | 'event'
74
+ | 'global'
75
+ | 'method'
76
+ | 'property'
77
+ | 'class'
78
+ | 'module'
79
+ | 'classMethod'
80
+ | 'ctor';
81
+
82
+ /**
83
+ * Data structure for enhanced heading nodes.
84
+ * Extracted as a separate interface for reusability and clarity.
85
+ */
86
+ export interface HeadingData extends Data {
87
+ /** Extracted plain text content of the heading */
88
+ text: string;
89
+ /** The name of the module that this heading refers to */
90
+ name: string;
91
+ /** URL-safe slug derived from heading text */
92
+ slug: string;
93
+ /** Optional type classification */
94
+ type?: HeadingType;
95
+ /**
96
+ * Marks the 2nd..nth heading of an overloaded function so it can be dropped
97
+ * from the ToC while still rendering on the page.
98
+ */
99
+ isOverload?: boolean;
100
+ }
101
+
102
+ /**
103
+ * Represents a processed heading that includes both the original
104
+ * AST structure and computed metadata like text content and URL slug.
105
+ */
106
+ export type HeadingNode = NodeWithData<Heading, HeadingData>;
107
+
108
+ /**
109
+ * Data structure for stability index entries.
110
+ */
111
+ export interface StabilityData extends Data {
112
+ /** Numeric stability index (0-3) */
113
+ index: string;
114
+ /** Human-readable stability description */
115
+ description: string;
116
+ }
117
+
118
+ /**
119
+ * Individual stability entry with metadata.
120
+ */
121
+ export type StabilityNode = NodeWithData<Blockquote, StabilityData>;
122
+
123
+ /**
124
+ * Complete metadata entry for a single API documentation element.
125
+ *
126
+ * This represents the processed result of parsing a section of API
127
+ * documentation, containing all the structured information needed
128
+ * for documentation generation, search indexing, and validation.
129
+ */
130
+ export interface MetadataEntry extends YAMLProperties {
131
+ /** Path + API identification */
132
+ api: string;
133
+ path: string; // Note: this is extensionless
134
+ basename: string;
135
+ /** Whether this page was generated by a downstream generator. */
136
+ synthetic?: boolean;
137
+ /** Whether this page was authored as MDX (JSX-in-Markdown). */
138
+ mdx?: boolean;
139
+ /** Processed heading with metadata */
140
+ heading: HeadingNode;
141
+ /** Stability classification information */
142
+ stability: StabilityNode;
143
+ /** Main content as markdown AST */
144
+ content: Root;
145
+ }
@@ -0,0 +1,160 @@
1
+ 'use strict';
2
+
3
+ import { basename, sep } from 'node:path/posix';
4
+
5
+ import { slug } from 'github-slugger';
6
+ import { u as createTree } from 'unist-builder';
7
+ import { findAfter } from 'unist-util-find-after';
8
+ import { remove } from 'unist-util-remove';
9
+ import { selectAll } from 'unist-util-select';
10
+ import { SKIP, visit } from 'unist-util-visit';
11
+
12
+ import createNodeSlugger from './slugger.mjs';
13
+ import { transformNodeToHeading } from './transformers.mjs';
14
+ import {
15
+ visitLinkReference,
16
+ visitMarkdownLink,
17
+ visitStability,
18
+ visitTextWithUnixManualNode,
19
+ visitYAML,
20
+ } from './visitors.mjs';
21
+ import { UNIST } from '../../../utils/queries/index.mjs';
22
+ import { getRemark as remark } from '../../../utils/remark.mjs';
23
+ import { relative } from '../../../utils/url.mjs';
24
+ import {
25
+ DEPRECATION_HEADING_REGEX,
26
+ IGNORE_STABILITY_STEMS,
27
+ } from '../constants.mjs';
28
+ import { resolveTypeAnnotations } from './resolveTypes.mjs';
29
+
30
+ /**
31
+ * This generator generates a flattened list of metadata entries from a API doc
32
+ *
33
+ * @param {{ tree: import('mdast.Root'), mdx?: boolean } & import('../types').MetadataEntry} input
34
+ * @param {Record<string, string>} typeMap
35
+ * @returns {Array<import('../types').MetadataEntry>}
36
+ */
37
+ export const parseApiDoc = ({ path, tree, mdx = false }, typeMap) => {
38
+ /**
39
+ * Collection of metadata entries for the file
40
+ * @type {Array<import('../types').MetadataEntry>}
41
+ */
42
+ const metadataCollection = [];
43
+
44
+ // Creates a new Slugger instance for the current API doc file
45
+ const nodeSlugger = createNodeSlugger();
46
+
47
+ // Slug the API (We use a non-class slugger, since we are fairly certain that `path` is unique)
48
+ const api = slug(path.slice(1).replace(sep, '-'));
49
+
50
+ /**
51
+ * Creates a stable slug for a heading in the current API document.
52
+ * @param {string} text Heading text to slug.
53
+ * @returns {string} The generated heading slug.
54
+ */
55
+ const getHeadingSlug = text => {
56
+ const deprecationHeading = DEPRECATION_HEADING_REGEX.exec(text);
57
+
58
+ return deprecationHeading ? deprecationHeading[1] : nodeSlugger.slug(text);
59
+ };
60
+
61
+ // Get all Markdown Footnote definitions from the tree
62
+ const markdownDefinitions = selectAll('definition', tree);
63
+
64
+ // Get all Markdown Heading entries from the tree
65
+ const headingNodes = selectAll('heading', tree);
66
+
67
+ // Handles Markdown link references and updates them to be plain links
68
+ visit(tree, UNIST.isLinkReference, node =>
69
+ visitLinkReference(node, markdownDefinitions)
70
+ );
71
+
72
+ // Removes all the original definitions from the tree as they are not needed anymore
73
+ remove(tree, markdownDefinitions);
74
+
75
+ // Make all the typeMap links relative to us
76
+ const relativeTypeMap = Object.fromEntries(
77
+ Object.entries(typeMap).map(([type, url]) => [type, relative(url, path)])
78
+ );
79
+
80
+ // Resolve every type annotation in the file at once (one TypeScript parse
81
+ // per file). MDX trees never contain `typeAnnotation` nodes — there,
82
+ // `{...}` is a real expression — so this is skipped.
83
+ if (!mdx) {
84
+ resolveTypeAnnotations(tree, relativeTypeMap, path);
85
+ }
86
+
87
+ // Handles the normalisation URLs that reference to API doc files with .md extension
88
+ visit(tree, UNIST.isMarkdownUrl, node => visitMarkdownLink(node));
89
+
90
+ // If the document has no headings but it has content, we add a fake heading to the top
91
+ if (headingNodes.length === 0 && tree.children.length > 0) {
92
+ tree.children.unshift(createTree('heading', { depth: 1 }, []));
93
+ }
94
+
95
+ // On "About this Documentation", we define the stability indices, and thus
96
+ // we don't need to check it for stability references
97
+ const ignoreStability = IGNORE_STABILITY_STEMS.includes(api);
98
+
99
+ // Process each heading and create metadata entries
100
+ visit(tree, UNIST.isHeading, (headingNode, index) => {
101
+ // Initialize heading
102
+ headingNode.data = transformNodeToHeading(headingNode);
103
+ // Initialize the metadata
104
+ const metadata = /** @type {import('../types').MetadataEntry} */ ({
105
+ api,
106
+ path,
107
+ mdx,
108
+ basename: basename(path),
109
+ heading: headingNode,
110
+ });
111
+
112
+ // Generate slug and update heading data
113
+ metadata.heading.data.slug = getHeadingSlug(metadata.heading.data.text);
114
+
115
+ // Find the next heading to determine section boundaries
116
+ const nextHeadingNode =
117
+ findAfter(tree, index, UNIST.isHeading) ?? headingNode;
118
+
119
+ const stop =
120
+ headingNode === nextHeadingNode
121
+ ? tree.children.length
122
+ : tree.children.indexOf(nextHeadingNode);
123
+
124
+ // Create subtree for this section. If it's the first entry, we start from the
125
+ // beginning of the tree to ensure top-level nodes (like YAML) are captured.
126
+ const startIndex = metadataCollection.length === 0 ? 0 : index;
127
+ const subTree = createTree('root', tree.children.slice(startIndex, stop));
128
+
129
+ visit(subTree, UNIST.isStabilityNode, node =>
130
+ visitStability(node, ignoreStability ? undefined : metadata)
131
+ );
132
+
133
+ // Process YAML nodes directly - merge all YAML data
134
+ visit(subTree, UNIST.isYamlNode, node => visitYAML(node, metadata));
135
+
136
+ // If YAML data contains a 'type', use it to override heading type
137
+ if (metadata.type) {
138
+ metadata.heading.data.type = metadata.type;
139
+ }
140
+
141
+ // Process Unix manual references
142
+ visit(subTree, UNIST.isTextWithUnixManual, (node, _, parent) =>
143
+ visitTextWithUnixManualNode(node, parent)
144
+ );
145
+
146
+ // Remove processed YAML nodes from the content
147
+ remove(subTree, [UNIST.isYamlNode]);
148
+
149
+ // Apply AST transformations
150
+ const parsedSubTree = remark().runSync(subTree);
151
+ metadata.content = parsedSubTree;
152
+
153
+ // Add to collection
154
+ metadataCollection.push(metadata);
155
+
156
+ return SKIP;
157
+ });
158
+
159
+ return metadataCollection;
160
+ };
@@ -0,0 +1,204 @@
1
+ import { parseSync } from '@swc/wasm';
2
+ import { visit } from 'unist-util-visit';
3
+
4
+ import { lookupTypeName, resolveTypeReference } from './transformers.mjs';
5
+ import logger from '../../../logger/index.mjs';
6
+ import { walk } from '../../../utils/swc.mjs';
7
+
8
+ // `null` is a keyword kind in SWC, unlike most parsers which treat it as a literal type
9
+ const KEYWORDS = new Set([
10
+ 'string',
11
+ 'number',
12
+ 'boolean',
13
+ 'any',
14
+ 'unknown',
15
+ 'never',
16
+ 'void',
17
+ 'undefined',
18
+ 'object',
19
+ 'bigint',
20
+ 'symbol',
21
+ 'null',
22
+ ]);
23
+
24
+ /**
25
+ * Parses a wrapped `type $Ti = <value>;` statement
26
+ */
27
+ const parseAliases = values => {
28
+ const prefixes = [];
29
+ let offset = 0;
30
+
31
+ const source = values
32
+ .map((value, i) => {
33
+ const decl = `type $T${i} = `;
34
+ prefixes.push(offset + decl.length);
35
+ const line = `${decl}${value};\n`;
36
+ offset += line.length;
37
+ return line;
38
+ })
39
+ .join('');
40
+
41
+ let program;
42
+
43
+ try {
44
+ program = parseSync(source, { syntax: 'typescript' });
45
+ } catch {
46
+ return undefined;
47
+ }
48
+
49
+ // SWC spans are absolute and keep growing across parse calls; the module's
50
+ // span begins at the first token, which is the start of our source
51
+ const spanOffset = program.span.start;
52
+
53
+ const identifiersPerAlias = values.map(() => []);
54
+
55
+ /**
56
+ *
57
+ */
58
+ const record = node => {
59
+ const start = node.span.start - spanOffset;
60
+ const end = node.span.end - spanOffset;
61
+
62
+ const index = Math.max(
63
+ 0,
64
+ prefixes.findLastIndex(prefix => prefix <= start)
65
+ );
66
+
67
+ const base = prefixes[index];
68
+ const text = source.slice(start, end);
69
+
70
+ identifiersPerAlias[index].push({
71
+ start: start - base,
72
+ end: end - base,
73
+ text,
74
+ lookup: text,
75
+ });
76
+ };
77
+
78
+ walk(program, {
79
+ /**
80
+ *
81
+ */
82
+ TsTypeReference: node => record(node.typeName), // Promise<T>, vm.Module
83
+ /**
84
+ *
85
+ */
86
+ TsTypeQuery: node => record(node.exprName), // typeof Foo
87
+ /**
88
+ *
89
+ */
90
+ TsImportType: node => node.qualifier && record(node.qualifier), // import('fs').Stats
91
+ /**
92
+ *
93
+ */
94
+ TsKeywordType: node => KEYWORDS.has(node.kind) && record(node), // string, null, ...
95
+ TsThisType: record,
96
+ });
97
+
98
+ return identifiersPerAlias.map(identifiers => ({ identifiers }));
99
+ };
100
+
101
+ /**
102
+ * Parses type values
103
+ */
104
+ export const parseTypeValues = values => {
105
+ const batch = parseAliases(values);
106
+
107
+ if (batch) {
108
+ return batch;
109
+ }
110
+
111
+ return values.map(value => parseAliases([value])?.[0] ?? { error: true });
112
+ };
113
+
114
+ /**
115
+ * Resolves a type value as a `|`-separated union of display names (e.g.
116
+ * `HTTP/2 Headers Object | HTTP/2 Raw Headers`). Map keys may contain spaces
117
+ * and slashes, so such values may not parse as TypeScript, but each part is
118
+ * still resolvable on its own.
119
+ *
120
+ * @param {string} value The type value
121
+ * @param {Record<string, string>} typeMap The toolchain mapping of types to links
122
+ * @returns {Array<{ start: number, end: number, text: string, href: string }>} The resolved links
123
+ */
124
+ const resolveDisplayNames = (value, typeMap) => {
125
+ const links = [];
126
+
127
+ for (const { 0: part, index } of value.matchAll(/(?:[^|]|\|\|)+/g)) {
128
+ // An array of a type links to the type itself
129
+ const name = part.trim().replace(/(\[\])+$/, '');
130
+ const href = name && resolveTypeReference(name, typeMap);
131
+
132
+ if (href) {
133
+ const start = index + part.indexOf(name);
134
+
135
+ links.push({ start, end: start + name.length, text: name, href });
136
+ }
137
+ }
138
+
139
+ return links;
140
+ };
141
+
142
+ /**
143
+ * Resolves a type value's links: the whole value when it is a map key in its
144
+ * own right, then its parsed identifiers, then its display names
145
+ *
146
+ * @param {string} value The type value
147
+ * @param {Record<string, string>} typeMap The toolchain mapping of types to links
148
+ * @param {{ error?: boolean, identifiers?: Array<object> }} result The parse of `value`
149
+ * @returns {Array<{ start: number, end: number, text: string, href: string }>} The resolved links
150
+ */
151
+ const resolveLinks = (value, typeMap, result) => {
152
+ const url = lookupTypeName(value, typeMap);
153
+
154
+ if (url) {
155
+ return [{ start: 0, end: value.length, text: value, href: url }];
156
+ }
157
+
158
+ if (result.error) {
159
+ return resolveDisplayNames(value, typeMap);
160
+ }
161
+
162
+ return (
163
+ result.identifiers
164
+ .map(({ start, end, text, lookup }) => ({
165
+ start,
166
+ end,
167
+ text,
168
+ href: resolveTypeReference(lookup, typeMap),
169
+ }))
170
+ .filter(({ href }) => href)
171
+ // The hast handlers slice the value by these ranges in order
172
+ .sort((a, b) => a.start - b.start)
173
+ );
174
+ };
175
+
176
+ /**
177
+ * Resolves every `typeAnnotation` node in a file's tree
178
+ */
179
+ export const resolveTypeAnnotations = (tree, typeMap, path) => {
180
+ const nodes = [];
181
+
182
+ // A visitor must not return a value: `visit` reads a number as the index to
183
+ // continue from, which would skip siblings
184
+ visit(tree, 'typeAnnotation', node => {
185
+ nodes.push(node);
186
+ });
187
+
188
+ parseTypeValues(nodes.map(({ value }) => value)).forEach((result, index) => {
189
+ const node = nodes[index];
190
+ const links = resolveLinks(node.value, typeMap, result);
191
+
192
+ // Unparseable display names must not be highlighted as the TypeScript
193
+ // they are not (which colours the `/` and `2` of `HTTP/2`)
194
+ node.data = { links, typescript: !result.error };
195
+
196
+ if (result.error && !links.length) {
197
+ node.data.parseError = true;
198
+
199
+ logger.warn(`Invalid type annotation: {${node.value}}`, {
200
+ file: { path, position: node.position },
201
+ });
202
+ }
203
+ });
204
+ };
@@ -0,0 +1,39 @@
1
+ 'use strict';
2
+
3
+ import GitHubSlugger, { slug as defaultSlugFn } from 'github-slugger';
4
+
5
+ import { DOC_API_SLUGS_REPLACEMENTS } from '../constants.mjs';
6
+
7
+ /**
8
+ * Creates a modified version of the GitHub Slugger
9
+ *
10
+ * @returns {InstanceType<typeof import('github-slugger').default>} The modified GitHub Slugger
11
+ */
12
+ const createNodeSlugger = () => {
13
+ const slugger = new GitHubSlugger();
14
+ const slugFn = slugger.slug.bind(slugger);
15
+
16
+ return {
17
+ ...slugger,
18
+ /**
19
+ * Creates a new slug based on the provided string
20
+ *
21
+ * @param {string} title The title to be parsed into a slug
22
+ */
23
+ // Applies certain string replacements that are specific
24
+ // to the way how Node.js generates slugs/anchor IDs
25
+ slug: title => slug(title, slugFn),
26
+ };
27
+ };
28
+
29
+ /**
30
+ * @param {string} title
31
+ * @param {typeof defaultSlugFn} slugFn
32
+ */
33
+ export const slug = (title, slugFn = defaultSlugFn) =>
34
+ DOC_API_SLUGS_REPLACEMENTS.reduce(
35
+ (piece, { from, to }) => piece.replace(from, to),
36
+ slugFn(title)
37
+ );
38
+
39
+ export default createNodeSlugger;
@@ -0,0 +1,109 @@
1
+ import {
2
+ DOC_MAN_BASE_URL,
3
+ DOC_API_HEADING_TYPES,
4
+ MODULE_QUALIFIED_NAME,
5
+ } from '../constants.mjs';
6
+ import { slug } from './slugger.mjs';
7
+ import { transformNodesToString } from '../../../utils/unist.mjs';
8
+ import BUILTIN_TYPE_MAP from '../maps/builtin.json' with { type: 'json' };
9
+ import MDN_TYPE_MAP from '../maps/mdn.json' with { type: 'json' };
10
+
11
+ /**
12
+ * @param {string} text The inner text
13
+ * @param {string} command The manual page
14
+ * @param {string} sectionNumber The manual section
15
+ * @param {string} sectionLetter The manual section number
16
+ */
17
+ export const transformUnixManualToLink = (
18
+ text,
19
+ command,
20
+ sectionNumber,
21
+ sectionLetter = ''
22
+ ) => {
23
+ return `[\`${text}\`](${DOC_MAN_BASE_URL}${sectionNumber}/${command}.${sectionNumber}${sectionLetter}.html)`;
24
+ };
25
+
26
+ /**
27
+ * Looks a type name up in the type maps: the toolchain-provided map first
28
+ * (Node.js types/modules), then the built-in (TC39) map, then MDN. Map keys
29
+ * may be arbitrary display names (e.g. `zlib options`), not just identifiers.
30
+ *
31
+ * @param {string} name The type name to look up
32
+ * @param {Record<string, string>} typeMap The toolchain mapping of types to links
33
+ * @returns {string} The reference URL or empty string if no match
34
+ */
35
+ export const lookupTypeName = (name, typeMap) => {
36
+ if (typeMap && name in typeMap) {
37
+ return typeMap[name];
38
+ }
39
+
40
+ const key = name.toLowerCase();
41
+
42
+ // Check in our built-in map (i.e. TC39 objects)
43
+ if (key in BUILTIN_TYPE_MAP) {
44
+ return BUILTIN_TYPE_MAP[key];
45
+ }
46
+
47
+ // Check in MDN
48
+ if (key in MDN_TYPE_MAP) {
49
+ return MDN_TYPE_MAP[key];
50
+ }
51
+
52
+ return '';
53
+ };
54
+
55
+ /**
56
+ * Resolves a type identifier to a documentation URL: map lookups first, then
57
+ * the dotted-name heuristic for Node.js types like `vm.Module` (which links
58
+ * to the module's page).
59
+ *
60
+ * @param {string} name The type identifier to resolve
61
+ * @param {Record<string, string>} typeMap The toolchain mapping of types to links
62
+ * @returns {string} The reference URL or empty string if no match
63
+ */
64
+ export const resolveTypeReference = (name, typeMap) => {
65
+ const url = lookupTypeName(name, typeMap);
66
+
67
+ if (url) {
68
+ return url;
69
+ }
70
+
71
+ // Transform Node.js types like 'vm.Something'.
72
+ if (MODULE_QUALIFIED_NAME.test(name)) {
73
+ const [mod, ...pieces] = name.split('.');
74
+ const isClass = pieces.at(-1).match(/^[A-Z][a-z]/);
75
+
76
+ return `${mod}.html#${isClass ? 'class-' : ''}${slug(name)}`;
77
+ }
78
+
79
+ return '';
80
+ };
81
+
82
+ /**
83
+ * Parses a raw Heading string into Heading metadata
84
+ *
85
+ * @param {import('mdast').Heading} heading The raw Heading text
86
+ * @param {number} depth The depth of the heading
87
+ * @returns {HeadingMetadataEntry} Parsed Heading entry
88
+ */
89
+ export const transformNodeToHeading = node => {
90
+ const text = transformNodesToString(node.children);
91
+
92
+ for (const { type, regex } of DOC_API_HEADING_TYPES) {
93
+ // Attempts to get a match from one of the heading types, if a match is found
94
+ // we use that type as the heading type, and extract the regex expression match group
95
+ // which should be the inner "plain" heading content (or the title of the heading for navigation)
96
+ const [, ...matches] = text.match(regex) ?? [];
97
+
98
+ if (matches?.length) {
99
+ return {
100
+ text,
101
+ type,
102
+ // The highest match group should be used.
103
+ name: matches.filter(Boolean).at(-1),
104
+ };
105
+ }
106
+ }
107
+
108
+ return { text, name: text, depth: node.depth };
109
+ };