reffy 20.0.13 → 20.0.15
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/LICENSE +21 -21
- package/README.md +151 -151
- package/index.js +29 -29
- package/package.json +5 -5
- package/reffy.js +324 -324
- package/schemas/browserlib/extract-algorithms.json +52 -52
- package/schemas/browserlib/extract-cssdfn.json +108 -108
- package/schemas/browserlib/extract-dfns.json +90 -90
- package/schemas/browserlib/extract-elements.json +17 -17
- package/schemas/browserlib/extract-events.json +31 -31
- package/schemas/browserlib/extract-headings.json +19 -19
- package/schemas/browserlib/extract-ids.json +7 -7
- package/schemas/browserlib/extract-links.json +12 -12
- package/schemas/browserlib/extract-refs.json +12 -12
- package/schemas/common.json +876 -876
- package/schemas/files/extracts/algorithms.json +12 -12
- package/schemas/files/extracts/css.json +16 -16
- package/schemas/files/extracts/dfns.json +12 -12
- package/schemas/files/extracts/elements.json +12 -12
- package/schemas/files/extracts/events.json +12 -12
- package/schemas/files/extracts/headings.json +12 -12
- package/schemas/files/extracts/ids.json +12 -12
- package/schemas/files/extracts/links.json +12 -12
- package/schemas/files/extracts/refs.json +12 -12
- package/schemas/files/index.json +59 -59
- package/schemas/postprocessing/events.json +50 -50
- package/schemas/postprocessing/idlnames-parsed.json +27 -27
- package/schemas/postprocessing/idlnames.json +17 -17
- package/schemas/postprocessing/idlparsed.json +67 -67
- package/src/browserlib/clone-and-clean.mjs +24 -24
- package/src/browserlib/create-outline.mjs +353 -353
- package/src/browserlib/extract-algorithms.mjs +723 -723
- package/src/browserlib/extract-cddl.mjs +125 -125
- package/src/browserlib/extract-dfns.mjs +1093 -1093
- package/src/browserlib/extract-headings.mjs +76 -76
- package/src/browserlib/extract-ids.mjs +28 -28
- package/src/browserlib/extract-links.mjs +45 -45
- package/src/browserlib/extract-references.mjs +308 -308
- package/src/browserlib/extract-webidl.mjs +89 -89
- package/src/browserlib/get-absolute-url.mjs +29 -29
- package/src/browserlib/get-code-elements.mjs +20 -20
- package/src/browserlib/get-generator.mjs +26 -26
- package/src/browserlib/get-lastmodified-date.mjs +13 -13
- package/src/browserlib/get-revision.mjs +12 -12
- package/src/browserlib/get-title.mjs +14 -14
- package/src/browserlib/informative-selector.mjs +24 -24
- package/src/browserlib/map-ids-to-headings.mjs +173 -173
- package/src/browserlib/reffy.json +85 -85
- package/src/browserlib/trim-spaces.mjs +35 -35
- package/src/cli/check-missing-dfns.js +587 -587
- package/src/cli/merge-crawl-results.js +132 -132
- package/src/cli/parse-webidl.js +447 -447
- package/src/lib/css-grammar-parse-tree.schema.json +109 -109
- package/src/lib/css-grammar-parser.js +440 -440
- package/src/lib/fetch.js +51 -51
- package/src/lib/markdown-report.js +360 -360
- package/src/lib/mock-server.js +218 -218
- package/src/lib/post-processor.js +322 -322
- package/src/lib/throttled-queue.js +129 -129
- package/src/postprocessing/annotate-links.js +41 -41
- package/src/postprocessing/csscomplete.js +48 -48
- package/src/postprocessing/idlnames.js +391 -391
- package/src/postprocessing/idlparsed.js +179 -179
- package/src/postprocessing/patch-dfns.js +51 -51
- package/src/specs/missing-css-rules.json +197 -197
- package/src/specs/spec-equivalents.json +149 -149
- package/src/browserlib/extract-editors.mjs~ +0 -14
- package/src/browserlib/extract-events.mjs~ +0 -3
- package/src/browserlib/generate-es-dfn-report.sh~ +0 -4
- package/src/browserlib/get-revision.mjs~ +0 -7
- package/src/cli/csstree-grammar-check.js +0 -28
- package/src/cli/csstree-grammar-check.js~ +0 -10
- package/src/cli/csstree-grammar-parser.js +0 -11
- package/src/cli/csstree-grammar-parser.js~ +0 -1
- package/src/cli/extract-editors.js~ +0 -38
- package/src/cli/process-specs.js~ +0 -28
- package/src/postprocessing/annotate-links.js~ +0 -8
- package/src/postprocessing/events.js~ +0 -245
|
@@ -1,391 +1,391 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Post-processing module that creates an index of IDL names and a list of
|
|
3
|
-
* IDL extracts per IDL name.
|
|
4
|
-
*
|
|
5
|
-
* The module runs at the crawl level. It depends on another post-processing
|
|
6
|
-
* module, namely the "idlparsed" one, which runs at the spec level (so no need
|
|
7
|
-
* to worry about ordering, "idlparsed" will always run before this one).
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import fs from 'node:fs';
|
|
11
|
-
import path from 'node:path';
|
|
12
|
-
import {
|
|
13
|
-
matchIdlDfn,
|
|
14
|
-
getExpectedDfnFromIdlDesc } from '../cli/check-missing-dfns.js';
|
|
15
|
-
import {
|
|
16
|
-
isLatestLevelThatPasses,
|
|
17
|
-
createFolderIfNeeded,
|
|
18
|
-
shouldSaveToFile } from '../lib/util.js';
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Definition of the post-processing module
|
|
23
|
-
*/
|
|
24
|
-
export default {
|
|
25
|
-
dependsOn: ['idlparsed', 'dfns'],
|
|
26
|
-
input: 'crawl',
|
|
27
|
-
run: generateIdlNames,
|
|
28
|
-
save: saveIdlNames
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Retrieve the list of definitions that are needed to link members of the
|
|
34
|
-
* the given IDL node
|
|
35
|
-
*
|
|
36
|
-
* @function
|
|
37
|
-
* @param {Object} desc The node that describes an IDL fragment (without the
|
|
38
|
-
* parsed IDL node structure)
|
|
39
|
-
* @param {Object} idlNode The parsed IDL node that describes the IDL fragment
|
|
40
|
-
* @param {Array} results The list of spec crawl results
|
|
41
|
-
* @return {Object} A list of related definitions indexed by URL of the spec
|
|
42
|
-
* that defines them.
|
|
43
|
-
*/
|
|
44
|
-
function getRelatedDfns(desc, idlNode, results) {
|
|
45
|
-
const dfns = [];
|
|
46
|
-
const spec = results.find(s => s.url === desc.spec.url);
|
|
47
|
-
if (!spec || !spec.dfns) {
|
|
48
|
-
return {};
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
const parentIdl = idlNode;
|
|
52
|
-
const idlToLinkify = [idlNode];
|
|
53
|
-
|
|
54
|
-
switch (idlNode.type) {
|
|
55
|
-
case 'enum':
|
|
56
|
-
if (idlNode.values) {
|
|
57
|
-
idlToLinkify.push(...idlNode.values);
|
|
58
|
-
}
|
|
59
|
-
break;
|
|
60
|
-
|
|
61
|
-
case 'callback':
|
|
62
|
-
case 'callback interface':
|
|
63
|
-
case 'dictionary':
|
|
64
|
-
case 'interface':
|
|
65
|
-
case 'interface mixin':
|
|
66
|
-
case 'namespace':
|
|
67
|
-
if (idlNode.members) {
|
|
68
|
-
idlToLinkify.push(...idlNode.members);
|
|
69
|
-
}
|
|
70
|
-
break;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// Complete IDL to linkify with a link to the definition in the spec, if found
|
|
74
|
-
idlToLinkify.forEach(idl => {
|
|
75
|
-
const expected = getExpectedDfnFromIdlDesc(idl, parentIdl);
|
|
76
|
-
if (expected) {
|
|
77
|
-
const dfn = spec.dfns.find(dfn => matchIdlDfn(expected, dfn));
|
|
78
|
-
if (dfn) {
|
|
79
|
-
dfns.push(dfn);
|
|
80
|
-
}
|
|
81
|
-
else {
|
|
82
|
-
// console.warn('[warn] IDL Names - Missing dfn', JSON.stringify(expected));
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
return { spec, dfns };
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* Save IDL names to individual JSON files in the given folder
|
|
93
|
-
*
|
|
94
|
-
* @function
|
|
95
|
-
* @private
|
|
96
|
-
* @param {Object} names Report generated by generateIdlNames
|
|
97
|
-
* @param {String} folder Path to folder
|
|
98
|
-
*/
|
|
99
|
-
async function saveParsedIdlNames(names, folder) {
|
|
100
|
-
await createFolderIfNeeded(folder);
|
|
101
|
-
await Promise.all(Object.entries(names).map(([name, idl]) => {
|
|
102
|
-
const json = JSON.stringify(idl, null, 2);
|
|
103
|
-
const filename = path.join(folder, name + '.json');
|
|
104
|
-
return fs.promises.writeFile(filename, json);
|
|
105
|
-
}));
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
/**
|
|
111
|
-
* Save IDL fragments to individual text files in the given folder
|
|
112
|
-
*
|
|
113
|
-
* @function
|
|
114
|
-
* @private
|
|
115
|
-
* @param {Object} names Report generated by generateIdlNames
|
|
116
|
-
* @param {String} folder Path to folder
|
|
117
|
-
*/
|
|
118
|
-
async function saveIdlNamesFragments(names, folder) {
|
|
119
|
-
function serializeNode(node) {
|
|
120
|
-
return `// Source: ${node.spec.title} (${node.spec.url})\n` +
|
|
121
|
-
node.fragment;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
await createFolderIfNeeded(folder);
|
|
125
|
-
await Promise.all(Object.entries(names).map(([name, idl]) => {
|
|
126
|
-
const res = [];
|
|
127
|
-
if (idl.defined) {
|
|
128
|
-
res.push(serializeNode(idl.defined));
|
|
129
|
-
}
|
|
130
|
-
if (idl.extended) {
|
|
131
|
-
idl.extended.map(node => res.push(serializeNode(node)));
|
|
132
|
-
}
|
|
133
|
-
const filename = path.join(folder, name + '.idl');
|
|
134
|
-
return fs.promises.writeFile(filename, res.join('\n\n'));
|
|
135
|
-
}));
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
/**
|
|
140
|
-
* Generate an `idlnames.json` index file that contains all IDL names with
|
|
141
|
-
* pointers to files in idlnames and idlnamesparsed subfolder.
|
|
142
|
-
*
|
|
143
|
-
* @function
|
|
144
|
-
* @private
|
|
145
|
-
* @param {Object} names Report generated by generateIdlNames
|
|
146
|
-
* @param {String} folder Path to folder where index file is to be saved
|
|
147
|
-
*/
|
|
148
|
-
async function saveIndex(names, folder) {
|
|
149
|
-
await createFolderIfNeeded(folder);
|
|
150
|
-
let index = {};
|
|
151
|
-
Object.keys(names).sort().forEach(name => {
|
|
152
|
-
index[name] = {
|
|
153
|
-
fragment: `idlnames/${name}.idl`,
|
|
154
|
-
parsed: `idlnamesparsed/${name}.json`,
|
|
155
|
-
type: names[name].type
|
|
156
|
-
};
|
|
157
|
-
});
|
|
158
|
-
const filename = path.join(folder, 'idlnames.json');
|
|
159
|
-
return fs.promises.writeFile(filename, JSON.stringify(index, null, 2));
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
/**
|
|
164
|
-
* Generate a report per referenceable IDL name from the given crawl results.
|
|
165
|
-
*
|
|
166
|
-
* @function
|
|
167
|
-
* @public
|
|
168
|
-
* @param {Object} crawl Crawl results
|
|
169
|
-
* @param {Object} options Generation options. Set "dfns" to true to embed
|
|
170
|
-
* definitions in the final export.
|
|
171
|
-
* @return {Object} A list indexed by referenceable IDL name that details, for
|
|
172
|
-
* each of them, the parsed IDL that defines the name throughout the specs,
|
|
173
|
-
* along with links to the actual definition of the terms in the specs
|
|
174
|
-
* (when known).
|
|
175
|
-
*/
|
|
176
|
-
async function generateIdlNames(crawl, options) {
|
|
177
|
-
function specInfo(spec) {
|
|
178
|
-
return {
|
|
179
|
-
spec: {
|
|
180
|
-
title: spec.title,
|
|
181
|
-
url: spec.url
|
|
182
|
-
}
|
|
183
|
-
};
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
const fragments = {};
|
|
187
|
-
const names = {};
|
|
188
|
-
|
|
189
|
-
function defineIDLContent(spec) {
|
|
190
|
-
return spec.idlparsed?.idlNames || spec.idlparsed?.idlExtendedNames;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
// Only keep latest version of specs and delta specs that define some IDL
|
|
194
|
-
const results = crawl.results.filter(spec =>
|
|
195
|
-
(spec.seriesComposition !== 'delta' && isLatestLevelThatPasses(spec, crawl.results, defineIDLContent)) ||
|
|
196
|
-
(spec.seriesComposition === 'delta' && defineIDLContent(spec)));
|
|
197
|
-
|
|
198
|
-
// Add main definitions of all IDL names
|
|
199
|
-
// (using the latest version of a spec that defines some IDL)
|
|
200
|
-
results.forEach(spec => {
|
|
201
|
-
if (!spec.idlparsed.idlNames) {
|
|
202
|
-
return;
|
|
203
|
-
}
|
|
204
|
-
Object.entries(spec.idlparsed.idlNames).forEach(([name, idl]) => {
|
|
205
|
-
const desc = Object.assign(specInfo(spec), { fragment: idl.fragment });
|
|
206
|
-
fragments[idl.fragment] = idl;
|
|
207
|
-
|
|
208
|
-
if (names[name]) {
|
|
209
|
-
// That should never happen, yet it does:
|
|
210
|
-
// IDL names are sometimes defined in multiple specs. Let's consider
|
|
211
|
-
// that the "first" (in order of apparence in the report) apparence is
|
|
212
|
-
// the main one, and let's ignore the second definition.
|
|
213
|
-
options.quiet ?? console.warn('[warn] IDL Names - Name defined more than once', name);
|
|
214
|
-
return;
|
|
215
|
-
}
|
|
216
|
-
names[name] = {
|
|
217
|
-
name: name,
|
|
218
|
-
type: idl.type,
|
|
219
|
-
defined: desc,
|
|
220
|
-
extended: [],
|
|
221
|
-
inheritance: idl.inheritance,
|
|
222
|
-
includes: []
|
|
223
|
-
};
|
|
224
|
-
});
|
|
225
|
-
});
|
|
226
|
-
|
|
227
|
-
// Add definitions that extend base definitions
|
|
228
|
-
results.forEach(spec => {
|
|
229
|
-
if (!spec.idlparsed.idlExtendedNames) {
|
|
230
|
-
return;
|
|
231
|
-
}
|
|
232
|
-
Object.entries(spec.idlparsed.idlExtendedNames).forEach(([name, extensions]) =>
|
|
233
|
-
extensions.forEach(idl => {
|
|
234
|
-
const desc = Object.assign(specInfo(spec), { fragment: idl.fragment });
|
|
235
|
-
fragments[idl.fragment] = idl;
|
|
236
|
-
|
|
237
|
-
if (!names[name]) {
|
|
238
|
-
// That should never happen, and it does not in practice unless there
|
|
239
|
-
// was a crawling error on the spec that normally defines the base
|
|
240
|
-
// IDL name. Alas, such crawling errors do happen from time to time.
|
|
241
|
-
options.quiet ?? console.warn('[warn] IDL Names - No definition found', name);
|
|
242
|
-
names[name] = {
|
|
243
|
-
extended: [],
|
|
244
|
-
includes: []
|
|
245
|
-
};
|
|
246
|
-
}
|
|
247
|
-
if (idl.includes) {
|
|
248
|
-
names[name].includes.push(idl.includes);
|
|
249
|
-
}
|
|
250
|
-
names[name].extended.push(desc);
|
|
251
|
-
}));
|
|
252
|
-
});
|
|
253
|
-
|
|
254
|
-
// Expand inheritance and includes info
|
|
255
|
-
Object.values(names).forEach(desc => {
|
|
256
|
-
if (desc.includes) {
|
|
257
|
-
desc.includes = desc.includes.map(name => names[name]).filter(k => !!k);
|
|
258
|
-
}
|
|
259
|
-
if (desc.inheritance) {
|
|
260
|
-
desc.inheritance = names[desc.inheritance];
|
|
261
|
-
}
|
|
262
|
-
});
|
|
263
|
-
|
|
264
|
-
// The expansions were done by reference. In the end, we'll want to serialize
|
|
265
|
-
// the structure, so we need to make sure that there aren't any cycle. Mixins
|
|
266
|
-
// cannot create cycles, but inheritance chains can, if not done properly.
|
|
267
|
-
Object.entries(names).forEach(([name, desc]) => {
|
|
268
|
-
let current = desc;
|
|
269
|
-
while (current) {
|
|
270
|
-
current = current.inheritance;
|
|
271
|
-
if (current && (current.name === name)) {
|
|
272
|
-
options.quiet ?? console.warn('[warn] IDL Names - Cyclic inheritance chain detected', name);
|
|
273
|
-
current.inheritance = null;
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
});
|
|
277
|
-
|
|
278
|
-
// Add a link to the definition of each IDL name, when possible
|
|
279
|
-
Object.entries(names).forEach(([name, desc]) => {
|
|
280
|
-
if (!desc.defined) {
|
|
281
|
-
return;
|
|
282
|
-
}
|
|
283
|
-
const spec = results.find(s => s.url === desc.defined.spec.url);
|
|
284
|
-
if (!spec || !spec.dfns) {
|
|
285
|
-
return;
|
|
286
|
-
}
|
|
287
|
-
const idl = fragments[desc.defined.fragment];
|
|
288
|
-
const expected = getExpectedDfnFromIdlDesc(idl);
|
|
289
|
-
if (!expected) {
|
|
290
|
-
return;
|
|
291
|
-
}
|
|
292
|
-
const dfn = spec.dfns.find(dfn => matchIdlDfn(expected, dfn));
|
|
293
|
-
if (!dfn) {
|
|
294
|
-
return;
|
|
295
|
-
}
|
|
296
|
-
desc.defined.href = dfn.href;
|
|
297
|
-
});
|
|
298
|
-
|
|
299
|
-
// Serialize structures
|
|
300
|
-
Object.entries(names).forEach(([name, desc]) => {
|
|
301
|
-
names[name] = JSON.parse(JSON.stringify(desc));
|
|
302
|
-
});
|
|
303
|
-
|
|
304
|
-
// If requested, add, for each IDL name, the list of definitions for the
|
|
305
|
-
// interfaces and members that the name defines, extends, inherits, or
|
|
306
|
-
// includes.
|
|
307
|
-
if (options.dfns) {
|
|
308
|
-
const dfns = {};
|
|
309
|
-
Object.entries(names).forEach(([name, desc]) => {
|
|
310
|
-
dfns[name] = {};
|
|
311
|
-
if (desc.defined) {
|
|
312
|
-
const idl = fragments[desc.defined.fragment];
|
|
313
|
-
const descDfns = getRelatedDfns(desc.defined, idl, results);
|
|
314
|
-
const url = descDfns.spec ? descDfns.spec.url : null;
|
|
315
|
-
if (url) {
|
|
316
|
-
if (!dfns[name][url]) {
|
|
317
|
-
dfns[name][url] = new Set();
|
|
318
|
-
}
|
|
319
|
-
descDfns.dfns.forEach(dfn => dfns[name][url].add(dfn));
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
if (desc.extended) {
|
|
323
|
-
desc.extended.forEach(ext => {
|
|
324
|
-
const idl = fragments[ext.fragment];
|
|
325
|
-
const extDfns = getRelatedDfns(ext, idl, results);
|
|
326
|
-
const url = extDfns.spec ? extDfns.spec.url : null;
|
|
327
|
-
if (url) {
|
|
328
|
-
if (!dfns[name][url]) {
|
|
329
|
-
dfns[name][url] = new Set();
|
|
330
|
-
}
|
|
331
|
-
extDfns.dfns.forEach(dfn => dfns[name][url].add(dfn));
|
|
332
|
-
}
|
|
333
|
-
});
|
|
334
|
-
}
|
|
335
|
-
});
|
|
336
|
-
|
|
337
|
-
// Add definitions at the root level and recursively extend the list
|
|
338
|
-
// with the definitions related to the IDL names that the current IDL name
|
|
339
|
-
// inherits from or includes.
|
|
340
|
-
function addDfns(rootName, name) {
|
|
341
|
-
name = name || rootName;
|
|
342
|
-
if (!names[rootName].dfns) {
|
|
343
|
-
names[rootName].dfns = {};
|
|
344
|
-
}
|
|
345
|
-
Object.entries(dfns[name]).forEach(([url, list]) => {
|
|
346
|
-
if (!names[rootName].dfns[url]) {
|
|
347
|
-
names[rootName].dfns[url] = new Set();
|
|
348
|
-
}
|
|
349
|
-
list.forEach(dfn => names[rootName].dfns[url].add(dfn));
|
|
350
|
-
});
|
|
351
|
-
const desc = names[name];
|
|
352
|
-
if (desc.includes) {
|
|
353
|
-
desc.includes.forEach(incl => addDfns(rootName, incl.name));
|
|
354
|
-
}
|
|
355
|
-
if (desc.inheritance) {
|
|
356
|
-
addDfns(rootName, desc.inheritance.name);
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
Object.keys(names).forEach(name => {
|
|
360
|
-
addDfns(name);
|
|
361
|
-
|
|
362
|
-
// Convert sets to arrays
|
|
363
|
-
Object.entries(names[name].dfns).forEach(([url, list]) => {
|
|
364
|
-
names[name].dfns[url] = Array.from(list);
|
|
365
|
-
});
|
|
366
|
-
});
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
return names;
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
/**
|
|
374
|
-
* Generate all IDL names exports: IDL fragments in `idlnames`, parsed
|
|
375
|
-
* structures in `idlnamesparsed`, and index file.
|
|
376
|
-
*
|
|
377
|
-
* @function
|
|
378
|
-
* @public
|
|
379
|
-
* @param {Object} names Report generated by generateIdlNames
|
|
380
|
-
* @param {Object} options Crawl options ("output" will be used)
|
|
381
|
-
*/
|
|
382
|
-
async function saveIdlNames(names, options) {
|
|
383
|
-
if (!shouldSaveToFile(options)) {
|
|
384
|
-
return;
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
const folder = options.output;
|
|
388
|
-
await saveParsedIdlNames(names, path.join(folder, 'idlnamesparsed'));
|
|
389
|
-
await saveIdlNamesFragments(names, path.join(folder, 'idlnames'));
|
|
390
|
-
await saveIndex(names, folder);
|
|
391
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Post-processing module that creates an index of IDL names and a list of
|
|
3
|
+
* IDL extracts per IDL name.
|
|
4
|
+
*
|
|
5
|
+
* The module runs at the crawl level. It depends on another post-processing
|
|
6
|
+
* module, namely the "idlparsed" one, which runs at the spec level (so no need
|
|
7
|
+
* to worry about ordering, "idlparsed" will always run before this one).
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import fs from 'node:fs';
|
|
11
|
+
import path from 'node:path';
|
|
12
|
+
import {
|
|
13
|
+
matchIdlDfn,
|
|
14
|
+
getExpectedDfnFromIdlDesc } from '../cli/check-missing-dfns.js';
|
|
15
|
+
import {
|
|
16
|
+
isLatestLevelThatPasses,
|
|
17
|
+
createFolderIfNeeded,
|
|
18
|
+
shouldSaveToFile } from '../lib/util.js';
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Definition of the post-processing module
|
|
23
|
+
*/
|
|
24
|
+
export default {
|
|
25
|
+
dependsOn: ['idlparsed', 'dfns'],
|
|
26
|
+
input: 'crawl',
|
|
27
|
+
run: generateIdlNames,
|
|
28
|
+
save: saveIdlNames
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Retrieve the list of definitions that are needed to link members of the
|
|
34
|
+
* the given IDL node
|
|
35
|
+
*
|
|
36
|
+
* @function
|
|
37
|
+
* @param {Object} desc The node that describes an IDL fragment (without the
|
|
38
|
+
* parsed IDL node structure)
|
|
39
|
+
* @param {Object} idlNode The parsed IDL node that describes the IDL fragment
|
|
40
|
+
* @param {Array} results The list of spec crawl results
|
|
41
|
+
* @return {Object} A list of related definitions indexed by URL of the spec
|
|
42
|
+
* that defines them.
|
|
43
|
+
*/
|
|
44
|
+
function getRelatedDfns(desc, idlNode, results) {
|
|
45
|
+
const dfns = [];
|
|
46
|
+
const spec = results.find(s => s.url === desc.spec.url);
|
|
47
|
+
if (!spec || !spec.dfns) {
|
|
48
|
+
return {};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const parentIdl = idlNode;
|
|
52
|
+
const idlToLinkify = [idlNode];
|
|
53
|
+
|
|
54
|
+
switch (idlNode.type) {
|
|
55
|
+
case 'enum':
|
|
56
|
+
if (idlNode.values) {
|
|
57
|
+
idlToLinkify.push(...idlNode.values);
|
|
58
|
+
}
|
|
59
|
+
break;
|
|
60
|
+
|
|
61
|
+
case 'callback':
|
|
62
|
+
case 'callback interface':
|
|
63
|
+
case 'dictionary':
|
|
64
|
+
case 'interface':
|
|
65
|
+
case 'interface mixin':
|
|
66
|
+
case 'namespace':
|
|
67
|
+
if (idlNode.members) {
|
|
68
|
+
idlToLinkify.push(...idlNode.members);
|
|
69
|
+
}
|
|
70
|
+
break;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Complete IDL to linkify with a link to the definition in the spec, if found
|
|
74
|
+
idlToLinkify.forEach(idl => {
|
|
75
|
+
const expected = getExpectedDfnFromIdlDesc(idl, parentIdl);
|
|
76
|
+
if (expected) {
|
|
77
|
+
const dfn = spec.dfns.find(dfn => matchIdlDfn(expected, dfn));
|
|
78
|
+
if (dfn) {
|
|
79
|
+
dfns.push(dfn);
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
// console.warn('[warn] IDL Names - Missing dfn', JSON.stringify(expected));
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
return { spec, dfns };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Save IDL names to individual JSON files in the given folder
|
|
93
|
+
*
|
|
94
|
+
* @function
|
|
95
|
+
* @private
|
|
96
|
+
* @param {Object} names Report generated by generateIdlNames
|
|
97
|
+
* @param {String} folder Path to folder
|
|
98
|
+
*/
|
|
99
|
+
async function saveParsedIdlNames(names, folder) {
|
|
100
|
+
await createFolderIfNeeded(folder);
|
|
101
|
+
await Promise.all(Object.entries(names).map(([name, idl]) => {
|
|
102
|
+
const json = JSON.stringify(idl, null, 2);
|
|
103
|
+
const filename = path.join(folder, name + '.json');
|
|
104
|
+
return fs.promises.writeFile(filename, json);
|
|
105
|
+
}));
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Save IDL fragments to individual text files in the given folder
|
|
112
|
+
*
|
|
113
|
+
* @function
|
|
114
|
+
* @private
|
|
115
|
+
* @param {Object} names Report generated by generateIdlNames
|
|
116
|
+
* @param {String} folder Path to folder
|
|
117
|
+
*/
|
|
118
|
+
async function saveIdlNamesFragments(names, folder) {
|
|
119
|
+
function serializeNode(node) {
|
|
120
|
+
return `// Source: ${node.spec.title} (${node.spec.url})\n` +
|
|
121
|
+
node.fragment;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
await createFolderIfNeeded(folder);
|
|
125
|
+
await Promise.all(Object.entries(names).map(([name, idl]) => {
|
|
126
|
+
const res = [];
|
|
127
|
+
if (idl.defined) {
|
|
128
|
+
res.push(serializeNode(idl.defined));
|
|
129
|
+
}
|
|
130
|
+
if (idl.extended) {
|
|
131
|
+
idl.extended.map(node => res.push(serializeNode(node)));
|
|
132
|
+
}
|
|
133
|
+
const filename = path.join(folder, name + '.idl');
|
|
134
|
+
return fs.promises.writeFile(filename, res.join('\n\n'));
|
|
135
|
+
}));
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Generate an `idlnames.json` index file that contains all IDL names with
|
|
141
|
+
* pointers to files in idlnames and idlnamesparsed subfolder.
|
|
142
|
+
*
|
|
143
|
+
* @function
|
|
144
|
+
* @private
|
|
145
|
+
* @param {Object} names Report generated by generateIdlNames
|
|
146
|
+
* @param {String} folder Path to folder where index file is to be saved
|
|
147
|
+
*/
|
|
148
|
+
async function saveIndex(names, folder) {
|
|
149
|
+
await createFolderIfNeeded(folder);
|
|
150
|
+
let index = {};
|
|
151
|
+
Object.keys(names).sort().forEach(name => {
|
|
152
|
+
index[name] = {
|
|
153
|
+
fragment: `idlnames/${name}.idl`,
|
|
154
|
+
parsed: `idlnamesparsed/${name}.json`,
|
|
155
|
+
type: names[name].type
|
|
156
|
+
};
|
|
157
|
+
});
|
|
158
|
+
const filename = path.join(folder, 'idlnames.json');
|
|
159
|
+
return fs.promises.writeFile(filename, JSON.stringify(index, null, 2));
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Generate a report per referenceable IDL name from the given crawl results.
|
|
165
|
+
*
|
|
166
|
+
* @function
|
|
167
|
+
* @public
|
|
168
|
+
* @param {Object} crawl Crawl results
|
|
169
|
+
* @param {Object} options Generation options. Set "dfns" to true to embed
|
|
170
|
+
* definitions in the final export.
|
|
171
|
+
* @return {Object} A list indexed by referenceable IDL name that details, for
|
|
172
|
+
* each of them, the parsed IDL that defines the name throughout the specs,
|
|
173
|
+
* along with links to the actual definition of the terms in the specs
|
|
174
|
+
* (when known).
|
|
175
|
+
*/
|
|
176
|
+
async function generateIdlNames(crawl, options) {
|
|
177
|
+
function specInfo(spec) {
|
|
178
|
+
return {
|
|
179
|
+
spec: {
|
|
180
|
+
title: spec.title,
|
|
181
|
+
url: spec.url
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const fragments = {};
|
|
187
|
+
const names = {};
|
|
188
|
+
|
|
189
|
+
function defineIDLContent(spec) {
|
|
190
|
+
return spec.idlparsed?.idlNames || spec.idlparsed?.idlExtendedNames;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Only keep latest version of specs and delta specs that define some IDL
|
|
194
|
+
const results = crawl.results.filter(spec =>
|
|
195
|
+
(spec.seriesComposition !== 'delta' && isLatestLevelThatPasses(spec, crawl.results, defineIDLContent)) ||
|
|
196
|
+
(spec.seriesComposition === 'delta' && defineIDLContent(spec)));
|
|
197
|
+
|
|
198
|
+
// Add main definitions of all IDL names
|
|
199
|
+
// (using the latest version of a spec that defines some IDL)
|
|
200
|
+
results.forEach(spec => {
|
|
201
|
+
if (!spec.idlparsed.idlNames) {
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
Object.entries(spec.idlparsed.idlNames).forEach(([name, idl]) => {
|
|
205
|
+
const desc = Object.assign(specInfo(spec), { fragment: idl.fragment });
|
|
206
|
+
fragments[idl.fragment] = idl;
|
|
207
|
+
|
|
208
|
+
if (names[name]) {
|
|
209
|
+
// That should never happen, yet it does:
|
|
210
|
+
// IDL names are sometimes defined in multiple specs. Let's consider
|
|
211
|
+
// that the "first" (in order of apparence in the report) apparence is
|
|
212
|
+
// the main one, and let's ignore the second definition.
|
|
213
|
+
options.quiet ?? console.warn('[warn] IDL Names - Name defined more than once', name);
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
names[name] = {
|
|
217
|
+
name: name,
|
|
218
|
+
type: idl.type,
|
|
219
|
+
defined: desc,
|
|
220
|
+
extended: [],
|
|
221
|
+
inheritance: idl.inheritance,
|
|
222
|
+
includes: []
|
|
223
|
+
};
|
|
224
|
+
});
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
// Add definitions that extend base definitions
|
|
228
|
+
results.forEach(spec => {
|
|
229
|
+
if (!spec.idlparsed.idlExtendedNames) {
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
Object.entries(spec.idlparsed.idlExtendedNames).forEach(([name, extensions]) =>
|
|
233
|
+
extensions.forEach(idl => {
|
|
234
|
+
const desc = Object.assign(specInfo(spec), { fragment: idl.fragment });
|
|
235
|
+
fragments[idl.fragment] = idl;
|
|
236
|
+
|
|
237
|
+
if (!names[name]) {
|
|
238
|
+
// That should never happen, and it does not in practice unless there
|
|
239
|
+
// was a crawling error on the spec that normally defines the base
|
|
240
|
+
// IDL name. Alas, such crawling errors do happen from time to time.
|
|
241
|
+
options.quiet ?? console.warn('[warn] IDL Names - No definition found', name);
|
|
242
|
+
names[name] = {
|
|
243
|
+
extended: [],
|
|
244
|
+
includes: []
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
if (idl.includes) {
|
|
248
|
+
names[name].includes.push(idl.includes);
|
|
249
|
+
}
|
|
250
|
+
names[name].extended.push(desc);
|
|
251
|
+
}));
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
// Expand inheritance and includes info
|
|
255
|
+
Object.values(names).forEach(desc => {
|
|
256
|
+
if (desc.includes) {
|
|
257
|
+
desc.includes = desc.includes.map(name => names[name]).filter(k => !!k);
|
|
258
|
+
}
|
|
259
|
+
if (desc.inheritance) {
|
|
260
|
+
desc.inheritance = names[desc.inheritance];
|
|
261
|
+
}
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
// The expansions were done by reference. In the end, we'll want to serialize
|
|
265
|
+
// the structure, so we need to make sure that there aren't any cycle. Mixins
|
|
266
|
+
// cannot create cycles, but inheritance chains can, if not done properly.
|
|
267
|
+
Object.entries(names).forEach(([name, desc]) => {
|
|
268
|
+
let current = desc;
|
|
269
|
+
while (current) {
|
|
270
|
+
current = current.inheritance;
|
|
271
|
+
if (current && (current.name === name)) {
|
|
272
|
+
options.quiet ?? console.warn('[warn] IDL Names - Cyclic inheritance chain detected', name);
|
|
273
|
+
current.inheritance = null;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
// Add a link to the definition of each IDL name, when possible
|
|
279
|
+
Object.entries(names).forEach(([name, desc]) => {
|
|
280
|
+
if (!desc.defined) {
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
const spec = results.find(s => s.url === desc.defined.spec.url);
|
|
284
|
+
if (!spec || !spec.dfns) {
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
const idl = fragments[desc.defined.fragment];
|
|
288
|
+
const expected = getExpectedDfnFromIdlDesc(idl);
|
|
289
|
+
if (!expected) {
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
const dfn = spec.dfns.find(dfn => matchIdlDfn(expected, dfn));
|
|
293
|
+
if (!dfn) {
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
desc.defined.href = dfn.href;
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
// Serialize structures
|
|
300
|
+
Object.entries(names).forEach(([name, desc]) => {
|
|
301
|
+
names[name] = JSON.parse(JSON.stringify(desc));
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
// If requested, add, for each IDL name, the list of definitions for the
|
|
305
|
+
// interfaces and members that the name defines, extends, inherits, or
|
|
306
|
+
// includes.
|
|
307
|
+
if (options.dfns) {
|
|
308
|
+
const dfns = {};
|
|
309
|
+
Object.entries(names).forEach(([name, desc]) => {
|
|
310
|
+
dfns[name] = {};
|
|
311
|
+
if (desc.defined) {
|
|
312
|
+
const idl = fragments[desc.defined.fragment];
|
|
313
|
+
const descDfns = getRelatedDfns(desc.defined, idl, results);
|
|
314
|
+
const url = descDfns.spec ? descDfns.spec.url : null;
|
|
315
|
+
if (url) {
|
|
316
|
+
if (!dfns[name][url]) {
|
|
317
|
+
dfns[name][url] = new Set();
|
|
318
|
+
}
|
|
319
|
+
descDfns.dfns.forEach(dfn => dfns[name][url].add(dfn));
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
if (desc.extended) {
|
|
323
|
+
desc.extended.forEach(ext => {
|
|
324
|
+
const idl = fragments[ext.fragment];
|
|
325
|
+
const extDfns = getRelatedDfns(ext, idl, results);
|
|
326
|
+
const url = extDfns.spec ? extDfns.spec.url : null;
|
|
327
|
+
if (url) {
|
|
328
|
+
if (!dfns[name][url]) {
|
|
329
|
+
dfns[name][url] = new Set();
|
|
330
|
+
}
|
|
331
|
+
extDfns.dfns.forEach(dfn => dfns[name][url].add(dfn));
|
|
332
|
+
}
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
// Add definitions at the root level and recursively extend the list
|
|
338
|
+
// with the definitions related to the IDL names that the current IDL name
|
|
339
|
+
// inherits from or includes.
|
|
340
|
+
function addDfns(rootName, name) {
|
|
341
|
+
name = name || rootName;
|
|
342
|
+
if (!names[rootName].dfns) {
|
|
343
|
+
names[rootName].dfns = {};
|
|
344
|
+
}
|
|
345
|
+
Object.entries(dfns[name]).forEach(([url, list]) => {
|
|
346
|
+
if (!names[rootName].dfns[url]) {
|
|
347
|
+
names[rootName].dfns[url] = new Set();
|
|
348
|
+
}
|
|
349
|
+
list.forEach(dfn => names[rootName].dfns[url].add(dfn));
|
|
350
|
+
});
|
|
351
|
+
const desc = names[name];
|
|
352
|
+
if (desc.includes) {
|
|
353
|
+
desc.includes.forEach(incl => addDfns(rootName, incl.name));
|
|
354
|
+
}
|
|
355
|
+
if (desc.inheritance) {
|
|
356
|
+
addDfns(rootName, desc.inheritance.name);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
Object.keys(names).forEach(name => {
|
|
360
|
+
addDfns(name);
|
|
361
|
+
|
|
362
|
+
// Convert sets to arrays
|
|
363
|
+
Object.entries(names[name].dfns).forEach(([url, list]) => {
|
|
364
|
+
names[name].dfns[url] = Array.from(list);
|
|
365
|
+
});
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
return names;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* Generate all IDL names exports: IDL fragments in `idlnames`, parsed
|
|
375
|
+
* structures in `idlnamesparsed`, and index file.
|
|
376
|
+
*
|
|
377
|
+
* @function
|
|
378
|
+
* @public
|
|
379
|
+
* @param {Object} names Report generated by generateIdlNames
|
|
380
|
+
* @param {Object} options Crawl options ("output" will be used)
|
|
381
|
+
*/
|
|
382
|
+
async function saveIdlNames(names, options) {
|
|
383
|
+
if (!shouldSaveToFile(options)) {
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
const folder = options.output;
|
|
388
|
+
await saveParsedIdlNames(names, path.join(folder, 'idlnamesparsed'));
|
|
389
|
+
await saveIdlNamesFragments(names, path.join(folder, 'idlnames'));
|
|
390
|
+
await saveIndex(names, folder);
|
|
391
|
+
}
|