@doc-kit/core 1.1.0 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/README.md +2 -1
  3. package/package.json +9 -4
  4. package/src/generators/index.mjs +2 -0
  5. package/src/generators/json/README.md +151 -0
  6. package/src/generators/json/constants.mjs +57 -0
  7. package/src/generators/json/generate.mjs +65 -0
  8. package/src/generators/json/generated/schema.d.ts +351 -0
  9. package/src/generators/json/index.mjs +27 -0
  10. package/src/generators/json/schema.json +529 -0
  11. package/src/generators/json/types.d.ts +29 -0
  12. package/src/generators/json/utils/document.mjs +58 -0
  13. package/src/generators/json/utils/entry.mjs +87 -0
  14. package/src/generators/json/utils/lifecycle.mjs +61 -0
  15. package/src/generators/json/utils/markdown.mjs +57 -0
  16. package/src/generators/json/utils/node.mjs +153 -0
  17. package/src/generators/json/utils/schema.mjs +15 -0
  18. package/src/generators/json/utils/signature.mjs +185 -0
  19. package/src/generators/json/utils/types.mjs +28 -0
  20. package/src/generators/json-all/README.md +31 -0
  21. package/src/generators/json-all/constants.mjs +5 -0
  22. package/src/generators/json-all/generate.mjs +35 -0
  23. package/src/generators/json-all/index.mjs +25 -0
  24. package/src/generators/json-all/schema.json +22 -0
  25. package/src/generators/json-all/types.d.ts +20 -0
  26. package/src/generators/json-simple/generate.mjs +2 -7
  27. package/src/generators/metadata/maps/mdn.json +10 -2
  28. package/src/generators/metadata/types.d.ts +5 -0
  29. package/src/generators/metadata/utils/transformers.mjs +1 -1
  30. package/src/logger/logger.mjs +8 -0
  31. package/src/threading/chunk-worker.mjs +7 -0
  32. package/src/threading/index.mjs +1 -0
  33. package/src/utils/file.mjs +10 -0
  34. package/src/utils/hierarchy.mjs +50 -0
  35. package/src/utils/overloads.mjs +54 -0
  36. package/src/utils/queries/index.mjs +2 -0
  37. package/src/utils/queries/utils.mjs +15 -0
  38. package/src/utils/remark.mjs +1 -1
  39. package/src/utils/signature/constants.mjs +3 -0
  40. package/src/utils/signature/extractListItem.mjs +140 -0
  41. package/src/utils/stability.mjs +24 -0
@@ -0,0 +1,22 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "$id": "https://doc-kit.nodejs.org/schemas/api-doc-all/1.0.1.json",
4
+ "title": "Bundle",
5
+ "description": "Every document of a documentation set, as emitted by the doc-kit `json-all` generator",
6
+ "type": "object",
7
+ "properties": {
8
+ "$schema": {
9
+ "type": "string",
10
+ "description": "The URL of the schema this bundle conforms to. Its last path segment is the schema version."
11
+ },
12
+ "documents": {
13
+ "type": "array",
14
+ "items": {
15
+ "$ref": "https://doc-kit.nodejs.org/schemas/api-doc/1.0.1.json"
16
+ },
17
+ "description": "The documents, in the order of the configured index, then by id."
18
+ }
19
+ },
20
+ "required": ["$schema", "documents"],
21
+ "additionalProperties": false
22
+ }
@@ -0,0 +1,20 @@
1
+ import type { Document } from '../json/generated/schema';
2
+
3
+ /**
4
+ * Every document of a documentation set
5
+ */
6
+ export interface Bundle {
7
+ /** The URL of the schema the bundle conforms to */
8
+ $schema: string;
9
+ documents: Array<Document>;
10
+ }
11
+
12
+ export interface Configuration {
13
+ /** Where the schema is published; `{schemaVersion}` is filled in */
14
+ schemaURL: string;
15
+ }
16
+
17
+ export type Generator = GeneratorMetadata<
18
+ Configuration,
19
+ Generate<Array<Document>, Promise<Bundle>>
20
+ >;
@@ -5,7 +5,7 @@ import { join } from 'node:path';
5
5
  import { remove } from 'unist-util-remove';
6
6
 
7
7
  import getConfig from '#utils/configuration/index.mjs';
