@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,48 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { extname, isAbsolute, join } from 'node:path';
|
|
3
|
+
import { pathToFileURL } from 'node:url';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Converts a value to a parsed URL object.
|
|
7
|
+
*
|
|
8
|
+
* @param {string|URL} url - The URL string or URL object to parse
|
|
9
|
+
* @returns {URL|null} The parsed URL object, or null if parsing fails
|
|
10
|
+
*/
|
|
11
|
+
export const toParsedURL = url => (url instanceof URL ? url : URL.parse(url));
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Loads content from a URL or file path
|
|
15
|
+
* @param {string|URL} url The URL or file path to load
|
|
16
|
+
* @returns {Promise<string>} The content as a string
|
|
17
|
+
*/
|
|
18
|
+
export const loadFromURL = async url => {
|
|
19
|
+
const parsedUrl = toParsedURL(url);
|
|
20
|
+
|
|
21
|
+
if (!parsedUrl || parsedUrl.protocol === 'file:') {
|
|
22
|
+
// Load from file system
|
|
23
|
+
return readFile(parsedUrl ?? url, 'utf-8');
|
|
24
|
+
} else {
|
|
25
|
+
// Load from network
|
|
26
|
+
const response = await fetch(parsedUrl);
|
|
27
|
+
return response.text();
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Dynamically imports a module from a URL, using JSON import assertion if applicable.
|
|
33
|
+
*
|
|
34
|
+
* @param {string|URL} url - The URL of the module to import
|
|
35
|
+
* @returns {Promise<any>} The imported module
|
|
36
|
+
*/
|
|
37
|
+
export const importFromURL = async url => {
|
|
38
|
+
const useJSONAssertion = extname(String(url)) === '.json';
|
|
39
|
+
|
|
40
|
+
const parsed = toParsedURL(url);
|
|
41
|
+
|
|
42
|
+
const imported = await import(
|
|
43
|
+
parsed ?? pathToFileURL(isAbsolute(url) ? url : join(process.cwd(), url)),
|
|
44
|
+
useJSONAssertion ? { with: { type: 'json' } } : {}
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
return imported.default ?? imported;
|
|
48
|
+
};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Creates a lazy-initialized function that caches the result of the first invocation.
|
|
3
|
+
* @template {(...args: any[]) => any} T
|
|
4
|
+
* @param {T} fn - The function to be lazily executed
|
|
5
|
+
* @returns {T} A wrapper function that lazily executes fn and caches its result
|
|
6
|
+
*/
|
|
7
|
+
export const lazy = fn =>
|
|
8
|
+
(
|
|
9
|
+
c =>
|
|
10
|
+
(...args) =>
|
|
11
|
+
(c ??= fn(...args))
|
|
12
|
+
)();
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Checks if a value is a plain JavaScript object.
|
|
16
|
+
* @param {*} value - The value to check
|
|
17
|
+
* @returns {boolean} True if the value is a plain object, false otherwise
|
|
18
|
+
*/
|
|
19
|
+
export const isPlainObject = value => {
|
|
20
|
+
if (value === null || typeof value !== 'object') {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const prototype = Object.getPrototypeOf(value);
|
|
25
|
+
return prototype === Object.prototype || prototype === null;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Checks if a value is an async generator/iterable.
|
|
30
|
+
* @param {unknown} obj - Value to check
|
|
31
|
+
* @returns {obj is AsyncGenerator} True if the value is an async iterable
|
|
32
|
+
*/
|
|
33
|
+
export const isAsyncIterable = obj =>
|
|
34
|
+
obj !== null &&
|
|
35
|
+
typeof obj === 'object' &&
|
|
36
|
+
typeof obj[Symbol.asyncIterator] === 'function';
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Returns a shallow copy of `obj` without the specified keys.
|
|
40
|
+
* @param {Record<string, any>} obj
|
|
41
|
+
* @param {string[]} keys - Keys to exclude
|
|
42
|
+
* @returns {Record<string, any>}
|
|
43
|
+
*/
|
|
44
|
+
export const omitKeys = (obj, keys = []) =>
|
|
45
|
+
Object.fromEntries(
|
|
46
|
+
Object.entries(obj).filter(([key]) => !keys.includes(key))
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Recursively merges plain objects from left to right.
|
|
51
|
+
* @template T
|
|
52
|
+
* @param {...T} objects - Any number of objects to merge
|
|
53
|
+
* @returns {T} A new object containing the deep merge of all provided objects
|
|
54
|
+
*/
|
|
55
|
+
export const deepMerge = (...objects) => {
|
|
56
|
+
return objects.reduce((result, source) => {
|
|
57
|
+
for (const [key, sourceValue] of Object.entries(source)) {
|
|
58
|
+
if (sourceValue === undefined) {
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const targetValue = result[key];
|
|
63
|
+
result[key] = isPlainObject(sourceValue)
|
|
64
|
+
? deepMerge(isPlainObject(targetValue) ? targetValue : {}, sourceValue)
|
|
65
|
+
: Array.isArray(sourceValue)
|
|
66
|
+
? sourceValue.slice()
|
|
67
|
+
: sourceValue;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return result;
|
|
71
|
+
}, {});
|
|
72
|
+
};
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
import { transformNodesToString } from '../unist.mjs';
|
|
4
|
+
import { isTypedList } from './utils.mjs';
|
|
5
|
+
|
|
6
|
+
// This defines the actual REGEX Queries
|
|
7
|
+
export const QUERIES = {
|
|
8
|
+
// Fixes the references to Markdown pages into the API documentation
|
|
9
|
+
markdownUrl: /^(?![+a-zA-Z]+:)([^#?]+)\.md(#.+)?$/,
|
|
10
|
+
// ReGeX for handling Stability Indexes Metadata
|
|
11
|
+
stabilityIndex: /^Stability: ([0-5](?:\.[0-3])?)(?:\s*-\s*)?(.*)$/s,
|
|
12
|
+
// ReGeX for handling the Stability Index Prefix
|
|
13
|
+
stabilityIndexPrefix: /Stability: ([0-5](?:\.[0-3])?)/,
|
|
14
|
+
// ReGeX for retrieving the inner content from a YAML block
|
|
15
|
+
yamlInnerContent: /^<!--[ ]?(?:YAML([\s\S]*?)|([ \S]*?))?[ ]?-->/,
|
|
16
|
+
// ReGeX for standard Markdown YAML frontmatter
|
|
17
|
+
standardYamlFrontmatter: /^---\r?\n([\s\S]*?)\r?\n---/,
|
|
18
|
+
// ReGeX for finding references to Unix manuals
|
|
19
|
+
unixManualPage: /\b([a-z.]+)\((\d)([a-z]?)\)/g,
|
|
20
|
+
// ReGeX for determing a typed list's non-property names
|
|
21
|
+
typedListStarters: /^(Returns|Extends|Type):?\s*/,
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export const UNIST = {
|
|
25
|
+
/**
|
|
26
|
+
* @param {import('@types/mdast').Blockquote} blockquote
|
|
27
|
+
* @returns {boolean}
|
|
28
|
+
*/
|
|
29
|
+
isStabilityNode: ({ type, children }) =>
|
|
30
|
+
type === 'blockquote' &&
|
|
31
|
+
QUERIES.stabilityIndex.test(transformNodesToString(children)),
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* @param {import('@types/mdast').Html} html
|
|
35
|
+
* @returns {boolean}
|
|
36
|
+
*/
|
|
37
|
+
isYamlNode: ({ type, value }) =>
|
|
38
|
+
type === 'html' && QUERIES.yamlInnerContent.test(value),
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* @param {import('@types/mdast').Text} text
|
|
42
|
+
* @returns {boolean}
|
|
43
|
+
*/
|
|
44
|
+
isTextWithUnixManual: ({ type, value }) =>
|
|
45
|
+
type === 'text' && QUERIES.unixManualPage.test(value),
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* @param {import('@types/mdast').Link} link
|
|
49
|
+
* @returns {boolean}
|
|
50
|
+
*/
|
|
51
|
+
isMarkdownUrl: ({ type, url }) =>
|
|
52
|
+
type === 'link' && QUERIES.markdownUrl.test(url),
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* @param {import('@types/mdast').Heading} heading
|
|
56
|
+
* @returns {boolean}
|
|
57
|
+
*/
|
|
58
|
+
isHeading: ({ type, depth }) =>
|
|
59
|
+
type === 'heading' && depth >= 1 && depth <= 5,
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* @param {import('@types/mdast').LinkReference} linkReference
|
|
63
|
+
* @returns {boolean}
|
|
64
|
+
*/
|
|
65
|
+
isLinkReference: ({ type, identifier }) =>
|
|
66
|
+
type === 'linkReference' && !!identifier,
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* @param {import('@types/mdast').List} list
|
|
70
|
+
* @returns {boolean}
|
|
71
|
+
*/
|
|
72
|
+
isLooselyTypedList: list => Boolean(isTypedList(list)),
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* @param {import('@types/mdast').List} list
|
|
76
|
+
* @returns {boolean}
|
|
77
|
+
*/
|
|
78
|
+
isStronglyTypedList: list => {
|
|
79
|
+
const confidence = isTypedList(list);
|
|
80
|
+
|
|
81
|
+
if (confidence === 1) {
|
|
82
|
+
// This is a loosely typed list, but we can still check if it is strongly typed.
|
|
83
|
+
const [, secondNode, thirdNode] =
|
|
84
|
+
list.children?.[0]?.children?.[0]?.children ?? [];
|
|
85
|
+
|
|
86
|
+
return (
|
|
87
|
+
secondNode?.value?.trim() === '' && thirdNode?.type === 'typeAnnotation'
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return Boolean(confidence);
|
|
92
|
+
},
|
|
93
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { VALID_JAVASCRIPT_PROPERTY } from './constants.mjs';
|
|
2
|
+
import { QUERIES } from './index.mjs';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @param {import('@types/mdast').List} list
|
|
6
|
+
* @returns {0 | 1 | 2} confidence
|
|
7
|
+
*
|
|
8
|
+
* 0: This is not a typed list
|
|
9
|
+
* 1: This is a loosely typed list
|
|
10
|
+
* 2: This is a strongly typed list
|
|
11
|
+
*/
|
|
12
|
+
export const isTypedList = list => {
|
|
13
|
+
if (!list || list.type !== 'list') {
|
|
14
|
+
return 0;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const firstNode = list.children?.[0]?.children?.[0]?.children[0];
|
|
18
|
+
|
|
19
|
+
if (!firstNode) {
|
|
20
|
+
return 0;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const value = firstNode?.value?.trimStart();
|
|
24
|
+
|
|
25
|
+
// Typed list starters (strong signal)
|
|
26
|
+
if (value && QUERIES.typedListStarters.test(value)) {
|
|
27
|
+
return 2;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Direct type annotation: {Type}
|
|
31
|
+
if (firstNode.type === 'typeAnnotation') {
|
|
32
|
+
return 2;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// inlineCode + space (weaker signal)
|
|
36
|
+
if (
|
|
37
|
+
firstNode.type === 'inlineCode' &&
|
|
38
|
+
value &&
|
|
39
|
+
VALID_JAVASCRIPT_PROPERTY.test(value)
|
|
40
|
+
) {
|
|
41
|
+
return 1;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return 0;
|
|
45
|
+
};
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
import rehypeStringify from 'rehype-stringify';
|
|
4
|
+
import remarkGfm from 'remark-gfm';
|
|
5
|
+
import remarkMdx from 'remark-mdx';
|
|
6
|
+
import remarkParse from 'remark-parse';
|
|
7
|
+
import remarkRehype from 'remark-rehype';
|
|
8
|
+
import remarkStringify from 'remark-stringify';
|
|
9
|
+
import { unified } from 'unified';
|
|
10
|
+
|
|
11
|
+
import syntaxHighlighter from './highlighter.mjs';
|
|
12
|
+
import { lazy } from './misc.mjs';
|
|
13
|
+
import { typeAnnotationToHast } from './type-annotations/hast.mjs';
|
|
14
|
+
import remarkTypeAnnotations from './type-annotations/remark.mjs';
|
|
15
|
+
|
|
16
|
+
// MDX node types that may appear in trees parsed by `getRemarkMdx`; the
|
|
17
|
+
// rehype pipelines pass them through untouched.
|
|
18
|
+
const passThrough = [
|
|
19
|
+
'element',
|
|
20
|
+
'mdxJsxTextElement',
|
|
21
|
+
'mdxJsxFlowElement',
|
|
22
|
+
'mdxJsxAttribute',
|
|
23
|
+
'mdxJsxAttributeValueExpression',
|
|
24
|
+
'mdxFlowExpression',
|
|
25
|
+
'mdxTextExpression',
|
|
26
|
+
'mdxjsEsm',
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Retrieves an instance of Remark configured to parse GFM (GitHub Flavored Markdown)
|
|
31
|
+
* plus `{...}` type annotations (see `./type-annotations`), which only exist
|
|
32
|
+
* in non-MDX files — the MDX pipeline below never registers them.
|
|
33
|
+
*/
|
|
34
|
+
export const getRemark = lazy(() =>
|
|
35
|
+
unified()
|
|
36
|
+
.use(remarkParse)
|
|
37
|
+
.use(remarkTypeAnnotations)
|
|
38
|
+
.use(remarkGfm)
|
|
39
|
+
.use(remarkStringify)
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Retrieves an instance of Remark configured to parse MDX (JSX-in-Markdown).
|
|
44
|
+
*
|
|
45
|
+
* Unlike {@link getRemark}, this understands `<Component />` and `{expression}`
|
|
46
|
+
* syntax as real JSX/expression nodes. It is only used for `.mdx` (or
|
|
47
|
+
* explicitly opted-in) files, since Node.js core `.md` files use bare `<` and
|
|
48
|
+
* `{` for type annotations that MDX would otherwise try to parse.
|
|
49
|
+
*/
|
|
50
|
+
export const getRemarkMdx = lazy(() =>
|
|
51
|
+
unified().use(remarkParse).use(remarkMdx).use(remarkGfm)
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Retrieves an instance of Remark configured to output stringified HTML code
|
|
56
|
+
*/
|
|
57
|
+
export const getRemarkRehype = lazy(() =>
|
|
58
|
+
unified()
|
|
59
|
+
.use(remarkParse)
|
|
60
|
+
// We make Rehype ignore existing HTML nodes (just the node itself, not its children)
|
|
61
|
+
// as these are nodes we manually created during the rehype process
|
|
62
|
+
// We also allow dangerous HTML to be passed through, since we have HTML within our Markdown
|
|
63
|
+
// and we trust the sources of the Markdown files
|
|
64
|
+
.use(remarkRehype, {
|
|
65
|
+
allowDangerousHtml: true,
|
|
66
|
+
passThrough,
|
|
67
|
+
handlers: { typeAnnotation: typeAnnotationToHast },
|
|
68
|
+
})
|
|
69
|
+
// We allow dangerous HTML to be passed through, since we have HTML within our Markdown
|
|
70
|
+
// and we trust the sources of the Markdown files
|
|
71
|
+
.use(rehypeStringify, { allowDangerousHtml: true })
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Retrieves an instance of Remark configured to output stringified HTML code
|
|
76
|
+
* including parsing Code Boxes with syntax highlighting
|
|
77
|
+
*/
|
|
78
|
+
export const getRemarkRehypeWithShiki = lazy(() =>
|
|
79
|
+
unified()
|
|
80
|
+
.use(remarkParse)
|
|
81
|
+
// We make Rehype ignore existing HTML nodes (just the node itself, not its children)
|
|
82
|
+
// as these are nodes we manually created during the rehype process
|
|
83
|
+
// We also allow dangerous HTML to be passed through, since we have HTML within our Markdown
|
|
84
|
+
// and we trust the sources of the Markdown files
|
|
85
|
+
.use(remarkRehype, {
|
|
86
|
+
allowDangerousHtml: true,
|
|
87
|
+
passThrough,
|
|
88
|
+
// legacy-html gets the minimal (unhighlighted) type rendering
|
|
89
|
+
handlers: { typeAnnotation: typeAnnotationToHast },
|
|
90
|
+
})
|
|
91
|
+
// This is a custom ad-hoc within the Shiki Rehype plugin, used to highlight code
|
|
92
|
+
// and transform them into HAST nodes
|
|
93
|
+
.use(syntaxHighlighter)
|
|
94
|
+
// We allow dangerous HTML to be passed through, since we have HTML within our Markdown
|
|
95
|
+
// and we trust the sources of the Markdown files
|
|
96
|
+
.use(rehypeStringify, { allowDangerousHtml: true })
|
|
97
|
+
);
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// Grabs a method's name
|
|
2
|
+
export const NAME_EXPRESSION = /^['`"]?([^'`": {]+)['`"]?\s*:?\s*/;
|
|
3
|
+
|
|
4
|
+
// Checks if there's a leading hyphen
|
|
5
|
+
export const LEADING_HYPHEN = /^-\s*/;
|
|
6
|
+
|
|
7
|
+
// Grabs the default value if present
|
|
8
|
+
export const DEFAULT_EXPRESSION = /\s*\*\*Default:\*\*\s*([^]+)$/i;
|
|
9
|
+
|
|
10
|
+
// Grabs the parameters from a method's signature
|
|
11
|
+
// ex/ 'new buffer.Blob([sources[, options]])'.match(PARAM_EXPRESSION) === ['([sources[, options]])', '[sources[, options]]']
|
|
12
|
+
export const PARAM_EXPRESSION = /\(([^)]+)\);?$/;
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DEFAULT_EXPRESSION,
|
|
3
|
+
LEADING_HYPHEN,
|
|
4
|
+
NAME_EXPRESSION,
|
|
5
|
+
} from './constants.mjs';
|
|
6
|
+
import parseSignature from './parseSignature.mjs';
|
|
7
|
+
import { leftHandAssign } from '../generators.mjs';
|
|
8
|
+
import { QUERIES, UNIST } from '../queries/index.mjs';
|
|
9
|
+
import { transformNodesToString } from '../unist.mjs';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Extracts and removes a specific pattern from a text string while storing the result in a key of the `current` object.
|
|
13
|
+
* @param {string} text
|
|
14
|
+
* @param {RegExp} pattern
|
|
15
|
+
* @param {string} key
|
|
16
|
+
* @param {Object} current
|
|
17
|
+
* @returns {string}
|
|
18
|
+
*/
|
|
19
|
+
export const extractPattern = (text, pattern, key, current) => {
|
|
20
|
+
const match = text.match(pattern)?.[1]?.trim().replace(/\.$/, '');
|
|
21
|
+
|
|
22
|
+
if (!match) {
|
|
23
|
+
return text;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
current[key] = match;
|
|
27
|
+
return text.replace(pattern, '');
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Parses an individual list item node to extract its properties
|
|
32
|
+
*
|
|
33
|
+
* @param {import('@types/mdast').ListItem} child
|
|
34
|
+
* @returns {import('./types').ParameterList}
|
|
35
|
+
*/
|
|
36
|
+
export function parseListItem(child) {
|
|
37
|
+
const current = {};
|
|
38
|
+
|
|
39
|
+
const subList = child.children.find(UNIST.isLooselyTypedList);
|
|
40
|
+
|
|
41
|
+
// Extract and clean raw text from the node, excluding nested lists.
|
|
42
|
+
// Type annotations serialize back to their `{Type}` source form.
|
|
43
|
+
current.textRaw = transformNodesToString(
|
|
44
|
+
child.children.filter(node => node !== subList)
|
|
45
|
+
)
|
|
46
|
+
.replace(/\s+/g, ' ')
|
|
47
|
+
.replace(/<!--.*?-->/gs, '');
|
|
48
|
+
|
|
49
|
+
let text = current.textRaw;
|
|
50
|
+
|
|
51
|
+
// Identify return items or extract key properties (name, type, default) from the text
|
|
52
|
+
const starter = text.match(QUERIES.typedListStarters);
|
|
53
|
+
if (starter) {
|
|
54
|
+
current.name =
|
|
55
|
+
starter[1] === 'Returns' ? 'return' : starter[1].toLowerCase();
|
|
56
|
+
text = text.slice(starter[0].length);
|
|
57
|
+
} else {
|
|
58
|
+
text = extractPattern(text, NAME_EXPRESSION, 'name', current);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// The type is the item's typeAnnotation node; it's only a type (and not
|
|
62
|
+
// prose) when its serialized form sits at the head of the remaining text
|
|
63
|
+
const annotation = child.children
|
|
64
|
+
.find(node => node.type === 'paragraph')
|
|
65
|
+
?.children.find(node => node.type === 'typeAnnotation');
|
|
66
|
+
|
|
67
|
+
if (annotation && text.startsWith(`{${annotation.value}}`)) {
|
|
68
|
+
current.type = annotation.value;
|
|
69
|
+
text = text.slice(annotation.value.length + 2).replace(/^\s+/, '');
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
text = extractPattern(text, DEFAULT_EXPRESSION, 'default', current);
|
|
73
|
+
|
|
74
|
+
// Set the remaining text as the description, removing any leading hyphen
|
|
75
|
+
current.desc = text.replace(LEADING_HYPHEN, '').trim() || undefined;
|
|
76
|
+
|
|
77
|
+
// Parse nested lists (options) recursively if present
|
|
78
|
+
if (subList) {
|
|
79
|
+
current.options = subList.children.map(parseListItem);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return current;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Parses a list of nodes and updates the corresponding section object with the extracted information.
|
|
87
|
+
* Handles different section types such as methods, properties, and events differently.
|
|
88
|
+
* @param {{ [key: string]: unknown }} section - The section object to populate with the parsed values
|
|
89
|
+
* @param {import('@types/mdast').RootContent[]} nodes
|
|
90
|
+
*/
|
|
91
|
+
export function parseList(section, nodes) {
|
|
92
|
+
const listIdx = nodes.findIndex(UNIST.isStronglyTypedList);
|
|
93
|
+
const list = nodes[listIdx];
|
|
94
|
+
|
|
95
|
+
const values = list ? list.children.map(parseListItem) : [];
|
|
96
|
+
|
|
97
|
+
let removeList = true;
|
|
98
|
+
|
|
99
|
+
// Update the section based on its type and parsed values
|
|
100
|
+
switch (section.type) {
|
|
101
|
+
case 'ctor':
|
|
102
|
+
// Constructors are their own signatures
|
|
103
|
+
leftHandAssign(section, parseSignature(section.textRaw, values));
|
|
104
|
+
break;
|
|
105
|
+
|
|
106
|
+
case 'classMethod':
|
|
107
|
+
case 'method':
|
|
108
|
+
// For methods and constructors, parse and attach signatures
|
|
109
|
+
section.signatures = [parseSignature(section.textRaw, values)];
|
|
110
|
+
break;
|
|
111
|
+
|
|
112
|
+
case 'property':
|
|
113
|
+
// For properties, update type and other details if values exist
|
|
114
|
+
if (values.length) {
|
|
115
|
+
delete values[0].name;
|
|
116
|
+
|
|
117
|
+
Object.assign(section, values[0]);
|
|
118
|
+
}
|
|
119
|
+
break;
|
|
120
|
+
|
|
121
|
+
case 'event':
|
|
122
|
+
// For events, assign parsed values as parameters
|
|
123
|
+
section.params = values;
|
|
124
|
+
break;
|
|
125
|
+
|
|
126
|
+
default:
|
|
127
|
+
removeList = false;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (removeList && list) {
|
|
131
|
+
nodes.splice(listIdx, 1);
|
|
132
|
+
}
|
|
133
|
+
}
|