Skip to content

Commit 46e2c4b

Browse files
committed
feat: legacy json generator
Closes #57 Signed-off-by: flakey5 <[email protected]>
1 parent f88ed7a commit 46e2c4b

File tree

9 files changed

+399
-0
lines changed

9 files changed

+399
-0
lines changed

src/generators/index.mjs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,13 @@
33
import jsonSimple from './json-simple/index.mjs';
44
import legacyHtml from './legacy-html/index.mjs';
55
import legacyHtmlAll from './legacy-html-all/index.mjs';
6+
import legacyJson from './legacy-json/index.mjs';
7+
import legacyJsonAll from './legacy-json-all/index.mjs';
68

79
export default {
810
'json-simple': jsonSimple,
911
'legacy-html': legacyHtml,
1012
'legacy-html-all': legacyHtmlAll,
13+
'legacy-json': legacyJson,
14+
'legacy-json-all': legacyJsonAll,
1115
};
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
'use strict';
2+
3+
/**
4+
* @typedef {Array<TemplateValues>} Input
5+
*
6+
* @type {import('../types.d.ts').GeneratorMetadata<Input, import('./types.d.ts').Output>}
7+
*/
8+
export default {
9+
name: 'legacy-json-all',
10+
11+
version: '1.0.0',
12+
13+
description:
14+
'Generates the `all.json` file from the `legacy-json` generator, which includes all the modules in one single file.',
15+
16+
dependsOn: 'legacy-json',
17+
18+
async generate(input) {},
19+
};
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { MiscSection, Section, SignatureSection } from '../legacy-json/types';
2+
3+
export interface Output {
4+
miscs: MiscSection[];
5+
modules: Section[];
6+
classes: SignatureSection[];
7+
globals: object[]; // TODO
8+
methods: SignatureSection[];
9+
}

src/generators/legacy-json/index.mjs

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
'use strict';
2+
3+
import { writeFile } from 'node:fs/promises';
4+
import { join } from 'node:path';
5+
import { groupNodesByModule } from '../../utils/generators.mjs';
6+
import buildContent from './utils/buildContent.mjs';
7+
8+
/**
9+
* @typedef {Array<ApiDocMetadataEntry>} Input
10+
*
11+
* @type {import('../types.d.ts').GeneratorMetadata<Input, import('./types.d.ts').Section>}
12+
*/
13+
export default {
14+
name: 'legacy-json',
15+
16+
version: '1.0.0',
17+
18+
description: 'Generates the legacy version of the JSON API docs.',
19+
20+
dependsOn: 'ast',
21+
22+
async generate(input, { output }) {
23+
// This array holds all the generated values for each module
24+
const generatedValues = [];
25+
26+
const groupedModules = groupNodesByModule(input);
27+
28+
// Gets the first nodes of each module, which is considered the "head" of the module
29+
const headNodes = input.filter(node => node.heading.depth === 1);
30+
31+
/**
32+
* @param {ApiDocMetadataEntry} head
33+
* @returns {import('./types.d.ts').ModuleSection}
34+
*/
35+
const processModuleNodes = head => {
36+
const nodes = groupedModules.get(head.api);
37+
38+
const parsedContent = buildContent(head, nodes);
39+
generatedValues.push(parsedContent);
40+
41+
return parsedContent;
42+
};
43+
44+
for (const node of headNodes) {
45+
const result = processModuleNodes(node);
46+
// console.log(result)
47+
48+
await writeFile(
49+
join(output, `${node.api}.json`),
50+
JSON.stringify(result),
51+
'utf8'
52+
);
53+
}
54+
55+
return generatedValues;
56+
},
57+
};