8
- import { writeFile } from '#utils/file.mjs';
8
+ import { writeJSON } from '#utils/file.mjs';
9
9
  import { UNIST } from '#utils/queries/index.mjs';
10
10
 
11
11
  /**
@@ -21,12 +21,7 @@ export async function generate(input) {
21
21
  );
22
22
 
23
23
  if (config.output) {
24
- // Writes all the API docs stringified content into one file
25
- // Note: The full JSON generator in the future will create one JSON file per top-level API doc file
26
- await writeFile(
27
- join(config.output, 'api-docs.json'),
28
- config.minify ? JSON.stringify(input) : JSON.stringify(input, null, 2)
29
- );
24
+ await writeJSON(join(config.output, 'api-docs.json'), input, config.minify);
30
25
  }
31
26
 
32
27
  return input;
@@ -421,6 +421,7 @@
421
421
  "idbkeyrange": "https://developer.mozilla.org/docs/Web/API/IDBKeyRange",
422
422
  "idbobjectstore": "https://developer.mozilla.org/docs/Web/API/IDBObjectStore",
423
423
  "idbopendbrequest": "https://developer.mozilla.org/docs/Web/API/IDBOpenDBRequest",
424
+ "idbrecord": "https://developer.mozilla.org/docs/Web/API/IDBRecord",
424
425
  "idbrequest": "https://developer.mozilla.org/docs/Web/API/IDBRequest",
425
426
  "idbtransaction": "https://developer.mozilla.org/docs/Web/API/IDBTransaction",
426
427
  "idbversionchangeevent": "https://developer.mozilla.org/docs/Web/API/IDBVersionChangeEvent",
@@ -442,6 +443,7 @@
442
443
  "inputdeviceinfo": "https://developer.mozilla.org/docs/Web/API/InputDeviceInfo",
443
444
  "inputevent": "https://developer.mozilla.org/docs/Web/API/InputEvent",
444
445
  "installevent": "https://developer.mozilla.org/docs/Web/API/InstallEvent",
446
+ "interactioncontentfulpaint": "https://developer.mozilla.org/docs/Web/API/InteractionContentfulPaint",
445
447
  "interestevent": "https://developer.mozilla.org/docs/Web/API/InterestEvent",
446
448
  "intersectionobserver": "https://developer.mozilla.org/docs/Web/API/IntersectionObserver",
447
449
  "intersectionobserverentry": "https://developer.mozilla.org/docs/Web/API/IntersectionObserverEntry",
@@ -452,6 +454,7 @@
452
454
  "keyboardlayoutmap": "https://developer.mozilla.org/docs/Web/API/KeyboardLayoutMap",
453
455
  "keyframeeffect": "https://developer.mozilla.org/docs/Web/API/KeyframeEffect",
454
456
  "languagedetector": "https://developer.mozilla.org/docs/Web/API/LanguageDetector",
457
+ "languagemodel": "https://developer.mozilla.org/docs/Web/API/LanguageModel",
455
458
  "largestcontentfulpaint": "https://developer.mozilla.org/docs/Web/API/LargestContentfulPaint",
456
459
  "launchparams": "https://developer.mozilla.org/docs/Web/API/LaunchParams",
457
460
  "launchqueue": "https://developer.mozilla.org/docs/Web/API/LaunchQueue",
@@ -472,6 +475,7 @@
472
475
  "magnetometer": "https://developer.mozilla.org/docs/Web/API/Magnetometer",
473
476
  "managedmediasource": "https://developer.mozilla.org/docs/Web/API/ManagedMediaSource",
474
477
  "managedsourcebuffer": "https://developer.mozilla.org/docs/Web/API/ManagedSourceBuffer",
478
+ "mathmlanchorelement": "https://developer.mozilla.org/docs/Web/API/MathMLAnchorElement",
475
479
  "mathmlelement": "https://developer.mozilla.org/docs/Web/API/MathMLElement",
476
480
  "mediacapabilities": "https://developer.mozilla.org/docs/Web/API/MediaCapabilities",
477
481
  "mediadeviceinfo": "https://developer.mozilla.org/docs/Web/API/MediaDeviceInfo",
@@ -589,7 +593,9 @@
589
593
  "performanceresourcetiming": "https://developer.mozilla.org/docs/Web/API/PerformanceResourceTiming",
590
594
  "performancescripttiming": "https://developer.mozilla.org/docs/Web/API/PerformanceScriptTiming",
591
595
  "performanceservertiming": "https://developer.mozilla.org/docs/Web/API/PerformanceServerTiming",
596
+ "performancesoftnavigation": "https://developer.mozilla.org/docs/Web/API/PerformanceSoftNavigation",
592
597
  "performancetiming": "https://developer.mozilla.org/docs/Web/API/PerformanceTiming",
598
+ "performancetimingconfidence": "https://developer.mozilla.org/docs/Web/API/PerformanceTimingConfidence",
593
599
  "periodicsyncevent": "https://developer.mozilla.org/docs/Web/API/PeriodicSyncEvent",
594
600
  "periodicsyncmanager": "https://developer.mozilla.org/docs/Web/API/PeriodicSyncManager",
595
601
  "periodicwave": "https://developer.mozilla.org/docs/Web/API/PeriodicWave",
@@ -636,6 +642,7 @@
636
642
  "rtcerror": "https://developer.mozilla.org/docs/Web/API/RTCError",
637
643
  "rtcerrorevent": "https://developer.mozilla.org/docs/Web/API/RTCErrorEvent",
638
644
  "rtcicecandidate": "https://developer.mozilla.org/docs/Web/API/RTCIceCandidate",
645
+ "rtcicecandidatepair": "https://developer.mozilla.org/docs/Web/API/RTCIceCandidatePair",
639
646
  "rtcicetransport": "https://developer.mozilla.org/docs/Web/API/RTCIceTransport",
640
647
  "rtcidentityassertion": "https://developer.mozilla.org/docs/Web/API/RTCIdentityAssertion",
641
648
  "rtcpeerconnection": "https://developer.mozilla.org/docs/Web/API/RTCPeerConnection",
@@ -672,7 +679,6 @@
672
679
  "restrictiontarget": "https://developer.mozilla.org/docs/Web/API/RestrictionTarget",
673
680
  "svgaelement": "https://developer.mozilla.org/docs/Web/API/SVGAElement",
674
681
  "svgangle": "https://developer.mozilla.org/docs/Web/API/SVGAngle",
675
- "svganimatecolorelement": "https://developer.mozilla.org/docs/Web/API/SVGAnimateColorElement",
676
682
  "svganimateelement": "https://developer.mozilla.org/docs/Web/API/SVGAnimateElement",
677
683
  "svganimatemotionelement": "https://developer.mozilla.org/docs/Web/API/SVGAnimateMotionElement",
678
684
  "svganimatetransformelement": "https://developer.mozilla.org/docs/Web/API/SVGAnimateTransformElement",
@@ -749,7 +755,6 @@
749
755
  "svgradialgradientelement": "https://developer.mozilla.org/docs/Web/API/SVGRadialGradientElement",
750
756
  "svgrect": "https://developer.mozilla.org/docs/Web/API/SVGRect",
751
757
  "svgrectelement": "https://developer.mozilla.org/docs/Web/API/SVGRectElement",
752
- "svgrenderingintent": "https://developer.mozilla.org/docs/Web/API/SVGRenderingIntent",
753
758
  "svgsvgelement": "https://developer.mozilla.org/docs/Web/API/SVGSVGElement",
754
759
  "svgscriptelement": "https://developer.mozilla.org/docs/Web/API/SVGScriptElement",
755
760
  "svgsetelement": "https://developer.mozilla.org/docs/Web/API/SVGSetElement",
@@ -947,8 +952,10 @@
947
952
  "webtransport": "https://developer.mozilla.org/docs/Web/API/WebTransport",
948
953
  "webtransportbidirectionalstream": "https://developer.mozilla.org/docs/Web/API/WebTransportBidirectionalStream",
949
954
  "webtransportdatagramduplexstream": "https://developer.mozilla.org/docs/Web/API/WebTransportDatagramDuplexStream",
955
+ "webtransportdatagramswritable": "https://developer.mozilla.org/docs/Web/API/WebTransportDatagramsWritable",
950
956
  "webtransporterror": "https://developer.mozilla.org/docs/Web/API/WebTransportError",
951
957
  "webtransportreceivestream": "https://developer.mozilla.org/docs/Web/API/WebTransportReceiveStream",
958
+ "webtransportsendgroup": "https://developer.mozilla.org/docs/Web/API/WebTransportSendGroup",
952
959
  "webtransportsendstream": "https://developer.mozilla.org/docs/Web/API/WebTransportSendStream",
953
960
  "wheelevent": "https://developer.mozilla.org/docs/Web/API/WheelEvent",
954
961
  "window": "https://developer.mozilla.org/docs/Web/API/Window",
@@ -1014,6 +1021,7 @@
1014
1021
  "xrview": "https://developer.mozilla.org/docs/Web/API/XRView",
1015
1022
  "xrviewerpose": "https://developer.mozilla.org/docs/Web/API/XRViewerPose",
1016
1023
  "xrviewport": "https://developer.mozilla.org/docs/Web/API/XRViewport",
1024
+ "xrvisibilitymaskchangeevent": "https://developer.mozilla.org/docs/Web/API/XRVisibilityMaskChangeEvent",
1017
1025
  "xrwebglbinding": "https://developer.mozilla.org/docs/Web/API/XRWebGLBinding",
1018
1026
  "xrwebgldepthinformation": "https://developer.mozilla.org/docs/Web/API/XRWebGLDepthInformation",
1019
1027
  "xrwebgllayer": "https://developer.mozilla.org/docs/Web/API/XRWebGLLayer",
@@ -97,6 +97,11 @@ export interface HeadingData extends Data {
97
97
  * from the ToC while still rendering on the page.
98
98
  */
