Skip to content

Commit e9d7d43

Browse files
committed
fixup! fixup! feat(json): add legacy JSON generator
1 parent e2c8fdd commit e9d7d43

File tree

6 files changed

+87
-104
lines changed

6 files changed

+87
-104
lines changed

src/constants.mjs

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -61,28 +61,30 @@ export const DOC_API_HEADING_TYPES = [
6161
{
6262
type: 'method',
6363
regex:
64-
/^`?(?:(?:(?:(?:\\?_)+|\b)\w+\b|\\?\[[\w.]+\\?\])\.?)*((?:(?:(?:\\?_)+|\b)\w+\b|\\?\[[\w.]+\\?\]))\([^)]*\)`?$/i,
64+
// Group 1: foo[bar]()
65+
// Group 2: foo.bar()
66+
// Group 3: foobar()
67+
/^`?(?:\w*(?:(\[[^]]+\])|(?:\.(\w+)))|(\w+))\([^)]*\)`?$/i,
6568
},
6669
{ type: 'event', regex: /^Event: +`?['"]?([^'"]+)['"]?`?$/i },
6770
{
6871
type: 'class',
6972
regex:
70-
/^class: +`?((?:(?:(?:(?:\\?_)+|\b)\w+\b|\\?\[[\w.]+\\?\])\.?)*[A-Z]\w+)(?: +extends +(?:(?:(?:(?:\\?_)+|\b)\w+\b|\\?\[[\w.]+\\?\])\.?)*[A-Z]\w+)?`?$/i,
73+
/^Class: +`?([A-Z]\w+(?:\.[A-Z]\w+)*(?: +extends +[A-Z]\w+(?:\.[A-Z]\w+)*)?)`?$/i,
7174
},
7275
{
7376
type: 'ctor',
74-
regex:
75-
/^(?:Constructor: +)?`?new +((?:(?:(?:(?:\\?_)+|\b)\w+\b|\\?\[[\w.]+\\?\])\.?)*[A-Z]\w+)\([^)]*\)`?$/i,
77+
regex: /^(?:Constructor: +)?`?new +([A-Z]\w+(?:\.[A-Z]\w+)*)\([^)]*\)`?$/i,
7678
},
7779
{
7880
type: 'classMethod',
7981
regex:
80-
/^Static method: +`?(?:(?:(?:(?:\\?_)+|\b)\w+\b|\\?\[[\w.]+\\?\])\.?)*((?:(?:(?:\\?_)+|\b)\w+\b|\\?\[[\w.]+\\?\]))\([^)]*\)`?$/i,
82+
/^Static method: +`?[A-Z]\w+(?:\.[A-Z]\w+)*(?:(\[\w+\.\w+\])|\.(\w+))\([^)]*\)`?$/i,
8183
},
8284
{
8385
type: 'property',
8486
regex:
85-
/^(?:Class property: +)?`?(?:(?:(?:(?:\\?_)+|\b)\w+\b|\\?\[[\w.]+\\?\])\.?)+((?:(?:(?:\\?_)+|\b)\w+\b|\\?\[[\w.]+\\?\]))`?$/i,
87+
/^(?:Class property: +)?`?[A-Z]\w+(?:\.[A-Z]\w+)*(?:(\[\w+\.\w+\])|\.(\w+))`?$/i,
8688
},
8789
];
8890

src/generators/legacy-json-all/index.mjs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -47,11 +47,7 @@ export default {
4747
});
4848
});
4949

50-
await writeFile(
51-
join(output, 'all.json'),
52-
JSON.stringify(generatedValue),
53-
'utf8'
54-
);
50+
await writeFile(join(output, 'all.json'), JSON.stringify(generatedValue));
5551

5652
return generatedValue;
5753
},

src/generators/legacy-json/index.mjs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,7 @@ export default {
5757
// Write it to the output file
5858
await writeFile(
5959
join(output, `${node.api}.json`),
60-
JSON.stringify(section),
61-
'utf8'
60+
JSON.stringify(section)
6261
);
6362
})
6463
);

src/generators/legacy-json/utils/buildHierarchy.mjs