src/generators/legacy-json/types.d.ts

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
export interface HierarchizedEntry extends ApiDocMetadataEntry {
2+
hierarchyChildren: ApiDocMetadataEntry[];
3+
}
4+
5+
export type Section =
6+
| SignatureSection
7+
| PropertySection
8+
| EventSection
9+
| MiscSection;
10+
11+
export interface Meta {
12+
changes: ApiDocMetadataChange[];
13+
added?: string[];
14+
napiVersion?: string[];
15+
deprecated?: string[];
16+
removed?: string[];
17+
}
18+
19+
export interface SectionBase {
20+
type: string;
21+
name: string;
22+
textRaw: string;
23+
displayName?: string;
24+
desc: string;
25+
shortDesc?: string;
26+
stability?: number;
27+
stabilityText?: string; // TODO find the type for this
28+
meta?: Meta;
29+
}
30+
31+
export interface ModuleSection extends SectionBase {
32+
type: 'module';
33+
source: string;
34+
miscs: MiscSection[];
35+
modules: ModuleSection[];
36+
// TODO ...
37+
}
38+
39+
export interface SignatureSection extends SectionBase {
40+
type: 'class' | 'ctor' | 'classMethod' | 'method';
41+
signatures: MethodSignature[];
42+
}
43+
44+
export interface MethodSignature {
45+
params: object[]; // TODO
46+
return?: object; // TODO
47+
}
48+
49+
export interface PropertySection extends SectionBase {
50+
type: 'property';
51+
typeof?: string;
52+
[key: string]: string | undefined;
53+
}
54+
55+
export interface EventSection extends SectionBase {
56+
type: 'event';
57+
params: object[]; // TODO
58+
}
59+
60+
export interface MiscSection extends SectionBase {
61+
type: 'misc';
62+
[key: string]: string | undefined;
63+
}
Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
import { buildHierarchy } from './buildHierarchy.mjs';
2+
3+
const sectionTypePlurals = {
4+
module: 'modules',
5+
misc: 'miscs',
6+
class: 'classes',
7+
method: 'methods',
8+
property: 'properties',
9+
global: 'globals',
10+
example: 'examples',
11+
ctor: 'ctors',
12+
classMethod: 'classMethods',
13+
event: 'events',
14+
var: 'vars',
15+
};
16+
17+
/**
18+
*
19+
* @param {HeadingMetadataParent} entry
20+
* @returns {import('../types.d.ts').Section}
21+
*/
22+
function createSection(entry) {
23+
const text = textJoin(entry.children);
24+
25+
// TODO check if type or name can be undefined
26+
return {
27+
textRaw: text,
28+
type: entry.data.type,
29+
name: entry.data.name,
30+
};
31+
}
32+
33+
/**
34+
*
35+
* @param {*} values TODO type
36+
* @returns {import('../types.d.ts').MethodSignature}
37+
*/
38+
function parseSignature(values) {
39+
/**
40+
* @type {import('../types.d.ts').MethodSignature}
41+
*/
42+
const signature = {};
43+
44+
signature.params = values.filter(value => {
45+
if (value.name === 'return') {
46+
signature.return = value;
47+
return false;
48+
}
49+
50+
return true;
51+
});
52+
53+
return signature;
54+
}
55+
56+
/**
57+
*
58+
* @param {import('mdast').PhrasingContent[]} nodes
59+
*/
60+
function textJoin(nodes) {
61+
return nodes
62+
.map(node => {
63+
switch (node.type) {
64+
case 'linkReference':
65+
console.error(`todo link reference`);
66+
return `TODO`;
67+
case `strong`:
68+
return `**${textJoin(node.children)}**`;
69+
case `emphasis`:
70+
return `_${textJoin(node.children)}_`;
71+
default:
72+
if (node.children) {
73+
return textJoin(node.children);
74+
}
75+
76+
return node.value;
77+
}
78+
})
79+
.join('');
80+
}
81+
82+
/**
83+
* @param {import('../types.d.ts').HierarchizedEntry} entry
84+
* @returns {import('../types.d.ts').Meta | undefined}
85+
*/
86+
function createMeta(entry) {
87+
const makeArrayIfNotAlready = val => (Array.isArray(val) ? val : [val]);
88+
89+
const { added_in, n_api_version, deprecated_in, removed_in, changes } = entry;
90+
if (added_in || n_api_version || deprecated_in || removed_in) {
91+
return {
92+
changes,
93+
added: makeArrayIfNotAlready(added_in),
94+
napiVersion: makeArrayIfNotAlready(n_api_version),
95+
deprecated: makeArrayIfNotAlready(deprecated_in),
96+
removed: makeArrayIfNotAlready(removed_in),
97+
};
98+
}
99+
100+
return undefined;
101+
}
102+
103+
function parseListItem() {
104+
return { type: 'asd' };
105+
}
106+
107+
/**
108+
*
109+
* @param {import('../types.d.ts').HierarchizedEntry} entry
110+
* @param {import('../types.d.ts').Section} parentSection
111+
*/
112+
function handleEntry(entry, parentSection) {
113+
let [headingNode, ...nodes] = structuredClone(entry.content.children);
114+
115+
const section = createSection(headingNode);
116+
section.meta = createMeta(entry);
117+
118+
const pluralType = sectionTypePlurals[section.type];
119+
if (!(pluralType in parentSection)) {
120+
parentSection[pluralType] = [];
121+
}
122+
parentSection[sectionTypePlurals[section.type]].push(section);
123+
124+
// Remove metadata not directly inferable from the markdown
125+
nodes.forEach((node, i) => {
126+
if (
127+
node.type === 'blockquote' &&
128+
node.children.length === 1 &&
129+
node.children[0].type === 'paragraph' &&
130+
nodes.slice(0, i).every(node => node.type === 'list')
131+
) {
132+
const text = textJoin(node.children[0].children);
133+
const stability = /^Stability: ([0-5])(?:\s*-\s*)?(.*)$/s.exec(text);
134+
if (stability) {
135+
section.stability = parseInt(stability[1], 10);
136+
section.stabilityText = stability[2].replaceAll('\n', ' ').trim();
137+
delete nodes[i];
138+
}
139+
}
140+
});
141+
142+
// Compress the node array.
143+
nodes = nodes.filter(() => true);
144+
145+
const list = nodes.length && nodes[0].type === 'list' ? nodes.shift() : null;
146+
147+
if (list) {
148+
const values = list ? list.children.map(child => parseListItem(child)) : [];
149+
150+
switch (section.type) {
151+
case 'ctor':
152+
case 'classMethod':
153+
case 'method': {
154+
const signature = parseSignature(values);
155+
section.signatures = [signature];
156+
break;
157+
}
158+
case 'property':
159+
break;
160+
case 'event':
161+
section.params = values;
162+
break;
163+
default:
164+
// List wasn't consumed, add it back
165+
nodes.unshift(list);
166+
}
167+
}
168+
169+
if (nodes.length) {
170+
if (section.desc) {
171+
section.shortDesc = section.desc;
172+
}
173+
174+
// TODO: parse definitions
175+
}
176+
177+
// Handle our children if we have them
178+
if (entry.hierarchyChildren) {
179+
entry.hierarchyChildren.forEach(child => handleEntry(child, section));
180+
}
181+
182+
// TODO: the cleanup work the current parser does
183+
}
184+
185+
/**
186+
* @param {ApiDocMetadataEntry} head
187+
* @param {Array<ApiDocMetadataEntry>} entries
188+
* @returns {import('../types.d.ts').ModuleSection}
189+
*/
190+
export default (head, entries) => {
191+
/**
192+
* @type {import('../types.d.ts').ModuleSection}
193+
*/
194+
const module = {
195+
type: 'module',
196+
source: `doc/api/${head.api_doc_source}`,
197+
};
198+
199+
buildHierarchy(entries).forEach(entry => handleEntry(entry, module));
200+
201+
return module;
202+
};

0 commit comments

Comments
 (0)