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.
- package/LICENSE +21 -21
- package/README.md +158 -158
- package/index.js +11 -11
- package/package.json +53 -53
- package/reffy.js +248 -248
- package/src/browserlib/canonicalize-url.mjs +50 -50
- package/src/browserlib/create-outline.mjs +352 -352
- package/src/browserlib/extract-cssdfn.mjs +319 -319
- package/src/browserlib/extract-dfns.mjs +686 -686
- package/src/browserlib/extract-elements.mjs +205 -205
- package/src/browserlib/extract-headings.mjs +48 -48
- package/src/browserlib/extract-ids.mjs +28 -28
- package/src/browserlib/extract-links.mjs +28 -28
- package/src/browserlib/extract-references.mjs +203 -203
- package/src/browserlib/extract-webidl.mjs +134 -134
- package/src/browserlib/get-absolute-url.mjs +21 -21
- package/src/browserlib/get-generator.mjs +26 -26
- package/src/browserlib/get-lastmodified-date.mjs +13 -13
- package/src/browserlib/get-title.mjs +11 -11
- package/src/browserlib/informative-selector.mjs +16 -16
- package/src/browserlib/map-ids-to-headings.mjs +136 -136
- package/src/browserlib/reffy.json +53 -53
- package/src/cli/check-missing-dfns.js +609 -609
- package/src/cli/generate-idlnames.js +430 -430
- package/src/cli/generate-idlparsed.js +139 -139
- package/src/cli/merge-crawl-results.js +128 -128
- package/src/cli/parse-webidl.js +430 -430
- 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 +55 -55
- package/src/lib/nock-server.js +119 -119
- package/src/lib/specs-crawler.js +605 -603
- package/src/lib/util.js +898 -898
- 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/generate-es-dfn-report.sh~ +0 -4
- 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
|
@@ -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
|
+
}
|