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