Lines changed: 30 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -20,42 +20,50 @@
2020
export function buildHierarchy(entries) {
2121
const roots = [];
2222

23+
// Recursive helper to find the parent with a depth less than the current depth.
24+
function findParent(entry, startIdx) {
25+
// Base case: if we're at the beginning of the list, no valid parent exists.
26+
if (startIdx < 0) {
27+
throw new Error(
28+
`Cannot find a suitable parent for entry at index ${startIdx + 1}`
29+
);
30+
}
31+
32+
const candidateParent = entries[startIdx];
33+
const candidateDepth = candidateParent.heading.depth;
34+
35+
// If we find a suitable parent, return it.
36+
if (candidateDepth < entry.heading.depth) {
37+
candidateParent.hierarchyChildren ??= [];
38+
return candidateParent;
39+
}
40+
41+
// Recurse upwards to find a suitable parent.
42+
return findParent(entry, startIdx - 1);
43+
}
44+
45+
// Main loop to construct the hierarchy.
2346
for (let i = 0; i < entries.length; i++) {
2447
const entry = entries[i];
2548
const currentDepth = entry.heading.depth;
2649

50+
// Top-level entries are added directly to roots.
2751
if (currentDepth <= 1) {
28-
// We're a top-level entry
2952
roots.push(entry);
3053
continue;
3154
}
3255

56+
// For non-root entries, find the appropriate parent.
3357
const previousEntry = entries[i - 1];
34-
3558
const previousDepth = previousEntry.heading.depth;
36-
if (currentDepth > previousDepth) {
37-
// We're a child of the previous one
38-
if (previousEntry.hierarchyChildren === undefined) {
39-
previousEntry.hierarchyChildren = [];
40-
}
4159

60+
if (currentDepth > previousDepth) {
61+
previousEntry.hierarchyChildren ??= [];
4262
previousEntry.hierarchyChildren.push(entry);
4363
} else {
44-
if (i < 2) {
45-
throw new Error(`can't find parent since i < 2 (${i})`);
46-
}
47-
48-
// Loop to find the entry we're a child of
49-
for (let j = i - 2; j >= 0; j--) {
50-
const jEntry = entries[j];
51-
const jDepth = jEntry.heading.depth;
52-
53-
if (currentDepth > jDepth) {
54-
// Found it
55-
jEntry.hierarchyChildren.push(entry);
56-
break;
57-
}
58-
}
64+
// Use recursive helper to find the nearest valid parent.
65+
const parent = findParent(entry, i - 2);
66+
parent.hierarchyChildren.push(entry);
5967
}
6068
}
6169

src/generators/legacy-json/utils/buildSection.mjs

Lines changed: 36 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -30,27 +30,15 @@ const sectionTypePlurals = {
3030
* @returns {import('../types.d.ts').Meta | undefined}
3131
*/
3232
function createMeta(entry) {
33-
const makeArrayIfNotAlready = val => (Array.isArray(val) ? val : [val]);
33+
const arrify = val => (Array.isArray(val) ? val : [val]);
3434
const { added_in, n_api_version, deprecated_in, removed_in, changes } = entry;
35-
if (
36-
!added_in &&
37-
!n_api_version &&
38-
!deprecated_in &&
39-
!removed_in &&
40-
changes.length < 1
41-
) {
42-
return undefined;
43-
}
35+
4436
return {
4537
changes,
46-
added: added_in ? makeArrayIfNotAlready(added_in) : undefined,
47-
napiVersion: n_api_version
48-
? makeArrayIfNotAlready(n_api_version)
49-
: undefined,
50-
deprecated: deprecated_in
51-
? makeArrayIfNotAlready(deprecated_in)
52-
: undefined,
53-
removed: removed_in ? makeArrayIfNotAlready(removed_in) : undefined,
38+
added: arrify(added_in ?? []),
39+
napiVersion: arrify(n_api_version ?? []),
40+
deprecated: arrify(deprecated_in ?? []),
41+
removed: arrify(removed_in ?? []),
5442
};
5543
}
5644

@@ -60,9 +48,8 @@ function createMeta(entry) {
6048
* @returns {import('../types.d.ts').Section}
6149
*/
6250
function createSection(entry, head) {
63-
const text = transformNodesToString(head.children);
6451
return {
65-
textRaw: text,
52+
textRaw: transformNodesToString(head.children),
6653
name: head.data.name,
6754
type: head.data.type,
6855
meta: createMeta(entry),
@@ -82,61 +69,45 @@ function parseListItem(child, entry) {
8269
*/
8370
const current = {};
8471

72+
const getRawContent = node => {
73+
return entry.rawContent.slice(
74+
node.position.start.offset,
75+
node.position.end.offset
76+
);
77+
};
78+
79+
// Utility to match and extract information based on a regex pattern
80+
const extractPattern = (text, pattern, key) => {
81+
const [, match] = text.match(pattern) || [];
82+
if (match) {
83+
current[key] = match.trim().replace(/\.$/, '');
84+
return text.replace(pattern, '');
85+
}
86+
return text;
87+
};
88+
8589
current.textRaw = child.children
8690
.filter(node => node.type !== 'list')
87-
.map(node =>
88-
entry.rawContent.slice(
89-
node.position.start.offset,
90-
node.position.end.offset
91-
)
92-
)
91+
.map(getRawContent)
9392
.join('')
9493
.replace(/\s+/g, ' ')
9594
.replaceAll(/<!--.*?-->/gs, '');
9695

97-
if (!current.textRaw) {
98-
throw new Error(`Empty list item: ${JSON.stringify(child)}`);
99-
}
100-
10196
let text = current.textRaw;
10297

103-
// Extract name
104-
if (RETURN_EXPRESSION.test(text)) {
105-
current.name = 'return';
106-
text = text.replace(RETURN_EXPRESSION, '');
107-
} else {
108-
const [, name] = text.match(NAME_EXPRESSION) || [];
109-
if (name) {
110-
current.name = name;
111-
text = text.replace(NAME_EXPRESSION, '');
112-
}
113-
}
114-
115-
// Extract type (if provided)
116-
const [, type] = text.match(TYPE_EXPRESSION) || [];
117-
if (type) {
118-
current.type = type;
119-
text = text.replace(TYPE_EXPRESSION, '');
120-
}
98+
// Extract `name`, `type`, `default`, and `desc` fields with helper functions
99+
text = extractPattern(text, RETURN_EXPRESSION, 'name') || text;
100+
text = current.name ? text : extractPattern(text, NAME_EXPRESSION, 'name');
101+
text = extractPattern(text, TYPE_EXPRESSION, 'type');
102+
text = extractPattern(text, DEFAULT_EXPRESSION, 'default');
121103

122-
// Remove leading hyphens
123-
text = text.replace(LEADING_HYPHEN, '');
124-
125-
// Extract default value (if exists)
126-
const [, defaultValue] = text.match(DEFAULT_EXPRESSION) || [];
127-
if (defaultValue) {
128-
current.default = defaultValue.replace(/\.$/, '');
129-
text = text.replace(DEFAULT_EXPRESSION, '');
130-
}
131-
132-
// Add remaining text to the desc
133-
if (text) {
134-
current.desc = text;
135-
}
104+
// Remove leading hyphens and remaining text becomes `desc`, if any.
105+
current.desc = text.replace(LEADING_HYPHEN, '').trim() || undefined;
136106

137-
const options = child.children.find(child => child.type === 'list');
138-
if (options) {
139-
current.options = options.children.map(child =>
107+
// Recursively parse options if available
108+
const optionsNode = child.children.find(child => child.type === 'list');
109+
if (optionsNode) {
110+
current.options = optionsNode.children.map(child =>
140111
parseListItem(child, entry)
141112
);
142113
}

src/utils/parser.mjs

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -123,12 +123,19 @@ export const parseHeadingIntoMetadata = (heading, depth) => {
123123
// Attempts to get a match from one of the heading types, if a match is found
124124
// we use that type as the heading type, and extract the regex expression match group
125125
// which should be the inner "plain" heading content (or the title of the heading for navigation)
126-
const [, innerHeading] = heading.match(regex) ?? [];
127-
128-
if (innerHeading && innerHeading.length) {
129-
return { text: heading, type, name: innerHeading, depth };
126+
const [, ...matches] = heading.match(regex) ?? [];
127+
128+
if (matches?.length) {
129+
return {
130+
text: heading,
131+
type,
132+
name: matches.filter(Boolean).at(-1),
133+
depth,
134+
};
130135
}
131136
}
132137

138+
console.log(undefined, heading);
139+
133140
return { text: heading, type: 'module', name: heading, depth };
134141
};

0 commit comments

Comments
 (0)