99
99
  isOverload?: boolean;
100
+ /**
101
+ * The slug of the first heading of the overloaded function this heading is
102
+ * an overload of. Set alongside `isOverload`.
103
+ */
104
+ overloadOf?: string;
100
105
  }
101
106
 
102
107
  /**
@@ -72,7 +72,7 @@ export const resolveTypeReference = (name, typeMap) => {
72
72
  // Transform Node.js types like 'vm.Something'.
73
73
  if (MODULE_QUALIFIED_NAME.test(name)) {
74
74
  const [mod, ...pieces] = name.split('.');
75
- const isClass = pieces.at(-1).match(/^[A-Z][a-z]/);
75
+ const isClass = pieces.at(-1).match(/^[A-Z](?=.*[a-z])/);
76
76
 
77
77
  return `${mod}.html#${isClass ? 'class-' : ''}${slug(name)}`;
78
78
  }
@@ -165,6 +165,13 @@ export const createLogger = (
165
165
  }
166
166
  };
167
167
 
168
+ /**
169
+ * Gets the current log level for this logger instance.
170
+ *
171
+ * @returns {number} The current numeric log level
172
+ */
173
+ const getLogLevel = () => currentLevel;
174
+
168
175
  return {
169
176
  info,
170
177
  warn,
@@ -173,5 +180,6 @@ export const createLogger = (
173
180
  debug,
174
181
  child,
175
182
  setLogLevel,
183
+ getLogLevel,
176
184
  };
177
185
  };
