reffy 20.0.13 → 20.0.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/README.md +151 -151
- package/index.js +29 -29
- package/package.json +5 -5
- package/reffy.js +324 -324
- package/schemas/browserlib/extract-algorithms.json +52 -52
- package/schemas/browserlib/extract-cssdfn.json +108 -108
- package/schemas/browserlib/extract-dfns.json +90 -90
- package/schemas/browserlib/extract-elements.json +17 -17
- package/schemas/browserlib/extract-events.json +31 -31
- package/schemas/browserlib/extract-headings.json +19 -19
- package/schemas/browserlib/extract-ids.json +7 -7
- package/schemas/browserlib/extract-links.json +12 -12
- package/schemas/browserlib/extract-refs.json +12 -12
- package/schemas/common.json +876 -876
- package/schemas/files/extracts/algorithms.json +12 -12
- package/schemas/files/extracts/css.json +16 -16
- package/schemas/files/extracts/dfns.json +12 -12
- package/schemas/files/extracts/elements.json +12 -12
- package/schemas/files/extracts/events.json +12 -12
- package/schemas/files/extracts/headings.json +12 -12
- package/schemas/files/extracts/ids.json +12 -12
- package/schemas/files/extracts/links.json +12 -12
- package/schemas/files/extracts/refs.json +12 -12
- package/schemas/files/index.json +59 -59
- package/schemas/postprocessing/events.json +50 -50
- package/schemas/postprocessing/idlnames-parsed.json +27 -27
- package/schemas/postprocessing/idlnames.json +17 -17
- package/schemas/postprocessing/idlparsed.json +67 -67
- package/src/browserlib/clone-and-clean.mjs +24 -24
- package/src/browserlib/create-outline.mjs +353 -353
- package/src/browserlib/extract-algorithms.mjs +723 -723
- package/src/browserlib/extract-cddl.mjs +125 -125
- package/src/browserlib/extract-dfns.mjs +1093 -1093
- package/src/browserlib/extract-headings.mjs +76 -76
- package/src/browserlib/extract-ids.mjs +28 -28
- package/src/browserlib/extract-links.mjs +45 -45
- package/src/browserlib/extract-references.mjs +308 -308
- package/src/browserlib/extract-webidl.mjs +89 -89
- package/src/browserlib/get-absolute-url.mjs +29 -29
- package/src/browserlib/get-code-elements.mjs +20 -20
- package/src/browserlib/get-generator.mjs +26 -26
- package/src/browserlib/get-lastmodified-date.mjs +13 -13
- package/src/browserlib/get-revision.mjs +12 -12
- package/src/browserlib/get-title.mjs +14 -14
- package/src/browserlib/informative-selector.mjs +24 -24
- package/src/browserlib/map-ids-to-headings.mjs +173 -173
- package/src/browserlib/reffy.json +85 -85
- package/src/browserlib/trim-spaces.mjs +35 -35
- package/src/cli/check-missing-dfns.js +587 -587
- package/src/cli/merge-crawl-results.js +132 -132
- package/src/cli/parse-webidl.js +447 -447
- package/src/lib/css-grammar-parse-tree.schema.json +109 -109
- package/src/lib/css-grammar-parser.js +440 -440
- package/src/lib/fetch.js +51 -51
- package/src/lib/markdown-report.js +360 -360
- package/src/lib/mock-server.js +218 -218
- package/src/lib/post-processor.js +322 -322
- package/src/lib/throttled-queue.js +129 -129
- package/src/postprocessing/annotate-links.js +41 -41
- package/src/postprocessing/csscomplete.js +48 -48
- package/src/postprocessing/idlnames.js +391 -391
- package/src/postprocessing/idlparsed.js +179 -179
- package/src/postprocessing/patch-dfns.js +51 -51
- package/src/specs/missing-css-rules.json +197 -197
- package/src/specs/spec-equivalents.json +149 -149
- package/src/browserlib/extract-editors.mjs~ +0 -14
- package/src/browserlib/extract-events.mjs~ +0 -3
- package/src/browserlib/generate-es-dfn-report.sh~ +0 -4
- package/src/browserlib/get-revision.mjs~ +0 -7
- package/src/cli/csstree-grammar-check.js +0 -28
- package/src/cli/csstree-grammar-check.js~ +0 -10
- package/src/cli/csstree-grammar-parser.js +0 -11
- package/src/cli/csstree-grammar-parser.js~ +0 -1
- package/src/cli/extract-editors.js~ +0 -38
- package/src/cli/process-specs.js~ +0 -28
- package/src/postprocessing/annotate-links.js~ +0 -8
- package/src/postprocessing/events.js~ +0 -245
|
@@ -1,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
|
}
|