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.
Files changed (78) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +151 -151
  3. package/index.js +29 -29
  4. package/package.json +5 -5
  5. package/reffy.js +324 -324
  6. package/schemas/browserlib/extract-algorithms.json +52 -52
  7. package/schemas/browserlib/extract-cssdfn.json +108 -108
  8. package/schemas/browserlib/extract-dfns.json +90 -90
  9. package/schemas/browserlib/extract-elements.json +17 -17
  10. package/schemas/browserlib/extract-events.json +31 -31
  11. package/schemas/browserlib/extract-headings.json +19 -19
  12. package/schemas/browserlib/extract-ids.json +7 -7
  13. package/schemas/browserlib/extract-links.json +12 -12
  14. package/schemas/browserlib/extract-refs.json +12 -12
  15. package/schemas/common.json +876 -876
  16. package/schemas/files/extracts/algorithms.json +12 -12
  17. package/schemas/files/extracts/css.json +16 -16
  18. package/schemas/files/extracts/dfns.json +12 -12
  19. package/schemas/files/extracts/elements.json +12 -12
  20. package/schemas/files/extracts/events.json +12 -12
  21. package/schemas/files/extracts/headings.json +12 -12
  22. package/schemas/files/extracts/ids.json +12 -12
  23. package/schemas/files/extracts/links.json +12 -12
  24. package/schemas/files/extracts/refs.json +12 -12
  25. package/schemas/files/index.json +59 -59
  26. package/schemas/postprocessing/events.json +50 -50
  27. package/schemas/postprocessing/idlnames-parsed.json +27 -27
  28. package/schemas/postprocessing/idlnames.json +17 -17
  29. package/schemas/postprocessing/idlparsed.json +67 -67
  30. package/src/browserlib/clone-and-clean.mjs +24 -24
  31. package/src/browserlib/create-outline.mjs +353 -353
  32. package/src/browserlib/extract-algorithms.mjs +723 -723
  33. package/src/browserlib/extract-cddl.mjs +125 -125
  34. package/src/browserlib/extract-dfns.mjs +1093 -1093
  35. package/src/browserlib/extract-headings.mjs +76 -76
  36. package/src/browserlib/extract-ids.mjs +28 -28
  37. package/src/browserlib/extract-links.mjs +45 -45
  38. package/src/browserlib/extract-references.mjs +308 -308
  39. package/src/browserlib/extract-webidl.mjs +89 -89
  40. package/src/browserlib/get-absolute-url.mjs +29 -29
  41. package/src/browserlib/get-code-elements.mjs +20 -20
  42. package/src/browserlib/get-generator.mjs +26 -26
  43. package/src/browserlib/get-lastmodified-date.mjs +13 -13
  44. package/src/browserlib/get-revision.mjs +12 -12
  45. package/src/browserlib/get-title.mjs +14 -14
  46. package/src/browserlib/informative-selector.mjs +24 -24
  47. package/src/browserlib/map-ids-to-headings.mjs +173 -173
  48. package/src/browserlib/reffy.json +85 -85
  49. package/src/browserlib/trim-spaces.mjs +35 -35
  50. package/src/cli/check-missing-dfns.js +587 -587
  51. package/src/cli/merge-crawl-results.js +132 -132
  52. package/src/cli/parse-webidl.js +447 -447
  53. package/src/lib/css-grammar-parse-tree.schema.json +109 -109
  54. package/src/lib/css-grammar-parser.js +440 -440
  55. package/src/lib/fetch.js +51 -51
  56. package/src/lib/markdown-report.js +360 -360
  57. package/src/lib/mock-server.js +218 -218
  58. package/src/lib/post-processor.js +322 -322
  59. package/src/lib/throttled-queue.js +129 -129
  60. package/src/postprocessing/annotate-links.js +41 -41
  61. package/src/postprocessing/csscomplete.js +48 -48
  62. package/src/postprocessing/idlnames.js +391 -391
  63. package/src/postprocessing/idlparsed.js +179 -179
  64. package/src/postprocessing/patch-dfns.js +51 -51
  65. package/src/specs/missing-css-rules.json +197 -197
  66. package/src/specs/spec-equivalents.json +149 -149
  67. package/src/browserlib/extract-editors.mjs~ +0 -14
  68. package/src/browserlib/extract-events.mjs~ +0 -3
  69. package/src/browserlib/generate-es-dfn-report.sh~ +0 -4
  70. package/src/browserlib/get-revision.mjs~ +0 -7
  71. package/src/cli/csstree-grammar-check.js +0 -28
  72. package/src/cli/csstree-grammar-check.js~ +0 -10
  73. package/src/cli/csstree-grammar-parser.js +0 -11
  74. package/src/cli/csstree-grammar-parser.js~ +0 -1
  75. package/src/cli/extract-editors.js~ +0 -38
  76. package/src/cli/process-specs.js~ +0 -28
  77. package/src/postprocessing/annotate-links.js~ +0 -8
  78. 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
+ }