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,361 +1,361 @@
1
- /**
2
- * Helper function to generate a short report of a crawl in GitHub Markdown
3
- * for a spec that features a summary of the crawl result, and details in
4
- * expandable details sections about elements worthy of interest (such as CSS
5
- * properties, exported definitions, Web IDL interfaces, etc.).
6
- *
7
- * The markdown does not contain titles on purpose so that it can be embedded
8
- * as is in a larger Markdown context (e.g., a GitHub issue that looks at a
9
- * spec from various perspectives).
10
- */
11
-
12
- import reffyModules from '../browserlib/reffy.json' with { type: 'json' };
13
- import idlparsed from '../postprocessing/idlparsed.js';
14
-
15
-
16
- /**
17
- * For each module, we need to know how to detect whether Reffy actually
18
- * extracted something from the spec, how to summarize the results when it
19
- * did, and whether/how to highlight specific details.
20
- *
21
- * TODO: reffy.json, browserlib code, and schemas could be refactored to bind
22
- * all the logic linked to a module together: how to extract, whether something
23
- * was extracted, how to summarize, etc. (but note the extraction logic actually
24
- * runs in a browser page, while the rest runs in a Node.js context and that,
25
- * for IDL, interesting info is returned by the idlparsed post-processing
26
- * module)
27
- */
28
- const moduleFunctions = {
29
- algorithms: {
30
- isPresent: isArrayPresent,
31
- summary: arrayInfo
32
- },
33
- cddl: {
34
- isPresent: isArrayPresent,
35
- summary: value => 'found'
36
- },
37
- css: {
38
- isPresent: value => ['properties', 'atrules', 'selectors', 'values']
39
- .find(prop => isArrayPresent(value?.[prop])),
40
- summary: value => ['properties', 'atrules', 'selectors', 'values']
41
- .map(prop => value[prop]?.length > 0 ?
42
- value[prop].length + ' ' + getCSSLabel(prop, value[prop].length) :
43
- null)
44
- .filter(found => found)
45
- .join(', '),
46
- details: value => ['properties', 'atrules', 'selectors']
47
- .map(prop => {
48
- if (!isArrayPresent(value[prop])) {
49
- return null;
50
- }
51
- const types = [
52
- 'css-at-rule',
53
- 'css-descriptor',
54
- 'css-function',
55
- 'css-property',
56
- 'css-selector',
57
- 'css-type',
58
- 'css-value'
59
- ].join(',')
60
- const details = value[prop]
61
- .map(val => '- ' + wrapTerm(val.name, 'css type', val.href) +
62
- ` ([xref search](https://respec.org/xref/?term=${encodeURIComponent(val.name)}&types=${encodeURIComponent(types)}))`
63
- );
64
- if (details.length === 0) {
65
- return null;
66
- }
67
- const report = ['<details>'];
68
- report.push(`<summary>${details.length} CSS ${getCSSLabel(prop, details.length)}</summary>`);
69
- report.push('');
70
- report.push(...details);
71
- report.push('</details>');
72
- return report.join('\n');
73
- })
74
- .filter(details => !!details)
75
- .join('\n')
76
- },
77
- dfns: {
78
- // For dfns, note we make a distinction between terms that are exported by
79
- // default (such as CSS and Web IDL terms) and terms that editors choose to
80
- // export explicitly. The former get reported in other details, the latter
81
- // are the ones most likely to cause duplication issues.
82
- isPresent: isArrayPresent,
83
- summary: value => [
84
- {
85
- access: 'explicitly exported',
86
- dfns: value
87
- .filter(dfn => dfn.access === 'public')
88
- .filter(dfn => dfn.type === 'dfn' || dfn.type === 'cddl')
89
- },
90
- {
91
- access: 'exported by default',
92
- dfns: value
93
- .filter(dfn => dfn.access === 'public')
94
- .filter(dfn => dfn.type !== 'dfn' && dfn.type !== 'cddl')
95
- },
96
- {
97
- access: 'private',
98
- dfns: value
99
- .filter(dfn => dfn.access !== 'public')
100
- }
101
- ]
102
- .map(t => t.dfns.length > 0 ? t.dfns.length + ' ' + t.access : null)
103
- .filter(found => found)
104
- .join(', '),
105
- details: value => {
106
- const details = value
107
- .filter(dfn => dfn.access === 'public')
108
- .filter(dfn => dfn.type === 'dfn' || dfn.type === 'cddl')
109
- .map(dfn => '- ' + wrapTerm(dfn.linkingText[0], dfn.type, dfn.href) +
110
- (dfn.for?.length > 0 ? ' for ' + wrapTerm(dfn.for[0], dfn.type): '') +
111
- `, type ${dfn.type}` +
112
- ` ([xref search](https://respec.org/xref/?term=${encodeURIComponent(dfn.linkingText[0])}))`
113
- );
114
- if (details.length === 0) {
115
- return null;
116
- }
117
- const s = details.length > 1 ? 's' : '';
118
- const report = ['<details>'];
119
- report.push(`<summary>${details.length} explicitly exported term${s}</summary>`);
120
- report.push('');
121
- report.push(...details);
122
- report.push('</details>');
123
- return report.join('\n');
124
- }
125
- },
126
- events: {
127
- isPresent: isArrayPresent,
128
- summary: arrayInfo
129
- },
130
- headings: {
131
- isPresent: isArrayPresent,
132
- summary: arrayInfo
133
- },
134
- idl: {
135
- // Note: For IDL, we're more interested in the info that gets produced by the
136
- // idlparsed post-processing module (which gets run automatically if it
137
- // did not run during crawl)
138
- // For extended names, exclude names that the spec itself defines
139
- // (they will be reported as names defined by the spec already)
140
- isPresent: value => (typeof value === 'string') && value.length > 0,
141
- summary: (value, spec) => {
142
- const parsedIdl = spec.idlparsed;
143
- if (typeof parsedIdl === 'string') {
144
- return 'invalid Web IDL found';
145
- }
146
- const res = [];
147
- const idlNames = Object.keys(parsedIdl.idlNames)
148
- .concat(Object.keys(parsedIdl.idlExtendedNames)
149
- .filter(name => !parsedIdl.idlNames[name]));
150
- if (idlNames.length > 0) {
151
- const s = idlNames.length > 1 ? 's' : '';
152
- res.push(`${idlNames.length} name${s} (or partial${s})`);
153
- }
154
- const globals = Object.keys(parsedIdl.globals);
155
- if (globals.length > 0) {
156
- const s = globals.length > 1 ? 's' : '';
157
- res.push(`${globals.length} global${s}`);
158
- }
159
- return res.join(', ');
160
- },
161
- details: (value, spec) => {
162
- const parsedIdl = spec.idlparsed;
163
- if (typeof parsedIdl === 'string') {
164
- return null;
165
- }
166
-
167
- const report = [];
168
-
169
- const idlNames = Object.keys(parsedIdl.idlNames);
170
- if (idlNames.length > 0) {
171
- const s = idlNames.length > 1 ? 's' : '';
172
- report.push('<details>');
173
- report.push(`<summary>${idlNames.length} Web IDL name${s}</summary>`);
174
- report.push('');
175
- for (const name of idlNames) {
176
- const type = parsedIdl.idlNames[name].type;
177
- report.push('- ' + type + ' ' +
178
- wrapTerm(name, type, parsedIdl.idlNames[name].href) +
179
- ` ([xref search](https://respec.org/xref/?term=${encodeURIComponent(name)}&types=_IDL_))`);
180
- }
181
- report.push('</details>');
182
- }
183
-
184
- const idlExtendedNames = Object.keys(parsedIdl.idlExtendedNames)
185
- .filter(name => !parsedIdl.idlNames[name]);
186
- if (idlExtendedNames.length > 0) {
187
- const s = idlExtendedNames.length > 1 ? 's' : '';
188
- report.push('<details>');
189
- report.push(`<summary>${idlExtendedNames.length} extended Web IDL name${s}</summary>`);
190
- report.push('');
191
- for (const name of idlExtendedNames) {
192
- const type = parsedIdl.idlExtendedNames[name][0].type;
193
- report.push('- ' + type + ' ' +
194
- wrapTerm(name, type, parsedIdl.idlExtendedNames[name][0].href) +
195
- ` ([xref search](https://respec.org/xref/?term=${encodeURIComponent(name)}&types=_IDL_))`);
196
- }
197
- report.push('</details>');
198
- }
199
-
200
- const globals = Object.keys(parsedIdl.globals);
201
- if (globals.length > 0) {
202
- const s = globals.length > 1 ? 's' : '';
203
- report.push('<details>');
204
- report.push(`<summary>${globals.length} Web IDL global${s}</summary>`);
205
- report.push('');
206
- for (const glob of globals) {
207
- report.push(`- \`${glob}\``);
208
- }
209
- report.push('</details>');
210
- }
211
-
212
- return report.join('\n');
213
- }
214
- },
215
- ids: {
216
- isPresent: isArrayPresent,
217
- summary: arrayInfo
218
- },
219
- links: {
220
- isPresent: value =>
221
- isArrayPresent(Object.keys(value?.rawlinks ?? {})) ||
222
- isArrayPresent(Object.keys(value?.autolinks ?? {})),
223
- summary: value => ['rawlinks', 'autolinks']
224
- .map(prop => Object.keys(value[prop]).length > 0 ?
225
- Object.keys(value[prop]).length + ' ' + prop :
226
- null)
227
- .filter(found => found)
228
- .join(', ')
229
- },
230
- refs: {
231
- isPresent: value =>
232
- isArrayPresent(value?.normative) ||
233
- isArrayPresent(value?.informative),
234
- summary: value => ['normative', 'informative']
235
- .map(prop => value[prop].length > 0 ?
236
- value[prop].length + ' ' + prop :
237
- null)
238
- .filter(found => found)
239
- .join(', ')
240
- }
241
- };
242
-
243
-
244
- /**
245
- * Return true if the given value is an array that contains at least one item.
246
- */
247
- function isArrayPresent(value) {
248
- return Array.isArray(value) && value.length > 0;
249
- }
250
-
251
-
252
- /**
253
- * Return the number of items found in the array
254
- */
255
- function arrayInfo(value) {
256
- return value.length + ' found';
257
- }
258
-
259
- function wrapTerm(term, type, href) {
260
- if (type === 'abstract-op' || type === 'dfn') {
261
- if (href) {
262
- return `[${term}](${href})`;
263
- }
264
- else {
265
- return `"${term}"`;
266
- }
267
- }
268
- const res = '`' + term + '`';
269
- if (href) {
270
- return `[${res}](${href})`;
271
- }
272
- else {
273
- return res;
274
- }
275
- }
276
-
277
- function getCSSLabel(prop, nb) {
278
- switch (prop) {
279
- case 'atrules':
280
- return nb > 1 ? 'at-rules' : 'at-rule';
281
- case 'properties':
282
- return nb > 1 ? 'properties' : 'property';
283
- case 'selectors':
284
- return nb > 1 ? 'selectors' : 'selector';
285
- case 'values':
286
- return nb > 1 ? 'values': 'value';
287
- }
288
- }
289
-
290
-
291
- /**
292
- * Return a Markdown string that summarizes the given spec crawl results
293
- */
294
- export async function generateSpecReport(specResult) {
295
- // Start report with a summary on spec metadata, adding URLs as needed
296
- const summary = [];
297
- for (const mod of reffyModules) {
298
- if (!mod.metadata) {
299
- continue;
300
- }
301
- if (specResult[mod.property]) {
302
- summary.push(`- ${mod.label}: ${specResult[mod.property]}`);
303
- }
304
- }
305
- summary.push(`- Canonical URL: [${specResult.url}](${specResult.url})`);
306
- if (specResult.crawled && specResult.crawled !== specResult.url) {
307
- summary.push(`- Crawled URL: [${specResult.crawled}](${specResult.crawled})`);
308
- }
309
-
310
- // If the spec defines IDL, run the idlparsed post-processing module
311
- if (specResult.idl && !specResult.idlparsed) {
312
- await idlparsed.run(specResult);
313
- }
314
-
315
- // Add summary of extracts found and not found
316
- const extractModules = reffyModules
317
- .filter(mod => !mod.metadata && moduleFunctions[mod.property])
318
- .map(mod => Object.assign(mod, moduleFunctions[mod.property]));
319
- const extractsSummary = [];
320
- const missingSummary = [];
321
- for (const mod of extractModules) {
322
- const value = specResult[mod.property];
323
- if (mod.isPresent(value)) {
324
- extractsSummary.push(` - ${mod.label}: ${mod.summary(value, specResult)}`);
325
- }
326
- else {
327
- missingSummary.push(mod.label);
328
- }
329
- }
330
- if (extractsSummary.length > 0) {
331
- extractsSummary.sort();
332
- summary.push(`- Spec defines:`);
333
- summary.push(...extractsSummary);
334
- }
335
- if (missingSummary.length > 0) {
336
- missingSummary.sort();
337
- summary.push(`- No ${missingSummary.join(', ')} definitions found`);
338
- }
339
-
340
- // End of summary, look at possible details of interest
341
- const details = [];
342
- for (const mod of extractModules) {
343
- const value = specResult[mod.property];
344
- if (!mod.details || !mod.isPresent(value)) {
345
- continue;
346
- }
347
- const modDetails = mod.details(value, specResult);
348
- if (modDetails) {
349
- details.push(modDetails);
350
- }
351
- }
352
-
353
- const report = [];
354
- report.push('Crawl summary:');
355
- report.push(...summary);
356
- if (details.length > 0) {
357
- report.push('');
358
- report.push(...details);
359
- }
360
- return report.join('\n');
1
+ /**
2
+ * Helper function to generate a short report of a crawl in GitHub Markdown
3
+ * for a spec that features a summary of the crawl result, and details in
4
+ * expandable details sections about elements worthy of interest (such as CSS
5
+ * properties, exported definitions, Web IDL interfaces, etc.).
6
+ *
7
+ * The markdown does not contain titles on purpose so that it can be embedded
8
+ * as is in a larger Markdown context (e.g., a GitHub issue that looks at a
9
+ * spec from various perspectives).
10
+ */
11
+
12
+ import reffyModules from '../browserlib/reffy.json' with { type: 'json' };
13
+ import idlparsed from '../postprocessing/idlparsed.js';
14
+
15
+
16
+ /**
17
+ * For each module, we need to know how to detect whether Reffy actually
18
+ * extracted something from the spec, how to summarize the results when it
19
+ * did, and whether/how to highlight specific details.
20
+ *
21
+ * TODO: reffy.json, browserlib code, and schemas could be refactored to bind
22
+ * all the logic linked to a module together: how to extract, whether something
23
+ * was extracted, how to summarize, etc. (but note the extraction logic actually
24
+ * runs in a browser page, while the rest runs in a Node.js context and that,
25
+ * for IDL, interesting info is returned by the idlparsed post-processing
26
+ * module)
27
+ */
28
+ const moduleFunctions = {
29
+ algorithms: {
30
+ isPresent: isArrayPresent,
31
+ summary: arrayInfo
32
+ },
33
+ cddl: {
34
+ isPresent: isArrayPresent,
35
+ summary: value => 'found'
36
+ },
37
+ css: {
38
+ isPresent: value => ['properties', 'atrules', 'selectors', 'values']
39
+ .find(prop => isArrayPresent(value?.[prop])),
40
+ summary: value => ['properties', 'atrules', 'selectors', 'values']
41
+ .map(prop => value[prop]?.length > 0 ?
42
+ value[prop].length + ' ' + getCSSLabel(prop, value[prop].length) :
43
+ null)
44
+ .filter(found => found)
45
+ .join(', '),
46
+ details: value => ['properties', 'atrules', 'selectors']
47
+ .map(prop => {
48
+ if (!isArrayPresent(value[prop])) {
49
+ return null;
50
+ }
51
+ const types = [
52
+ 'css-at-rule',
53
+ 'css-descriptor',
54
+ 'css-function',
55
+ 'css-property',
56
+ 'css-selector',
57
+ 'css-type',
58
+ 'css-value'
59
+ ].join(',')
60
+ const details = value[prop]
61
+ .map(val => '- ' + wrapTerm(val.name, 'css type', val.href) +
62
+ ` ([xref search](https://respec.org/xref/?term=${encodeURIComponent(val.name)}&types=${encodeURIComponent(types)}))`
63
+ );
64
+ if (details.length === 0) {
65
+ return null;
66
+ }
67
+ const report = ['<details>'];
68
+ report.push(`<summary>${details.length} CSS ${getCSSLabel(prop, details.length)}</summary>`);
69
+ report.push('');
70
+ report.push(...details);
71
+ report.push('</details>');
72
+ return report.join('\n');
73
+ })
74
+ .filter(details => !!details)
75
+ .join('\n')
76
+ },
77
+ dfns: {
78
+ // For dfns, note we make a distinction between terms that are exported by
79
+ // default (such as CSS and Web IDL terms) and terms that editors choose to
80
+ // export explicitly. The former get reported in other details, the latter
81
+ // are the ones most likely to cause duplication issues.
82
+ isPresent: isArrayPresent,
83
+ summary: value => [
84
+ {
85
+ access: 'explicitly exported',
86
+ dfns: value
87
+ .filter(dfn => dfn.access === 'public')
88
+ .filter(dfn => dfn.type === 'dfn' || dfn.type === 'cddl')
89
+ },
90
+ {
91
+ access: 'exported by default',
92
+ dfns: value
93
+ .filter(dfn => dfn.access === 'public')
94
+ .filter(dfn => dfn.type !== 'dfn' && dfn.type !== 'cddl')
95
+ },
96
+ {
97
+ access: 'private',
98
+ dfns: value
99
+ .filter(dfn => dfn.access !== 'public')
100
+ }
101
+ ]
102
+ .map(t => t.dfns.length > 0 ? t.dfns.length + ' ' + t.access : null)
103
+ .filter(found => found)
104
+ .join(', '),
105
+ details: value => {
106
+ const details = value
107
+ .filter(dfn => dfn.access === 'public')
108
+ .filter(dfn => dfn.type === 'dfn' || dfn.type === 'cddl')
109
+ .map(dfn => '- ' + wrapTerm(dfn.linkingText[0], dfn.type, dfn.href) +
110
+ (dfn.for?.length > 0 ? ' for ' + wrapTerm(dfn.for[0], dfn.type): '') +
111
+ `, type ${dfn.type}` +
112
+ ` ([xref search](https://respec.org/xref/?term=${encodeURIComponent(dfn.linkingText[0])}))`
113
+ );
114
+ if (details.length === 0) {
115
+ return null;
116
+ }
117
+ const s = details.length > 1 ? 's' : '';
118
+ const report = ['<details>'];
119
+ report.push(`<summary>${details.length} explicitly exported term${s}</summary>`);
120
+ report.push('');
121
+ report.push(...details);
122
+ report.push('</details>');
123
+ return report.join('\n');
124
+ }
125
+ },
126
+ events: {
127
+ isPresent: isArrayPresent,
128
+ summary: arrayInfo
129
+ },
130
+ headings: {
131
+ isPresent: isArrayPresent,
132
+ summary: arrayInfo
133
+ },
134
+ idl: {
135
+ // Note: For IDL, we're more interested in the info that gets produced by the
136
+ // idlparsed post-processing module (which gets run automatically if it
137
+ // did not run during crawl)
138
+ // For extended names, exclude names that the spec itself defines
139
+ // (they will be reported as names defined by the spec already)
140
+ isPresent: value => (typeof value === 'string') && value.length > 0,
141
+ summary: (value, spec) => {
142
+ const parsedIdl = spec.idlparsed;
143
+ if (typeof parsedIdl === 'string') {
144
+ return 'invalid Web IDL found';
145
+ }
146
+ const res = [];
147
+ const idlNames = Object.keys(parsedIdl.idlNames)
148
+ .concat(Object.keys(parsedIdl.idlExtendedNames)
149
+ .filter(name => !parsedIdl.idlNames[name]));
150
+ if (idlNames.length > 0) {
151
+ const s = idlNames.length > 1 ? 's' : '';
152
+ res.push(`${idlNames.length} name${s} (or partial${s})`);
153
+ }
154
+ const globals = Object.keys(parsedIdl.globals);
155
+ if (globals.length > 0) {
156
+ const s = globals.length > 1 ? 's' : '';
157
+ res.push(`${globals.length} global${s}`);
158
+ }
159
+ return res.join(', ');
160
+ },
161
+ details: (value, spec) => {
162
+ const parsedIdl = spec.idlparsed;
163
+ if (typeof parsedIdl === 'string') {
164
+ return null;
165
+ }
166
+
167
+ const report = [];
168
+
169
+ const idlNames = Object.keys(parsedIdl.idlNames);
170
+ if (idlNames.length > 0) {
171
+ const s = idlNames.length > 1 ? 's' : '';
172
+ report.push('<details>');
173
+ report.push(`<summary>${idlNames.length} Web IDL name${s}</summary>`);
174
+ report.push('');
175
+ for (const name of idlNames) {
176
+ const type = parsedIdl.idlNames[name].type;
177
+ report.push('- ' + type + ' ' +
178
+ wrapTerm(name, type, parsedIdl.idlNames[name].href) +
179
+ ` ([xref search](https://respec.org/xref/?term=${encodeURIComponent(name)}&types=_IDL_))`);
180
+ }
181
+ report.push('</details>');
182
+ }
183
+
184
+ const idlExtendedNames = Object.keys(parsedIdl.idlExtendedNames)
185
+ .filter(name => !parsedIdl.idlNames[name]);
186
+ if (idlExtendedNames.length > 0) {
187
+ const s = idlExtendedNames.length > 1 ? 's' : '';
188
+ report.push('<details>');
189
+ report.push(`<summary>${idlExtendedNames.length} extended Web IDL name${s}</summary>`);
190
+ report.push('');
191
+ for (const name of idlExtendedNames) {
192
+ const type = parsedIdl.idlExtendedNames[name][0].type;
193
+ report.push('- ' + type + ' ' +
194
+ wrapTerm(name, type, parsedIdl.idlExtendedNames[name][0].href) +
195
+ ` ([xref search](https://respec.org/xref/?term=${encodeURIComponent(name)}&types=_IDL_))`);
196
+ }
197
+ report.push('</details>');
198
+ }
199
+
200
+ const globals = Object.keys(parsedIdl.globals);
201
+ if (globals.length > 0) {
202
+ const s = globals.length > 1 ? 's' : '';
203
+ report.push('<details>');
204
+ report.push(`<summary>${globals.length} Web IDL global${s}</summary>`);
205
+ report.push('');
206
+ for (const glob of globals) {
207
+ report.push(`- \`${glob}\``);
208
+ }
209
+ report.push('</details>');
210
+ }
211
+
212
+ return report.join('\n');
213
+ }
214
+ },
215
+ ids: {
216
+ isPresent: isArrayPresent,
217
+ summary: arrayInfo
218
+ },
219
+ links: {
220
+ isPresent: value =>
221
+ isArrayPresent(Object.keys(value?.rawlinks ?? {})) ||
222
+ isArrayPresent(Object.keys(value?.autolinks ?? {})),
223
+ summary: value => ['rawlinks', 'autolinks']
224
+ .map(prop => Object.keys(value[prop]).length > 0 ?
225
+ Object.keys(value[prop]).length + ' ' + prop :
226
+ null)
227
+ .filter(found => found)
228
+ .join(', ')
229
+ },
230
+ refs: {
231
+ isPresent: value =>
232
+ isArrayPresent(value?.normative) ||
233
+ isArrayPresent(value?.informative),
234
+ summary: value => ['normative', 'informative']
235
+ .map(prop => value[prop].length > 0 ?
236
+ value[prop].length + ' ' + prop :
237
+ null)
238
+ .filter(found => found)
239
+ .join(', ')
240
+ }
241
+ };
242
+
243
+
244
+ /**
245
+ * Return true if the given value is an array that contains at least one item.
246
+ */
247
+ function isArrayPresent(value) {
248
+ return Array.isArray(value) && value.length > 0;
249
+ }
250
+
251
+
252
+ /**
253
+ * Return the number of items found in the array
254
+ */
255
+ function arrayInfo(value) {
256
+ return value.length + ' found';
257
+ }
258
+
259
+ function wrapTerm(term, type, href) {
260
+ if (type === 'abstract-op' || type === 'dfn') {
261
+ if (href) {
262
+ return `[${term}](${href})`;
263
+ }
264
+ else {
265
+ return `"${term}"`;
266
+ }
267
+ }
268
+ const res = '`' + term + '`';
269
+ if (href) {
270
+ return `[${res}](${href})`;
271
+ }
272
+ else {
273
+ return res;
274
+ }
275
+ }
276
+
277
+ function getCSSLabel(prop, nb) {
278
+ switch (prop) {
279
+ case 'atrules':
280
+ return nb > 1 ? 'at-rules' : 'at-rule';
281
+ case 'properties':
282
+ return nb > 1 ? 'properties' : 'property';
283
+ case 'selectors':
284
+ return nb > 1 ? 'selectors' : 'selector';
285
+ case 'values':
286
+ return nb > 1 ? 'values': 'value';
287
+ }
288
+ }
289
+
290
+
291
+ /**
292
+ * Return a Markdown string that summarizes the given spec crawl results
293
+ */
294
+ export async function generateSpecReport(specResult) {
295
+ // Start report with a summary on spec metadata, adding URLs as needed
296
+ const summary = [];
297
+ for (const mod of reffyModules) {
298
+ if (!mod.metadata) {
299
+ continue;
300
+ }
301
+ if (specResult[mod.property]) {
302
+ summary.push(`- ${mod.label}: ${specResult[mod.property]}`);
303
+ }
304
+ }
305
+ summary.push(`- Canonical URL: [${specResult.url}](${specResult.url})`);
306
+ if (specResult.crawled && specResult.crawled !== specResult.url) {
307
+ summary.push(`- Crawled URL: [${specResult.crawled}](${specResult.crawled})`);
308
+ }
309
+
310
+ // If the spec defines IDL, run the idlparsed post-processing module
311
+ if (specResult.idl && !specResult.idlparsed) {
312
+ await idlparsed.run(specResult);
313
+ }
314
+
315
+ // Add summary of extracts found and not found
316
+ const extractModules = reffyModules
317
+ .filter(mod => !mod.metadata && moduleFunctions[mod.property])
318
+ .map(mod => Object.assign(mod, moduleFunctions[mod.property]));
319
+ const extractsSummary = [];
320
+ const missingSummary = [];
321
+ for (const mod of extractModules) {
322
+ const value = specResult[mod.property];
323
+ if (mod.isPresent(value)) {
324
+ extractsSummary.push(` - ${mod.label}: ${mod.summary(value, specResult)}`);
325
+ }
326
+ else {
327
+ missingSummary.push(mod.label);
328
+ }
329
+ }
330
+ if (extractsSummary.length > 0) {
331
+ extractsSummary.sort();
332
+ summary.push(`- Spec defines:`);
333
+ summary.push(...extractsSummary);
334
+ }
335
+ if (missingSummary.length > 0) {
336
+ missingSummary.sort();
337
+ summary.push(`- No ${missingSummary.join(', ')} definitions found`);
338
+ }
339
+
340
+ // End of summary, look at possible details of interest
341
+ const details = [];
342
+ for (const mod of extractModules) {
343
+ const value = specResult[mod.property];
344
+ if (!mod.details || !mod.isPresent(value)) {
345
+ continue;
346
+ }
347
+ const modDetails = mod.details(value, specResult);
348
+ if (modDetails) {
349
+ details.push(modDetails);
350
+ }
351
+ }
352
+
353
+ const report = [];
354
+ report.push('Crawl summary:');
355
+ report.push(...summary);
356
+ if (details.length > 0) {
357
+ report.push('');
358
+ report.push(...details);
359
+ }
360
+ return report.join('\n');
361
361
  }