@@ -1,6 +1,13 @@
1
+ import { workerData } from 'node:worker_threads';
2
+
1
3
  import { loadGenerator } from '#generators/loader.mjs';
4
+ import logger from '#logger/index.mjs';
2
5
  import { setConfig } from '#utils/configuration/index.mjs';
3
6
 
7
+ if (workerData?.logLevel !== undefined) {
8
+ logger.setLogLevel(workerData.logLevel);
9
+ }
10
+
4
11
  /**
5
12
  * Processes a chunk of items using the specified generator's processChunk method.
6
13
  * This is the worker entry point for Piscina.
@@ -23,5 +23,6 @@ export default function createWorkerPool(threads) {
23
23
  minThreads: 0,
24
24
  maxThreads: threads,
25
25
  idleTimeout: 1_000,
26
+ workerData: { logLevel: logger.getLogLevel() },
26
27
  });
27
28
  }
@@ -18,3 +18,13 @@ export const writeFile = (file, ...args) =>
18
18
  fs
19
19
  .mkdir(dirname(file), { recursive: true })
20
20
  .then(() => fs.writeFile(file, ...args));
21
+
22
+ /**
23
+ * Writes a value as JSON
24
+ *
25
+ * @param {string} file
26
+ * @param {unknown} value
27
+ * @param {boolean} [minify]
28
+ */
29
+ export const writeJSON = (file, value, minify = false) =>
30
+ writeFile(file, JSON.stringify(value, null, minify ? 0 : 2));
@@ -0,0 +1,50 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * A node in the entry hierarchy.
5
+ *
6
+ * @typedef {object} HierarchizedEntry
7
+ * @property {import('../generators/metadata/types').MetadataEntry} entry The metadata entry this node wraps
8
+ * @property {Array<HierarchizedEntry>} children Entries nested under this one, by heading depth
9
+ */
10
+
11
+ /**
12
+ * Finds the closest preceding node whose heading is shallower than the
13
+ * entry's, which is the entry's parent.
14
+ *
15
+ * @param {import('../generators/metadata/types').MetadataEntry} entry The entry to find a parent for
16
+ * @param {Array<HierarchizedEntry>} nodes Wrapper nodes, index-aligned with the entries
17
+ * @param {number} startIdx The index to search backwards from
18
+ * @returns {HierarchizedEntry | undefined} The parent, or `undefined` when no shallower entry precedes it
19
+ */
20
+ export const findParent = (entry, nodes, startIdx) => {
21
+ for (let i = startIdx; i >= 0; i--) {
22
+ if (nodes[i].entry.heading.depth < entry.heading.depth) {
23
+ return nodes[i];
24
+ }
25
+ }
26
+
27
+ return undefined;
28
+ };
29
+
30
+ /**
31
+ * @param {Array<import('../generators/metadata/types').MetadataEntry>} entries Entries in document order
32
+ * @returns {Array<HierarchizedEntry>} The root nodes
33
+ */
34
+ export const buildHierarchy = entries => {
35
+ const roots = [];
36
+
37
+ // Wrapper nodes, index-aligned with `entries`
38
+ const nodes = entries.map(entry => ({ entry, children: [] }));
39
+
40
+ nodes.forEach((node, i) => {
41
+ const parent =
42
+ node.entry.heading.depth <= 1
43
+ ? undefined
44
+ : findParent(node.entry, nodes, i - 1);
45
+
46
+ (parent?.children ?? roots).push(node);
47
+ });
48
+
49
+ return roots;
50
+ };
@@ -0,0 +1,54 @@
1
+ 'use strict';
2
+
3
+ // Heading types that document a callable and therefore may appear several times
4
+ // in a row as overloaded signatures of the same function.
5
+ const OVERLOADABLE_TYPES = new Set(['method', 'ctor', 'classMethod']);
6
+
7
+ /**
8
+ * Two headings document the same function (i.e. are overloads of one another)
9
+ * when they sit at the same depth and share the same resolved name and type.
10
+ *
11
+ * @param {import('../generators/metadata/types').HeadingNode} a
12
+ * @param {import('../generators/metadata/types').HeadingNode} b
13
+ */
14
+ const isSameFunction = (a, b) =>
15
+ a.depth === b.depth &&
16
+ a.data.type === b.data.type &&
17
+ a.data.name === b.data.name;
18
+
19
+ /**
20
+ * Flags overloaded function headings so consumers can present a single entry
21
+ * per function.
22
+ *
23
+ * Node.js documents each overload of a function as its own heading (e.g. the
24
+ * five `new Buffer(...)` signatures). This marks the 2nd..nth heading of each
25
+ * such run with `isOverload`, and with `overloadOf` pointing at the slug of
26
+ * the run's first heading. The first (most stable) heading is left as-is.
27
+ *
28
+ * @param {Array<import('../generators/metadata/types').MetadataEntry>} entries - Entries, in document order.
29
+ * @returns {Array<import('../generators/metadata/types').MetadataEntry>} The same entries (mutated).
30
+ */
31
+ export const annotateOverloads = entries => {
32
+ for (let i = 0; i < entries.length; i++) {
33
+ const { heading } = entries[i];
34
+
35
+ if (!OVERLOADABLE_TYPES.has(heading.data.type)) {
36
+ continue;
37
+ }
38
+
39
+ // Flag each following heading that overloads the same function.
40
+ let end = i + 1;
41
+ while (
42
+ end < entries.length &&
43
+ isSameFunction(entries[end].heading, heading)
44
+ ) {
45
+ entries[end].heading.data.isOverload = true;
46
+ entries[end].heading.data.overloadOf = heading.data.slug;
47
+ end++;
48
+ }
49
+
50
+ i = end - 1;
51
+ }
52
+
53
+ return entries;
54
+ };
@@ -4,6 +4,8 @@ import { transformNodesToString } from '#utils/unist.mjs';
4
4
 
