@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.
- package/CHANGELOG.md +16 -0
- package/LICENSE +21 -0
- package/README.md +36 -0
- package/package.json +88 -0
- package/shiki.config.mjs +50 -0
- package/src/caching.mjs +183 -0
- package/src/generators/ast/README.md +8 -0
- package/src/generators/ast/constants.mjs +1 -0
- package/src/generators/ast/generate.mjs +125 -0
- package/src/generators/ast/index.mjs +20 -0
- package/src/generators/ast/types.d.ts +7 -0
- package/src/generators/ast-js/README.md +8 -0
- package/src/generators/ast-js/generate.mjs +56 -0
- package/src/generators/ast-js/index.mjs +24 -0
- package/src/generators/ast-js/types.d.ts +5 -0
- package/src/generators/index.mjs +48 -0
- package/src/generators/json-simple/README.md +9 -0
- package/src/generators/json-simple/generate.mjs +33 -0
- package/src/generators/json-simple/index.mjs +23 -0
- package/src/generators/json-simple/types.d.ts +6 -0
- package/src/generators/loader.mjs +96 -0
- package/src/generators/metadata/README.md +8 -0
- package/src/generators/metadata/constants.mjs +68 -0
- package/src/generators/metadata/generate.mjs +40 -0
- package/src/generators/metadata/index.mjs +21 -0
- package/src/generators/metadata/maps/builtin.json +16 -0
- package/src/generators/metadata/maps/mdn.json +1114 -0
- package/src/generators/metadata/types.d.ts +145 -0
- package/src/generators/metadata/utils/parse.mjs +160 -0
- package/src/generators/metadata/utils/resolveTypes.mjs +204 -0
- package/src/generators/metadata/utils/slugger.mjs +39 -0
- package/src/generators/metadata/utils/transformers.mjs +109 -0
- package/src/generators/metadata/utils/visitors.mjs +134 -0
- package/src/generators/metadata/utils/yaml.mjs +58 -0
- package/src/generators/types.d.ts +129 -0
- package/src/generators.mjs +145 -0
- package/src/logger/constants.mjs +34 -0
- package/src/logger/index.mjs +27 -0
- package/src/logger/logger.mjs +177 -0
- package/src/logger/transports/console.mjs +54 -0
- package/src/logger/transports/github.mjs +42 -0
- package/src/logger/transports/index.mjs +11 -0
- package/src/logger/types.d.ts +29 -0
- package/src/logger/utils/colors.mjs +18 -0
- package/src/logger/utils/time.mjs +21 -0
- package/src/parsers/markdown.mjs +49 -0
- package/src/parsers/types.d.ts +7 -0
- package/src/threading/chunk-worker.mjs +23 -0
- package/src/threading/index.mjs +27 -0
- package/src/threading/parallel.mjs +140 -0
- package/src/utils/array.mjs +7 -0
- package/src/utils/configuration/index.mjs +263 -0
- package/src/utils/configuration/templates.mjs +21 -0
- package/src/utils/configuration/types.d.ts +64 -0
- package/src/utils/file.mjs +20 -0
- package/src/utils/generators.mjs +71 -0
- package/src/utils/highlighter.mjs +198 -0
- package/src/utils/html-minifier.mjs +12 -0
- package/src/utils/loaders.mjs +48 -0
- package/src/utils/misc.mjs +72 -0
- package/src/utils/queries/constants.mjs +3 -0
- package/src/utils/queries/index.mjs +93 -0
- package/src/utils/queries/utils.mjs +45 -0
- package/src/utils/remark.mjs +97 -0
- package/src/utils/signature/constants.mjs +12 -0
- package/src/utils/signature/parseList.mjs +133 -0
- package/src/utils/signature/parseSignature.mjs +208 -0
- package/src/utils/signature/types.d.ts +66 -0
- package/src/utils/swc.mjs +34 -0
- package/src/utils/type-annotations/hast.mjs +115 -0
- package/src/utils/type-annotations/mdast.mjs +75 -0
- package/src/utils/type-annotations/remark.mjs +24 -0
- package/src/utils/type-annotations/syntax.mjs +188 -0
- package/src/utils/unist.mjs +75 -0
- 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
|
+
};
|