5
5
  import { isTypedListItem, isTypedList } from './utils.mjs';
6
6
 
7
+ export { splitTypedItems } from './utils.mjs';
8
+
7
9
  // This defines the actual REGEX Queries
8
10
  export const QUERIES = {
9
11
  // Fixes the references to Markdown pages into the API documentation
@@ -67,3 +67,18 @@ export const isTypedList = list => {
67
67
 
68
68
  return getTypedConfidence(list.children?.[0]?.children?.[0]?.children?.[0]);
69
69
  };
70
+
71
+ /**
72
+ * Splits a typed list's items into the leading typed ones and the rest:
73
+ * prose bullets that happen to share the list in the source markdown.
74
+ *
75
+ * @param {import('@types/mdast').List} list
76
+ * @returns {{ typed: Array<import('@types/mdast').ListItem>, rest: Array<import('@types/mdast').ListItem> }}
77
+ */
78
+ export const splitTypedItems = ({ children }) => {
79
+ const end = children.findIndex(item => !isTypedListItem(item));
80
+
81
+ return end === -1
82
+ ? { typed: children, rest: [] }
83
+ : { typed: children.slice(0, end), rest: children.slice(end) };
84
+ };
@@ -76,7 +76,7 @@ export const getRemark = lazy(() =>
76
76
  * `{` for type annotations that MDX would otherwise try to parse.
77
77
  */
78
78
  export const getRemarkMdx = lazy(() =>
79
- unified().use(remarkParse).use(remarkMdx).use(remarkGfm)
79
+ unified().use(remarkParse).use(remarkMdx).use(remarkGfm).use(remarkStringify)
80
80
  );
81
81
 
82
82
  /**
@@ -4,6 +4,9 @@ export const NAME_EXPRESSION = /^['`"]?([^'`": {]+)['`"]?\s*:?\s*/;
4
4
  // Checks if there's a leading hyphen
5
5
  export const LEADING_HYPHEN = /^-\s*/;
6
6
 
7
+ // The separators between a parameter's type and its description
8
+ export const TRIMMABLE_PADDING_REGEX = /^[\s:-]+/;
9
+
7
10
  // Grabs the default value if present
8
11
  export const DEFAULT_EXPRESSION = /\s*\*\*Default:\*\*\s*([^]+)$/i;
9
12
 
@@ -0,0 +1,140 @@
1
+ 'use strict';
2
+
3
+ import { QUERIES, UNIST } from '#utils/queries/index.mjs';
4
+ import { transformNodesToString } from '#utils/unist.mjs';
5
+
6
+ import { DEFAULT_EXPRESSION, TRIMMABLE_PADDING_REGEX } from './constants.mjs';
7
+
8
+ // The text of the `**Default:**` marker introducing a default value
9
+ const DEFAULT_MARKER = /^default:$/i;
10
+
11
+ /**
12
+ * A typed list item taken apart into its parts, none of them stringified, so
13
+ * each consumer can render the description its own way.
14
+ *
15
+ * @typedef {object} ExtractedListItem
16
+ * @property {string | undefined} name The parameter name (the leading code span), if any
17
+ * @property {'Returns' | 'Extends' | 'Type' | undefined} prefix The special prefix the item starts with, if any
18
+ * @property {import('mdast').Node | undefined} annotation The leading `typeAnnotation` node, if any
19
+ * @property {Array<import('mdast').PhrasingContent>} text The rest of the first paragraph: the description,
20
+ * with leading separators trimmed and any `**Default:**` marker still in place
21
+ * @property {Array<import('mdast').BlockContent>} blocks The item's further block content, excluding its nested typed list
22
+ * @property {string | undefined} default The `**Default:**` value as written (code span included), if any
23
+ * @property {Array<import('mdast').ListItem>} items The items of the nested typed list, if any
24
+ */
25
+
26
+ /**
27
+ * Replaces the first node's text, or drops the node when nothing is left.
28
+ *
29
+ * @param {Array<import('mdast').PhrasingContent>} nodes
30
+ * @param {string} value The new text of the first node
31
+ */
32
+ const replaceLeadingText = (nodes, value) => {
33
+ if (value.trim()) {
34
+ nodes[0] = { ...nodes[0], value };
35
+ } else {
36
+ nodes.shift();
37
+ }
38
+ };
39
+
40
+ /**
41
+ * Takes a typed list item apart: `` `name` {Type} Description. **Default:** `value` ``,
42
+ * or one of the `Returns:`, `Extends:` and `Type:` forms.
43
+ *
44
+ * @param {import('mdast').ListItem} item
45
+ * @returns {ExtractedListItem}
46
+ */
47
+ export const extractListItem = item => {
48
+ const [paragraph, ...rest] = item.children ?? [];
49
+ const text = [...(paragraph?.children ?? [])];
50
+
51
+ const result = {
52
+ name: undefined,
53
+ prefix: undefined,
54
+ annotation: undefined,
55
+ text,
56
+ blocks: [],
57
+ default: undefined,
58
+ items: [],
59
+ };
60
+
61
+ const [first] = text;
62
+
63
+ if (first?.type === 'inlineCode') {
64
+ result.name = first.value.trimEnd();
65
+ text.shift();
66
+ } else if (first?.type === 'text') {
67
+ const match = first.value.match(QUERIES.typedListStarters);
68
+
69
+ if (match) {
70
+ result.prefix = match[1];
71
+ replaceLeadingText(text, first.value.slice(match[0].length));
72
+ }
73
+ }
74
+
75
+ // The whitespace between the name and the type
76
+ if (text[0]?.type === 'text' && !text[0].value.trim()) {
77
+ text.shift();
78
+ }
79
+
80
+ if (text[0]?.type === 'typeAnnotation') {
81
+ result.annotation = text.shift();
82
+ }
83
+
84
+ // Leading separators: `- description`, `: description`
85
+ if (text[0]?.type === 'text') {
86
+ replaceLeadingText(
87
+ text,
88
+ text[0].value.replace(TRIMMABLE_PADDING_REGEX, '')
89
+ );
90
+ }
91
+
92
+ result.default = DEFAULT_EXPRESSION.exec(transformNodesToString(text))?.[1]
93
+ .trim()
94
+ .replace(/\.$/, '');
95
+
96
+ for (const node of rest) {
97
+ if (result.items.length === 0 && UNIST.isLooselyTypedList(node)) {
98
+ result.items = node.children;
99
+ } else {
100
+ result.blocks.push(node);
101
+ }
102
+ }
103
+
104
+ return result;
105
+ };
106
+
107
+ /**
108
+ * Drops the `**Default:**` marker, and everything after it, from an item's
109
+ * description.
110
+ *
111
+ * @param {Array<import('mdast').PhrasingContent>} nodes The item's description
112
+ * @returns {Array<import('mdast').PhrasingContent>} The description without its default value
113
+ */
114
+ export const removeDefault = nodes => {
115
+ const index = nodes.findIndex(
116
+ node =>
117
+ node.type === 'strong' &&
118
+ DEFAULT_MARKER.test(transformNodesToString(node.children).trim())
119
+ );
120
+
121
+ if (index === -1) {
122
+ return nodes;
123
+ }
124
+
125
+ const kept = nodes.slice(0, index);
126
+ const last = kept.at(-1);
127
+
128
+ // The whitespace that separated the description from its default
129
+ if (last?.type === 'text') {
130
+ const value = last.value.trimEnd();
131
+
132
+ kept.pop();
133
+
134
+ if (value) {
135
+ kept.push({ ...last, value });
136
+ }
137
+ }
138
+
139
+ return kept;
140
+ };
@@ -0,0 +1,24 @@
1
+ 'use strict';
2
+
3
+ import { slice } from 'mdast-util-slice-markdown';
4
+ import { toString } from 'mdast-util-to-string';
5
+
6
+ import { QUERIES } from './queries/index.mjs';
7
+
8
+ /**
9
+ * The content of a stability blockquote without its `Stability: N - `
10
+ * prefix, as a copy of the blockquote. The prefix may have been turned into a
11
+ * link by the AST stage; the separator goes with it.
12
+ *
13
+ * @param {import('../generators/metadata/types').StabilityNode} node
14
+ * @returns {import('mdast').Blockquote | null} The copy, or `null` when nothing follows the prefix
15
+ */
16
+ export const removeStabilityPrefix = node => {
17
+ const text = toString(node.children[0]);
18
+ const match = QUERIES.stabilityIndex.exec(text);
19
+ const start = match ? text.length - match[2].length : 0;
20
+
21
+ return slice(node, start, undefined, {
22
+ textHandling: { boundaries: 'preserve' },
23
+ }).node;
24
+ };