reffy 6.2.0 → 6.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +158 -158
  3. package/index.js +11 -11
  4. package/package.json +53 -53
  5. package/reffy.js +248 -248
  6. package/src/browserlib/canonicalize-url.mjs +50 -50
  7. package/src/browserlib/create-outline.mjs +352 -352
  8. package/src/browserlib/extract-cssdfn.mjs +319 -319
  9. package/src/browserlib/extract-dfns.mjs +686 -686
  10. package/src/browserlib/extract-elements.mjs +205 -205
  11. package/src/browserlib/extract-headings.mjs +48 -48
  12. package/src/browserlib/extract-ids.mjs +28 -28
  13. package/src/browserlib/extract-links.mjs +28 -28
  14. package/src/browserlib/extract-references.mjs +203 -203
  15. package/src/browserlib/extract-webidl.mjs +134 -134
  16. package/src/browserlib/get-absolute-url.mjs +21 -21
  17. package/src/browserlib/get-generator.mjs +26 -26
  18. package/src/browserlib/get-lastmodified-date.mjs +13 -13
  19. package/src/browserlib/get-title.mjs +11 -11
  20. package/src/browserlib/informative-selector.mjs +16 -16
  21. package/src/browserlib/map-ids-to-headings.mjs +136 -136
  22. package/src/browserlib/reffy.json +53 -53
  23. package/src/cli/check-missing-dfns.js +609 -609
  24. package/src/cli/generate-idlnames.js +430 -430
  25. package/src/cli/generate-idlparsed.js +139 -139
  26. package/src/cli/merge-crawl-results.js +128 -128
  27. package/src/cli/parse-webidl.js +430 -430
  28. package/src/lib/css-grammar-parse-tree.schema.json +109 -109
  29. package/src/lib/css-grammar-parser.js +440 -440
  30. package/src/lib/fetch.js +55 -55
  31. package/src/lib/nock-server.js +119 -119
  32. package/src/lib/specs-crawler.js +605 -603
  33. package/src/lib/util.js +898 -898
  34. package/src/specs/missing-css-rules.json +197 -197
  35. package/src/specs/spec-equivalents.json +149 -149
  36. package/src/browserlib/extract-editors.mjs~ +0 -14
  37. package/src/browserlib/generate-es-dfn-report.sh~ +0 -4
  38. package/src/cli/csstree-grammar-check.js +0 -28
  39. package/src/cli/csstree-grammar-check.js~ +0 -10
  40. package/src/cli/csstree-grammar-parser.js +0 -11
  41. package/src/cli/csstree-grammar-parser.js~ +0 -1
  42. package/src/cli/extract-editors.js~ +0 -38
  43. package/src/cli/process-specs.js~ +0 -28
@@ -1,686 +1,686 @@
1
- import extractWebIdl from './extract-webidl.mjs';
2
- import informativeSelector from './informative-selector.mjs';
3
- import {parse} from "../../node_modules/webidl2/index.js";
4
- /**
5
- * Extract definitions in the spec that follow the "Definitions data model":
6
- * https://tabatkins.github.io/bikeshed/#dfn-contract
7
- *
8
- * Each definition returned by the function will have the following properties:
9
- * - id: The local ID in the DOM. Should be unique within a spec page.
10
- * - href: The absolute URL to the definition.
11
- * - linkingText: List of linking phrases for references.
12
- * - localLinkingText: List of linking phrases for local references only.
13
- * - type: The definition type. One of the values in
14
- * https://tabatkins.github.io/bikeshed/#dfn-types
15
- * - for: The list of namespaces for the definition
16
- * - access: "public" when definition can be referenced by other specifications,
17
- * "private" when it should be viewed as a local definition.
18
- * - informative: true when definition appears in an informative section,
19
- * false if it is normative
20
- * - heading: Heading under which the term is to be found. An object with "id",
21
- * "title", and "number" properties
22
- * - definedIn: An indication of where the definition appears in the spec. Value
23
- * can be one of "dt", "pre", "table", "heading", "note", "example", or
24
- * "prose" (last one indicates that definition appears in the main body of
25
- * the spec)
26
- *
27
- * @function
28
- * @public
29
- * @return {Array(Object)} An Array of definitions
30
- */
31
-
32
- function normalize(str) {
33
- return str.trim().replace(/\s+/g, ' ');
34
- }
35
-
36
- // Valid types defined in https://tabatkins.github.io/bikeshed/#dfn-types
37
- // (+ "namespace" and "event" which are not yet in the doc)
38
- function hasValidType(el) {
39
- const validDfnTypes = [
40
- // CSS types
41
- 'property',
42
- 'descriptor',
43
- 'value',
44
- 'type',
45
- 'at-rule',
46
- 'function',
47
- 'selector',
48
-
49
- // Web IDL types
50
- 'namespace',
51
- 'interface',
52
- 'constructor',
53
- 'method',
54
- 'argument',
55
- 'attribute',
56
- 'callback',
57
- 'dictionary',
58
- 'dict-member',
59
- 'enum',
60
- 'enum-value',
61
- 'exception',
62
- 'const',
63
- 'typedef',
64
- 'stringifier',
65
- 'serializer',
66
- 'iterator',
67
- 'maplike',
68
- 'setlike',
69
- 'extended-attribute',
70
- 'event',
71
-
72
- // Element types
73
- 'element',
74
- 'element-state',
75
- 'element-attr',
76
- 'attr-value',
77
-
78
-
79
- // URL scheme
80
- 'scheme',
81
-
82
- // HTTP header
83
- 'http-header',
84
-
85
- // Grammar type
86
- 'grammar',
87
-
88
- // "English" terms
89
- 'abstract-op',
90
- 'dfn'
91
- ];
92
-
93
- const type = el.getAttribute('data-dfn-type') ?? 'dfn';
94
- const isValid = validDfnTypes.includes(type);
95
- if (!isValid) {
96
- console.warn('[reffy]', `"${type}" is an invalid dfn type for "${normalize(el.textContent)}"`);
97
- }
98
- return isValid;
99
- }
100
-
101
-
102
- function definitionMapper(el, idToHeading) {
103
- let definedIn = 'prose';
104
- const enclosingEl = el.closest('dt,pre,table,h1,h2,h3,h4,h5,h6,.note,.example') || el;
105
- switch (enclosingEl.nodeName) {
106
- case 'DT':
107
- case 'PRE':
108
- case 'TABLE':
109
- definedIn = enclosingEl.nodeName.toLowerCase();
110
- break;
111
- case 'H1':
112
- case 'H2':
113
- case 'H3':
114
- case 'H4':
115
- case 'H5':
116
- case 'H6':
117
- definedIn = 'heading';
118
- break;
119
- default:
120
- if (enclosingEl.classList.contains('note')) {
121
- definedIn = 'note';
122
- }
123
- else if (enclosingEl.classList.contains('example')) {
124
- definedIn = 'example';
125
- }
126
- break;
127
- }
128
-
129
- // Compute the absolute URL with fragment
130
- // (Note the crawler merges pages of a multi-page spec in the first page
131
- // to ease parsing logic, and we want to get back to the URL of the page)
132
- const page = el.closest('[data-reffy-page]')?.getAttribute('data-reffy-page');
133
- const url = new URL(page ?? window.location.href);
134
- url.hash = '#' + el.getAttribute('id');
135
- const href = url.toString();
136
-
137
- return {
138
- // ID is the id attribute
139
- // (ID may not be unique in a multi-page spec)
140
- id: el.getAttribute('id'),
141
-
142
- // Absolute URL with fragment
143
- href,
144
-
145
- // Linking text is given by the data-lt attribute if present, or it is the
146
- // textual content
147
- linkingText: el.hasAttribute('data-lt') ?
148
- el.getAttribute('data-lt').split('|').map(normalize) :
149
- [normalize(el.textContent)],
150
-
151
- // Additional linking text can be defined for local references
152
- localLinkingText: el.getAttribute('data-local-lt') ?
153
- el.getAttribute('data-local-lt').split('|').map(normalize) :
154
- [],
155
-
156
- // Link type must be specified, or it is "dfn"
157
- type: el.getAttribute('data-dfn-type') || 'dfn',
158
-
159
- // Definition may be namespaced to other constructs. Note the list is not
160
- // purely comma-separated due to function parameters. For instance,
161
- // attribute value may be "method(foo,bar), method()"
162
- for: el.getAttribute('data-dfn-for') ?
163
- el.getAttribute('data-dfn-for').split(/,(?![^\(]*\))/).map(normalize) :
164
- [],
165
-
166
- // Definition is public if explicitly marked as exportable or if export has
167
- // not been explicitly disallowed and its type is not "dfn"
168
- access: (el.hasAttribute('data-export') ||
169
- (!el.hasAttribute('data-noexport') &&
170
- el.hasAttribute('data-dfn-type') &&
171
- el.getAttribute('data-dfn-type') !== 'dfn')) ?
172
- 'public' : 'private',
173
-
174
- // Whether the term is defined in a normative/informative section
175
- informative: !!el.closest(informativeSelector),
176
-
177
- // Heading under which the term is to be found
178
- heading: idToHeading[href],
179
-
180
- // Enclosing element under which the definition appears. Value can be one of
181
- // "dt", "pre", "table", "heading", "note", "example", or "prose" (last one
182
- // indicates that definition appears in the main body of the specification)
183
- definedIn
184
- };
185
- }
186
-
187
- export default function (spec, idToHeading = {}) {
188
- const definitionsSelector = [
189
- // re data-lt, see https://github.com/w3c/reffy/issues/336#issuecomment-650339747
190
- 'dfn[id]:not([data-lt=""])',
191
- 'h2[id][data-dfn-type]:not([data-lt=""])',
192
- 'h3[id][data-dfn-type]:not([data-lt=""])',
193
- 'h4[id][data-dfn-type]:not([data-lt=""])',
194
- 'h5[id][data-dfn-type]:not([data-lt=""])',
195
- 'h6[id][data-dfn-type]:not([data-lt=""])'
196
- ].join(',');
197
-
198
- let extraDefinitions = [];
199
- const shortname = (typeof spec === 'string') ? spec : spec.shortname;
200
- switch (shortname) {
201
- case "html":
202
- preProcessHTML();
203
- break;
204
- case "ecmascript":
205
- preProcessEcmascript();
206
- break;
207
- case "SVG2":
208
- preProcessSVG2();
209
- break;
210
- }
211
-
212
- return [...document.querySelectorAll(definitionsSelector)]
213
- .map(node => {
214
- // 2021-06-21: Temporary preprocessing of invalid "idl" dfn type (used for
215
- // internal slots) while fix for https://github.com/w3c/respec/issues/3644
216
- // propagates to all EDs and /TR specs. To be dropped once crawls no
217
- // longer produce warnings.
218
- if (node.getAttribute('data-dfn-type') === 'idl') {
219
- const linkingText = node.hasAttribute('data-lt') ?
220
- node.getAttribute('data-lt').split('|').map(normalize) :
221
- [normalize(node.textContent)];
222
- node.setAttribute('data-dfn-type', linkingText[0].endsWith(')') ? 'method' : 'attribute');
223
- console.warn('[reffy]', `Fixed invalid "idl" dfn type "${normalize(node.textContent)}"`);
224
- }
225
- return node;
226
- })
227
- .filter(hasValidType)
228
- .map(node => definitionMapper(node, idToHeading));
229
- }
230
-
231
- function preProcessEcmascript() {
232
- // Skip elements in sections marked as legacy
233
- const legacySectionFilter = n => !n.closest("[legacy]");
234
-
235
- const wrapWithDfn = (el) => {
236
- // wrap with a dfn
237
- const dfn = document.createElement("dfn");
238
- for (let child of [...el.childNodes]) {
239
- dfn.appendChild(child);
240
- }
241
- el.appendChild(dfn);
242
- // set id
243
- dfn.setAttribute("id", el.parentNode.getAttribute("id"));
244
- dfn.dataset.ltNodefault = true;
245
- return dfn;
246
- };
247
-
248
- const cleanMethodName = (name) => {
249
- return name.replace(/\[/g, '')
250
- .replace(/\]/g, '') // removing brackets used to mark optional args
251
- .replace(/ \( */, '(')
252
- .replace(/ *\)/, ')')
253
- .replace(/ *,/g, ','); // trimming internal spaces
254
- };
255
-
256
- let definitionNames = new Set();
257
- let idlTypes = {};
258
-
259
- // We find the list of abstract methods
260
- // to help with scoping abstract operations
261
- let abstractMethods = {};
262
- const abstractMethodCaptions = [...document.querySelectorAll("figcaption")]
263
- .filter(el => el.textContent.match(/(abstract|additional) method/i) && el.parentNode.querySelector("emu-xref"));
264
- for (const figcaption of abstractMethodCaptions) {
265
- const scope = figcaption.querySelector("emu-xref").textContent;
266
- const table = figcaption.parentNode.querySelector("tbody");
267
- for (const td of table.querySelectorAll("tr td:first-child")) {
268
- // We only consider the name of the method, not the potential parameters
269
- // as they're not necessarily consistently named across
270
- // the list and the definition
271
- const methodName = td.textContent.trim().split('(')[0];
272
- abstractMethods[methodName] = scope;
273
- }
274
- }
275
-
276
- const sectionNumberRegExp = /^([A-Z]\.)?[0-9\.]+ /;
277
- [...document.querySelectorAll("h1")]
278
- .filter(legacySectionFilter)
279
- .forEach(el => {
280
- let dfnName = el.textContent.replace(sectionNumberRegExp, '').trim() ;// remove section number
281
- const dfnId = el.parentNode.id;
282
- if (dfnId.match(/-objects?$/) && dfnName.match(/ Objects?$/)) {
283
-
284
- // Skip headings that look like object definitions, but aren't
285
- const notObjectIds = ["sec-global-object", "sec-fundamental-objects", "sec-waiterlist-objects"];
286
- if (notObjectIds.includes(dfnId)) return;
287
-
288
- // only keep ids that match a credible pattern for object names
289
- // i.e. a single word
290
- // there are exceptions to that simple rule
291
- // RegExp includes its expansion (regular expansion) in the id
292
- // WeakRef is translated into weak-ref in the id
293
- const objectsIdsExceptions = ["sec-regexp-regular-expression-objects", "sec-weak-ref-objects", "sec-aggregate-error-objects", "sec-finalization-registry-objects", "sec-async-function-objects"];
294
-
295
- if (!dfnId.match(/sec-[a-z]+-objects?/)
296
- && !objectsIdsExceptions.includes(dfnId)
297
- ) return;
298
- const dfn = wrapWithDfn(el);
299
- // set data-lt
300
- dfnName = dfnName
301
- .replace(/^The /, '')
302
- .replace(/ Objects?$/, '')
303
- // regexp def includes "(Regular Expression)"
304
- .replace(/ \([^\)]*\)/, '') ;
305
- dfn.dataset.lt = dfnName;
306
-
307
- // FIXME
308
- // These interfaces are also defined in WebIDL, which in general is
309
- // the prefered source for these terms
310
- // Because bikeshed does not yet support spec-specific imports,
311
- // we hide these terms as not exported
312
- // cf https://github.com/w3c/reffy/pull/732#issuecomment-925950287
313
- const exportExceptions = [ "Promise", "DataView", "ArrayBuffer" ];
314
- if (exportExceptions.includes(dfnName)) {
315
- dfn.dataset.noexport = "";
316
- }
317
-
318
- if (dfnName.match(/^[A-Z]/)) {
319
- // set dfn-type
320
- if (dfnName.match(/Error$/)) {
321
- dfn.dataset.dfnType = "exception";
322
- } else if (!el.parentNode.querySelector('[id$="constructor"]')) {
323
- // Objects without constructors match to the namespace type
324
- dfn.dataset.dfnType = "namespace";
325
- } else {
326
- dfn.dataset.dfnType = "interface";
327
- }
328
- // We keep track of types associated with a name
329
- // to associate the same type to the relevant intrinsic object
330
- // à la %Math%
331
- idlTypes[dfnName] = dfn.dataset.dfnType;
332
- }
333
- definitionNames.add(dfnName);
334
- } else if (dfnId.match(/-[a-z]+error$/) && !dfnName.match(/\(/)) {
335
- const dfn = wrapWithDfn(el);
336
- dfn.dataset.lt = dfnName;
337
- dfn.dataset.dfnType = "exception";
338
- definitionNames.add(dfnName);
339
- idlTypes[dfnName] = dfn.dataset.dfnType;
340
- } else if (dfnId.match(/[-\.]prototype[-\.]/)) {
341
- // methods and attributes on objects
342
-
343
- // Skip headings with a space and no parenthesis
344
- // (they mention prototype but aren't a prototype property def)
345
- // with the exception of "set " and "get " headings
346
- // (which describe setters and getters)
347
- if (!dfnName.match(/\(/) && (dfnName.match(/ /) && !dfnName.match(/^[gs]et /))) return;
348
-
349
- // Skip unscoped internal methods à la [[SetPrototypeOf]](V)
350
- if (dfnName.match(/\[\[/)) return;
351
-
352
- // Skip symbol-based property definitions;
353
- // not clear they're useful as externally referenceable names
354
- if (dfnName.match(/@@/)) return;
355
-
356
- // Skip .constructor as that cannot be considered as an attribute
357
- if (dfnName.match(/\.constructor$/)) return;
358
-
359
- const dfn = wrapWithDfn(el);
360
- // set definition scope
361
- dfn.dataset.dfnFor = dfnName.replace(/\.prototype\..*/, '')
362
- .replace(/^[gs]et /, ''); // remove "get"/"set" markers
363
-
364
- // Remove parent object prototype (set as scope)
365
- dfnName = dfnName.replace(/.*\.prototype\./, '');
366
-
367
- dfn.dataset.lt = dfnName;
368
- // set dfn-type
369
- if (dfn.dataset.lt.match(/\(/)) {
370
- dfnName = cleanMethodName(dfnName);
371
- dfn.dataset.lt = dfnName;
372
- dfn.dataset.dfnType = "method";
373
- } else {
374
- dfn.dataset.dfnType = "attribute";
375
- }
376
- } else if (el.closest("#sec-value-properties-of-the-global-object")) {
377
- // properties of the global object
378
- if (el.id !== "#sec-value-properties-of-the-global-object"){
379
- const dfn = wrapWithDfn(el);
380
- dfn.dataset.lt = dfnName;
381
- dfn.dataset.dfnType = "attribute";
382
- dfn.dataset.dfnFor = "globalThis";
383
- }
384
- } else {
385
- // We handle other headings that look like a method / property
386
- // on an object instance (rather than its prototype)
387
- // or an abstract op
388
-
389
- // if there is already a dfn element, we move on
390
- if (el.querySelector("dfn")) return;
391
-
392
- // only dealing with well-known patterns
393
- if (!dfnName.match(/^[a-z]+\.[a-z]+/i) // à la JSON.parse
394
- && !dfnName.match(/^([a-z]+)+ *\(/i) // à la ArrayCreate ( or decodeURI (
395
- ) return;
396
- // Skip symbol-based property definitions
397
- if (dfnName.match(/@@/)) return;
398
-
399
- // Skip .prototype as that cannot be considered
400
- // as an attribute
401
- if (dfnName.match(/\.prototype$/)) return;
402
-
403
- // Skip headings where foo.bar appears as part of a longer phrase
404
- if (!dfnName.match(/\(/) && dfnName.match(/ /)) return;
405
-
406
- // redundant definitions of constructors on the global object
407
- // e.g. "Array ( . . . )"
408
- if (dfnName.match(/\. \. \./)) return;
409
-
410
- const dfn = wrapWithDfn(el);
411
-
412
- if (dfnName.match(/^[a-z]+\.[a-z]+/i)) {
413
- // set definition scope
414
- // This assumes that such methods and attributes are only defined
415
- // one-level deep from the global scope
416
- dfn.dataset.dfnFor = dfnName.replace(/\..*$/, '');
417
- dfnName = dfnName.replace(dfn.dataset.dfnFor + ".", '');
418
- if (dfnName.match(/\(/)) {
419
- dfnName = cleanMethodName(dfnName);
420
- dfn.dataset.lt = dfnName;
421
- dfn.dataset.dfnType = "method";
422
- } else {
423
- dfn.dataset.lt = dfnName;
424
- if (dfnName.match(/^[A-Z]+$/)) {
425
- dfn.dataset.dfnType = "const";
426
- } else {
427
- dfn.dataset.dfnType = "attribute";
428
- }
429
- }
430
- } else if (dfnName.match(/^([A-Z]+[a-z]*)+ *\(/)) { // Abstract ops à la ArrayCreate or global constructor
431
- dfnName = cleanMethodName(dfnName);
432
- dfn.dataset.lt = dfnName;
433
- const opName = dfnName.split('(')[0];
434
-
435
- // distinguish global constructors from abstract operations
436
- if (idlTypes[opName]) {
437
- dfn.dataset.dfnType = "constructor";
438
- dfn.dataset.dfnFor = opName;
439
- } else {
440
- // If the name is listed as an Abstract Method
441
- // we set the dfn-for accordingly
442
- if (abstractMethods[opName]) {
443
- dfn.dataset.dfnFor = abstractMethods[opName];
444
- }
445
-
446
- dfn.dataset.dfnType = "abstract-op";
447
- }
448
- } else { // methods of the global object
449
- dfnName = cleanMethodName(dfnName);
450
- dfn.dataset.lt = dfnName;
451
- dfn.dataset.dfnType = "method";
452
- dfn.dataset.dfnFor = "globalThis";
453
- }
454
- definitionNames.add(dfnName);
455
- }
456
- });
457
- // Extract abstract operations from <emu-eqn> with aoid attribute
458
- [...document.querySelectorAll("emu-eqn[aoid]")]
459
- .filter(legacySectionFilter)
460
- .forEach(el => {
461
- // Skip definitions of constant values (e.g. msPerDay)
462
- if (el.textContent.match(/=/)) return;
463
- const dfn = wrapWithDfn(el);
464
- dfn.dataset.lt = el.getAttribute("aoid");
465
- dfn.dataset.dfnType = "abstract-op";
466
- dfn.id = el.id;
467
- });
468
-
469
- // Extract State Components from tables
470
- [...document.querySelectorAll("figure > table")]
471
- .filter(legacySectionFilter)
472
- .forEach(el => {
473
- const title = el.parentNode.querySelector("figcaption")?.textContent || "";
474
- if (!title.match(/state components for/i)) return;
475
- const scope = title.replace(/^.*state components for/i, '').trim();
476
- for (const td of el.querySelectorAll("tr td:first-child")) {
477
- const dfn = wrapWithDfn(td);
478
- dfn.dataset.dfnFor = scope;
479
- dfn.id = el.closest("emu-table[id],emu-clause[id]").id;
480
- }
481
- });
482
-
483
- [...document.querySelectorAll("dfn")]
484
- .filter(legacySectionFilter)
485
- .forEach(el => {
486
- // Skip definitions in conformance page and conventions page
487
- if (el.closest('section[data-reffy-page$="conformance.html"]') ||
488
- el.closest('section[data-reffy-page$="notational-conventions.html"]')) {
489
- el.removeAttribute("id");
490
- return;
491
- }
492
-
493
- // rely on the aoid attribute as a hint we're dealing
494
- // with an abstract-op
495
- if (el.getAttribute("aoid")) {
496
- el.dataset.dfnType = "abstract-op";
497
- }
498
-
499
- // Mark well-known intrinsic objects as the same type as their visible object (if set), defaulting to "interface"
500
- if (el.textContent.match(/^%[A-Z].*%$/)) {
501
- el.dataset.dfnType = idlTypes[el.textContent.replace(/%/g, '')] || "interface";
502
- definitionNames.add(el.textContent.trim());
503
- }
504
-
505
- // %names% in the global object section are operations of the globalThis object
506
- if (el.closest('[data-reffy-page$="global-object.html"]') && el.textContent.match(/^%[a-z]+%/i)) {
507
- el.dataset.dfnFor = "globalThis";
508
- // TODO: this doesn't capture the arguments
509
- el.dataset.dfnType = "method";
510
- }
511
-
512
- // Mark well-known symbols as "const"
513
- // for lack of a better type, and as the WebIDL spec has been doing
514
- if (el.textContent.match(/^@@[a-z]*$/i)) {
515
- el.dataset.dfnType = "const";
516
- }
517
- if (el.getAttribute("variants")) {
518
- el.dataset.lt = (el.dataset.lt ?? el.textContent.trim()) + "|" + el.getAttribute("variants");
519
- }
520
-
521
- // Skip definitions that have already been identified
522
- // with a more specific typing
523
- if (!el.dataset.dfnType) {
524
- // we already have a matching typed definition
525
- if (definitionNames.has(el.textContent.trim())) return;
526
- }
527
-
528
- // If the <dfn> has no id, we attach it the one from the closest
529
- // <emu-clause> with an id
530
- // Note that this means several definitions can share the same id
531
- if (!el.getAttribute("id")) {
532
- if (el.closest("emu-clause[id]")) {
533
- el.setAttribute("id", el.closest("emu-clause").getAttribute("id"));
534
- }
535
- }
536
-
537
- // Any generic <dfn> not previously filtered out
538
- // is deemed to be exported, scoped to ECMAScript
539
- if (!el.dataset.dfnType) {
540
- if (!el.dataset.dfnFor) {
541
- el.dataset.dfnFor = "ECMAScript";
542
- }
543
- el.dataset.export = "";
544
- }
545
- });
546
- // Another pass of clean up for duplicates
547
- // This cannot be done in the first pass
548
- // because %Foo.prototype% does not necessarily get identified before
549
- // the equivalent " prototype object" dfn
550
-
551
- [...document.querySelectorAll("dfn[id][data-export]")]
552
- .filter(legacySectionFilter)
553
- .forEach(dfn => {
554
- // we have the syntactic equivalent %x.prototype%
555
- let m = dfn.textContent.trim().match(/^(.*) prototype( object)?$/);
556
- if (m && definitionNames.has(`%${m[1].trim()}.prototype%`)) {
557
- dfn.removeAttribute("id");
558
- delete dfn.dataset.export;
559
- return;
560
- }
561
- });
562
- }
563
-
564
- function preProcessHTML() {
565
- const headingSelector = [
566
- 'h2[id]:not([data-dfn-type]) dfn',
567
- 'h3[id]:not([data-dfn-type]) dfn',
568
- 'h4[id]:not([data-dfn-type]) dfn',
569
- 'h5[id]:not([data-dfn-type]) dfn',
570
- 'h6[id]:not([data-dfn-type]) dfn'
571
- ].join(',');
572
-
573
- // we copy the id on the dfn when it is set on the surrounding heading
574
- document.querySelectorAll(headingSelector)
575
- .forEach(el => {
576
- const headingId = el.closest("h2, h3, h4, h5, h6").id;
577
- if (!el.id) {
578
- el.id = headingId;
579
- }
580
- });
581
-
582
- // all the definitions in indices.html are non-normative, so we skip them
583
- // to avoid having to properly type them
584
- // they're not all that interesting
585
- document.querySelectorAll('section[data-reffy-page$="indices.html"] dfn[id]')
586
- .forEach(el => {
587
- el.dataset.dfnSkip = true;
588
- });
589
-
590
- document.querySelectorAll("dfn[id]:not([data-dfn-type]):not([data-skip])")
591
- .forEach(el => {
592
- // Hard coded rules for special ids
593
- // dom-style is defined elsewhere
594
- if (el.id === "dom-style") {
595
- el.dataset.dfnType = 'attribute';
596
- el.dataset.dfnFor = 'HTMLElement';
597
- el.dataset.noexport = "";
598
- return;
599
- }
600
-
601
- // If there is a link, we assume this documents an imported definition
602
- // so we make it ignored by removing the id
603
- if (el.querySelector('a[href^="http"]')) {
604
- return;
605
- }
606
- });
607
- }
608
-
609
- function preProcessSVG2() {
610
- const idl = extractWebIdl();
611
- const idlTree = parse(idl);
612
- const idlInterfaces = idlTree.filter(item => item.type === "interface" || item.type === "interface mixin");
613
-
614
- // the only element definition not properly marked up in the SVG spec
615
- const linkHeading = document.getElementById("LinkElement");
616
- if (linkHeading && !linkHeading.dataset.dfnType) {
617
- linkHeading.dataset.dfnType = "element";
618
- linkHeading.dataset.lt = "link";
619
- }
620
-
621
- document.querySelectorAll(".attrdef dfn[id]:not([data-dfn-type]):not([data-skip])")
622
- .forEach(el => {
623
- el.dataset.dfnType = "element-attr";
624
- const attrDesc = document.querySelector('[data-reffy-page$="attindex.html"] th span.attr-name a[href$="#' + el.id + '"]');
625
- if (attrDesc) {
626
- el.dataset.dfnFor = attrDesc.closest('tr').querySelector('td').textContent;
627
- } else {
628
- console.error("Could not find description for " + el.textContent);
629
- }
630
- });
631
- document.querySelectorAll("dt[id] > .adef, dt[id] > .property")
632
- .forEach(el => {
633
- const dt = el.parentNode;
634
- const newDt = document.createElement("dt");
635
- const dfn = document.createElement("dfn");
636
- dfn.id = dt.id;
637
- dfn.dataset.dfnType = el.classList.contains("adef") ? "element-attr" : "property";
638
- const indexPage = el.classList.contains("adef") ? "attindex.html" : "propidx.html";
639
- const attrDesc = document.querySelector('[data-reffy-page$="' + indexPage + '"] th a[href$="#' + dfn.id + '"]');
640
- if (attrDesc) {
641
- // TODO: this doesn't deal with grouping of elements, e.g. "text content elements"
642
- dfn.dataset.dfnFor = [...attrDesc.closest('tr').querySelectorAll('span.element-name a')].map (n => n.textContent).join(',');
643
- } else {
644
- console.error("Could not find description for " + el.textContent + "/" + dfn.id);
645
- }
646
- dfn.textContent = el.textContent;
647
- newDt.appendChild(dfn);
648
- dt.replaceWith(newDt);
649
- });
650
- document.querySelectorAll('b[id^="__svg__"]').forEach(el => {
651
- const [,, containername, membername] = el.id.split('__');
652
- if (containername && membername) {
653
- let container = idlTree.find(i => i.name === containername);
654
- if (container) {
655
- let member = container.members.find(m => m.name === membername);
656
- if (member) {
657
- const dfn = document.createElement("dfn");
658
- dfn.id = el.id;
659
- dfn.textContent = el.textContent;
660
- dfn.dataset.dfnFor = containername;
661
- dfn.dataset.dfnType = member.type === "operation" ? "method" : member.type;
662
- el.replaceWith(dfn);
663
- }
664
- }
665
- }
666
- });
667
- document.querySelectorAll('h3[id^="Interface"]:not([data-dfn-type])').forEach(el => {
668
- const name = el.id.slice("Interface".length);
669
- if (idlTree.find(i => i.name === name && i.type === "interface")) {
670
- el.dataset.dfnType = "interface";
671
- el.dataset.lt = name;
672
- }
673
- });
674
- document.querySelectorAll('b[id]:not([data-dfn-type])').forEach(el => {
675
- const name = el.textContent;
676
- const idlItem = idlTree.find(i => i.name === name) ;
677
- if (idlItem) {
678
- const dfn = document.createElement("dfn");
679
- dfn.id = el.id;
680
- dfn.dataset.dfnType = idlItem.type;
681
- dfn.textContent = el.textContent;
682
- el.replaceWith(dfn);
683
- }
684
- });
685
-
686
- }
1
+ import extractWebIdl from './extract-webidl.mjs';
2
+ import informativeSelector from './informative-selector.mjs';
3
+ import {parse} from "../../node_modules/webidl2/index.js";
4
+ /**
5
+ * Extract definitions in the spec that follow the "Definitions data model":
6
+ * https://tabatkins.github.io/bikeshed/#dfn-contract
7
+ *
8
+ * Each definition returned by the function will have the following properties:
9
+ * - id: The local ID in the DOM. Should be unique within a spec page.
10
+ * - href: The absolute URL to the definition.
11
+ * - linkingText: List of linking phrases for references.
12
+ * - localLinkingText: List of linking phrases for local references only.
13
+ * - type: The definition type. One of the values in
14
+ * https://tabatkins.github.io/bikeshed/#dfn-types
15
+ * - for: The list of namespaces for the definition
16
+ * - access: "public" when definition can be referenced by other specifications,
17
+ * "private" when it should be viewed as a local definition.
18
+ * - informative: true when definition appears in an informative section,
19
+ * false if it is normative
20
+ * - heading: Heading under which the term is to be found. An object with "id",
21
+ * "title", and "number" properties
22
+ * - definedIn: An indication of where the definition appears in the spec. Value
23
+ * can be one of "dt", "pre", "table", "heading", "note", "example", or
24
+ * "prose" (last one indicates that definition appears in the main body of
25
+ * the spec)
26
+ *
27
+ * @function
28
+ * @public
29
+ * @return {Array(Object)} An Array of definitions
30
+ */
31
+
32
+ function normalize(str) {
33
+ return str.trim().replace(/\s+/g, ' ');
34
+ }
35
+
36
+ // Valid types defined in https://tabatkins.github.io/bikeshed/#dfn-types
37
+ // (+ "namespace" and "event" which are not yet in the doc)
38
+ function hasValidType(el) {
39
+ const validDfnTypes = [
40
+ // CSS types
41
+ 'property',
42
+ 'descriptor',
43
+ 'value',
44
+ 'type',
45
+ 'at-rule',
46
+ 'function',
47
+ 'selector',
48
+
49
+ // Web IDL types
50
+ 'namespace',
51
+ 'interface',
52
+ 'constructor',
53
+ 'method',
54
+ 'argument',
55
+ 'attribute',
56
+ 'callback',
57
+ 'dictionary',
58
+ 'dict-member',
59
+ 'enum',
60
+ 'enum-value',
61
+ 'exception',
62
+ 'const',
63
+ 'typedef',
64
+ 'stringifier',
65
+ 'serializer',
66
+ 'iterator',
67
+ 'maplike',
68
+ 'setlike',
69
+ 'extended-attribute',
70
+ 'event',
71
+
72
+ // Element types
73
+ 'element',
74
+ 'element-state',
75
+ 'element-attr',
76
+ 'attr-value',
77
+
78
+
79
+ // URL scheme
80
+ 'scheme',
81
+
82
+ // HTTP header
83
+ 'http-header',
84
+
85
+ // Grammar type
86
+ 'grammar',
87
+
88
+ // "English" terms
89
+ 'abstract-op',
90
+ 'dfn'
91
+ ];
92
+
93
+ const type = el.getAttribute('data-dfn-type') ?? 'dfn';
94
+ const isValid = validDfnTypes.includes(type);
95
+ if (!isValid) {
96
+ console.warn('[reffy]', `"${type}" is an invalid dfn type for "${normalize(el.textContent)}"`);
97
+ }
98
+ return isValid;
99
+ }
100
+
101
+
102
+ function definitionMapper(el, idToHeading) {
103
+ let definedIn = 'prose';
104
+ const enclosingEl = el.closest('dt,pre,table,h1,h2,h3,h4,h5,h6,.note,.example') || el;
105
+ switch (enclosingEl.nodeName) {
106
+ case 'DT':
107
+ case 'PRE':
108
+ case 'TABLE':
109
+ definedIn = enclosingEl.nodeName.toLowerCase();
110
+ break;
111
+ case 'H1':
112
+ case 'H2':
113
+ case 'H3':
114
+ case 'H4':
115
+ case 'H5':
116
+ case 'H6':
117
+ definedIn = 'heading';
118
+ break;
119
+ default:
120
+ if (enclosingEl.classList.contains('note')) {
121
+ definedIn = 'note';
122
+ }
123
+ else if (enclosingEl.classList.contains('example')) {
124
+ definedIn = 'example';
125
+ }
126
+ break;
127
+ }
128
+
129
+ // Compute the absolute URL with fragment
130
+ // (Note the crawler merges pages of a multi-page spec in the first page
131
+ // to ease parsing logic, and we want to get back to the URL of the page)
132
+ const page = el.closest('[data-reffy-page]')?.getAttribute('data-reffy-page');
133
+ const url = new URL(page ?? window.location.href);
134
+ url.hash = '#' + el.getAttribute('id');
135
+ const href = url.toString();
136
+
137
+ return {
138
+ // ID is the id attribute
139
+ // (ID may not be unique in a multi-page spec)
140
+ id: el.getAttribute('id'),
141
+
142
+ // Absolute URL with fragment
143
+ href,
144
+
145
+ // Linking text is given by the data-lt attribute if present, or it is the
146
+ // textual content
147
+ linkingText: el.hasAttribute('data-lt') ?
148
+ el.getAttribute('data-lt').split('|').map(normalize) :
149
+ [normalize(el.textContent)],
150
+
151
+ // Additional linking text can be defined for local references
152
+ localLinkingText: el.getAttribute('data-local-lt') ?
153
+ el.getAttribute('data-local-lt').split('|').map(normalize) :
154
+ [],
155
+
156
+ // Link type must be specified, or it is "dfn"
157
+ type: el.getAttribute('data-dfn-type') || 'dfn',
158
+
159
+ // Definition may be namespaced to other constructs. Note the list is not
160
+ // purely comma-separated due to function parameters. For instance,
161
+ // attribute value may be "method(foo,bar), method()"
162
+ for: el.getAttribute('data-dfn-for') ?
163
+ el.getAttribute('data-dfn-for').split(/,(?![^\(]*\))/).map(normalize) :
164
+ [],
165
+
166
+ // Definition is public if explicitly marked as exportable or if export has
167
+ // not been explicitly disallowed and its type is not "dfn"
168
+ access: (el.hasAttribute('data-export') ||
169
+ (!el.hasAttribute('data-noexport') &&
170
+ el.hasAttribute('data-dfn-type') &&
171
+ el.getAttribute('data-dfn-type') !== 'dfn')) ?
172
+ 'public' : 'private',
173
+
174
+ // Whether the term is defined in a normative/informative section
175
+ informative: !!el.closest(informativeSelector),
176
+
177
+ // Heading under which the term is to be found
178
+ heading: idToHeading[href],
179
+
180
+ // Enclosing element under which the definition appears. Value can be one of
181
+ // "dt", "pre", "table", "heading", "note", "example", or "prose" (last one
182
+ // indicates that definition appears in the main body of the specification)
183
+ definedIn
184
+ };
185
+ }
186
+
187
+ export default function (spec, idToHeading = {}) {
188
+ const definitionsSelector = [
189
+ // re data-lt, see https://github.com/w3c/reffy/issues/336#issuecomment-650339747
190
+ 'dfn[id]:not([data-lt=""])',
191
+ 'h2[id][data-dfn-type]:not([data-lt=""])',
192
+ 'h3[id][data-dfn-type]:not([data-lt=""])',
193
+ 'h4[id][data-dfn-type]:not([data-lt=""])',
194
+ 'h5[id][data-dfn-type]:not([data-lt=""])',
195
+ 'h6[id][data-dfn-type]:not([data-lt=""])'
196
+ ].join(',');
197
+
198
+ let extraDefinitions = [];
199
+ const shortname = (typeof spec === 'string') ? spec : spec.shortname;
200
+ switch (shortname) {
201
+ case "html":
202
+ preProcessHTML();
203
+ break;
204
+ case "ecmascript":
205
+ preProcessEcmascript();
206
+ break;
207
+ case "SVG2":
208
+ preProcessSVG2();
209
+ break;
210
+ }
211
+
212
+ return [...document.querySelectorAll(definitionsSelector)]
213
+ .map(node => {
214
+ // 2021-06-21: Temporary preprocessing of invalid "idl" dfn type (used for
215
+ // internal slots) while fix for https://github.com/w3c/respec/issues/3644
216
+ // propagates to all EDs and /TR specs. To be dropped once crawls no
217
+ // longer produce warnings.
218
+ if (node.getAttribute('data-dfn-type') === 'idl') {
219
+ const linkingText = node.hasAttribute('data-lt') ?
220
+ node.getAttribute('data-lt').split('|').map(normalize) :
221
+ [normalize(node.textContent)];
222
+ node.setAttribute('data-dfn-type', linkingText[0].endsWith(')') ? 'method' : 'attribute');
223
+ console.warn('[reffy]', `Fixed invalid "idl" dfn type "${normalize(node.textContent)}"`);
224
+ }
225
+ return node;
226
+ })
227
+ .filter(hasValidType)
228
+ .map(node => definitionMapper(node, idToHeading));
229
+ }
230
+
231
+ function preProcessEcmascript() {
232
+ // Skip elements in sections marked as legacy
233
+ const legacySectionFilter = n => !n.closest("[legacy]");
234
+
235
+ const wrapWithDfn = (el) => {
236
+ // wrap with a dfn
237
+ const dfn = document.createElement("dfn");
238
+ for (let child of [...el.childNodes]) {
239
+ dfn.appendChild(child);
240
+ }
241
+ el.appendChild(dfn);
242
+ // set id
243
+ dfn.setAttribute("id", el.parentNode.getAttribute("id"));
244
+ dfn.dataset.ltNodefault = true;
245
+ return dfn;
246
+ };
247
+
248
+ const cleanMethodName = (name) => {
249
+ return name.replace(/\[/g, '')
250
+ .replace(/\]/g, '') // removing brackets used to mark optional args
251
+ .replace(/ \( */, '(')
252
+ .replace(/ *\)/, ')')
253
+ .replace(/ *,/g, ','); // trimming internal spaces
254
+ };
255
+
256
+ let definitionNames = new Set();
257
+ let idlTypes = {};
258
+
259
+ // We find the list of abstract methods
260
+ // to help with scoping abstract operations
261
+ let abstractMethods = {};
262
+ const abstractMethodCaptions = [...document.querySelectorAll("figcaption")]
263
+ .filter(el => el.textContent.match(/(abstract|additional) method/i) && el.parentNode.querySelector("emu-xref"));
264
+ for (const figcaption of abstractMethodCaptions) {
265
+ const scope = figcaption.querySelector("emu-xref").textContent;
266
+ const table = figcaption.parentNode.querySelector("tbody");
267
+ for (const td of table.querySelectorAll("tr td:first-child")) {
268
+ // We only consider the name of the method, not the potential parameters
269
+ // as they're not necessarily consistently named across
270
+ // the list and the definition
271
+ const methodName = td.textContent.trim().split('(')[0];
272
+ abstractMethods[methodName] = scope;
273
+ }
274
+ }
275
+
276
+ const sectionNumberRegExp = /^([A-Z]\.)?[0-9\.]+ /;
277
+ [...document.querySelectorAll("h1")]
278
+ .filter(legacySectionFilter)
279
+ .forEach(el => {
280
+ let dfnName = el.textContent.replace(sectionNumberRegExp, '').trim() ;// remove section number
281
+ const dfnId = el.parentNode.id;
282
+ if (dfnId.match(/-objects?$/) && dfnName.match(/ Objects?$/)) {
283
+
284
+ // Skip headings that look like object definitions, but aren't
285
+ const notObjectIds = ["sec-global-object", "sec-fundamental-objects", "sec-waiterlist-objects"];
286
+ if (notObjectIds.includes(dfnId)) return;
287
+
288
+ // only keep ids that match a credible pattern for object names
289
+ // i.e. a single word
290
+ // there are exceptions to that simple rule
291
+ // RegExp includes its expansion (regular expansion) in the id
292
+ // WeakRef is translated into weak-ref in the id
293
+ const objectsIdsExceptions = ["sec-regexp-regular-expression-objects", "sec-weak-ref-objects", "sec-aggregate-error-objects", "sec-finalization-registry-objects", "sec-async-function-objects"];
294
+
295
+ if (!dfnId.match(/sec-[a-z]+-objects?/)
296
+ && !objectsIdsExceptions.includes(dfnId)
297
+ ) return;
298
+ const dfn = wrapWithDfn(el);
299
+ // set data-lt
300
+ dfnName = dfnName
301
+ .replace(/^The /, '')
302
+ .replace(/ Objects?$/, '')
303
+ // regexp def includes "(Regular Expression)"
304
+ .replace(/ \([^\)]*\)/, '') ;
305
+ dfn.dataset.lt = dfnName;
306
+
307
+ // FIXME
308
+ // These interfaces are also defined in WebIDL, which in general is
309
+ // the prefered source for these terms
310
+ // Because bikeshed does not yet support spec-specific imports,
311
+ // we hide these terms as not exported
312
+ // cf https://github.com/w3c/reffy/pull/732#issuecomment-925950287
313
+ const exportExceptions = [ "Promise", "DataView", "ArrayBuffer" ];
314
+ if (exportExceptions.includes(dfnName)) {
315
+ dfn.dataset.noexport = "";
316
+ }
317
+
318
+ if (dfnName.match(/^[A-Z]/)) {
319
+ // set dfn-type
320
+ if (dfnName.match(/Error$/)) {
321
+ dfn.dataset.dfnType = "exception";
322
+ } else if (!el.parentNode.querySelector('[id$="constructor"]')) {
323
+ // Objects without constructors match to the namespace type
324
+ dfn.dataset.dfnType = "namespace";
325
+ } else {
326
+ dfn.dataset.dfnType = "interface";
327
+ }
328
+ // We keep track of types associated with a name
329
+ // to associate the same type to the relevant intrinsic object
330
+ // à la %Math%
331
+ idlTypes[dfnName] = dfn.dataset.dfnType;
332
+ }
333
+ definitionNames.add(dfnName);
334
+ } else if (dfnId.match(/-[a-z]+error$/) && !dfnName.match(/\(/)) {
335
+ const dfn = wrapWithDfn(el);
336
+ dfn.dataset.lt = dfnName;
337
+ dfn.dataset.dfnType = "exception";
338
+ definitionNames.add(dfnName);
339
+ idlTypes[dfnName] = dfn.dataset.dfnType;
340
+ } else if (dfnId.match(/[-\.]prototype[-\.]/)) {
341
+ // methods and attributes on objects
342
+
343
+ // Skip headings with a space and no parenthesis
344
+ // (they mention prototype but aren't a prototype property def)
345
+ // with the exception of "set " and "get " headings
346
+ // (which describe setters and getters)
347
+ if (!dfnName.match(/\(/) && (dfnName.match(/ /) && !dfnName.match(/^[gs]et /))) return;
348
+
349
+ // Skip unscoped internal methods à la [[SetPrototypeOf]](V)
350
+ if (dfnName.match(/\[\[/)) return;
351
+
352
+ // Skip symbol-based property definitions;
353
+ // not clear they're useful as externally referenceable names
354
+ if (dfnName.match(/@@/)) return;
355
+
356
+ // Skip .constructor as that cannot be considered as an attribute
357
+ if (dfnName.match(/\.constructor$/)) return;
358
+
359
+ const dfn = wrapWithDfn(el);
360
+ // set definition scope
361
+ dfn.dataset.dfnFor = dfnName.replace(/\.prototype\..*/, '')
362
+ .replace(/^[gs]et /, ''); // remove "get"/"set" markers
363
+
364
+ // Remove parent object prototype (set as scope)
365
+ dfnName = dfnName.replace(/.*\.prototype\./, '');
366
+
367
+ dfn.dataset.lt = dfnName;
368
+ // set dfn-type
369
+ if (dfn.dataset.lt.match(/\(/)) {
370
+ dfnName = cleanMethodName(dfnName);
371
+ dfn.dataset.lt = dfnName;
372
+ dfn.dataset.dfnType = "method";
373
+ } else {
374
+ dfn.dataset.dfnType = "attribute";
375
+ }
376
+ } else if (el.closest("#sec-value-properties-of-the-global-object")) {
377
+ // properties of the global object
378
+ if (el.id !== "#sec-value-properties-of-the-global-object"){
379
+ const dfn = wrapWithDfn(el);
380
+ dfn.dataset.lt = dfnName;
381
+ dfn.dataset.dfnType = "attribute";
382
+ dfn.dataset.dfnFor = "globalThis";
383
+ }
384
+ } else {
385
+ // We handle other headings that look like a method / property
386
+ // on an object instance (rather than its prototype)
387
+ // or an abstract op
388
+
389
+ // if there is already a dfn element, we move on
390
+ if (el.querySelector("dfn")) return;
391
+
392
+ // only dealing with well-known patterns
393
+ if (!dfnName.match(/^[a-z]+\.[a-z]+/i) // à la JSON.parse
394
+ && !dfnName.match(/^([a-z]+)+ *\(/i) // à la ArrayCreate ( or decodeURI (
395
+ ) return;
396
+ // Skip symbol-based property definitions
397
+ if (dfnName.match(/@@/)) return;
398
+
399
+ // Skip .prototype as that cannot be considered
400
+ // as an attribute
401
+ if (dfnName.match(/\.prototype$/)) return;
402
+
403
+ // Skip headings where foo.bar appears as part of a longer phrase
404
+ if (!dfnName.match(/\(/) && dfnName.match(/ /)) return;
405
+
406
+ // redundant definitions of constructors on the global object
407
+ // e.g. "Array ( . . . )"
408
+ if (dfnName.match(/\. \. \./)) return;
409
+
410
+ const dfn = wrapWithDfn(el);
411
+
412
+ if (dfnName.match(/^[a-z]+\.[a-z]+/i)) {
413
+ // set definition scope
414
+ // This assumes that such methods and attributes are only defined
415
+ // one-level deep from the global scope
416
+ dfn.dataset.dfnFor = dfnName.replace(/\..*$/, '');
417
+ dfnName = dfnName.replace(dfn.dataset.dfnFor + ".", '');
418
+ if (dfnName.match(/\(/)) {
419
+ dfnName = cleanMethodName(dfnName);
420
+ dfn.dataset.lt = dfnName;
421
+ dfn.dataset.dfnType = "method";
422
+ } else {
423
+ dfn.dataset.lt = dfnName;
424
+ if (dfnName.match(/^[A-Z]+$/)) {
425
+ dfn.dataset.dfnType = "const";
426
+ } else {
427
+ dfn.dataset.dfnType = "attribute";
428
+ }
429
+ }
430
+ } else if (dfnName.match(/^([A-Z]+[a-z]*)+ *\(/)) { // Abstract ops à la ArrayCreate or global constructor
431
+ dfnName = cleanMethodName(dfnName);
432
+ dfn.dataset.lt = dfnName;
433
+ const opName = dfnName.split('(')[0];
434
+
435
+ // distinguish global constructors from abstract operations
436
+ if (idlTypes[opName]) {
437
+ dfn.dataset.dfnType = "constructor";
438
+ dfn.dataset.dfnFor = opName;
439
+ } else {
440
+ // If the name is listed as an Abstract Method
441
+ // we set the dfn-for accordingly
442
+ if (abstractMethods[opName]) {
443
+ dfn.dataset.dfnFor = abstractMethods[opName];
444
+ }
445
+
446
+ dfn.dataset.dfnType = "abstract-op";
447
+ }
448
+ } else { // methods of the global object
449
+ dfnName = cleanMethodName(dfnName);
450
+ dfn.dataset.lt = dfnName;
451
+ dfn.dataset.dfnType = "method";
452
+ dfn.dataset.dfnFor = "globalThis";
453
+ }
454
+ definitionNames.add(dfnName);
455
+ }
456
+ });
457
+ // Extract abstract operations from <emu-eqn> with aoid attribute
458
+ [...document.querySelectorAll("emu-eqn[aoid]")]
459
+ .filter(legacySectionFilter)
460
+ .forEach(el => {
461
+ // Skip definitions of constant values (e.g. msPerDay)
462
+ if (el.textContent.match(/=/)) return;
463
+ const dfn = wrapWithDfn(el);
464
+ dfn.dataset.lt = el.getAttribute("aoid");
465
+ dfn.dataset.dfnType = "abstract-op";
466
+ dfn.id = el.id;
467
+ });
468
+
469
+ // Extract State Components from tables
470
+ [...document.querySelectorAll("figure > table")]
471
+ .filter(legacySectionFilter)
472
+ .forEach(el => {
473
+ const title = el.parentNode.querySelector("figcaption")?.textContent || "";
474
+ if (!title.match(/state components for/i)) return;
475
+ const scope = title.replace(/^.*state components for/i, '').trim();
476
+ for (const td of el.querySelectorAll("tr td:first-child")) {
477
+ const dfn = wrapWithDfn(td);
478
+ dfn.dataset.dfnFor = scope;
479
+ dfn.id = el.closest("emu-table[id],emu-clause[id]").id;
480
+ }
481
+ });
482
+
483
+ [...document.querySelectorAll("dfn")]
484
+ .filter(legacySectionFilter)
485
+ .forEach(el => {
486
+ // Skip definitions in conformance page and conventions page
487
+ if (el.closest('section[data-reffy-page$="conformance.html"]') ||
488
+ el.closest('section[data-reffy-page$="notational-conventions.html"]')) {
489
+ el.removeAttribute("id");
490
+ return;
491
+ }
492
+
493
+ // rely on the aoid attribute as a hint we're dealing
494
+ // with an abstract-op
495
+ if (el.getAttribute("aoid")) {
496
+ el.dataset.dfnType = "abstract-op";
497
+ }
498
+
499
+ // Mark well-known intrinsic objects as the same type as their visible object (if set), defaulting to "interface"
500
+ if (el.textContent.match(/^%[A-Z].*%$/)) {
501
+ el.dataset.dfnType = idlTypes[el.textContent.replace(/%/g, '')] || "interface";
502
+ definitionNames.add(el.textContent.trim());
503
+ }
504
+
505
+ // %names% in the global object section are operations of the globalThis object
506
+ if (el.closest('[data-reffy-page$="global-object.html"]') && el.textContent.match(/^%[a-z]+%/i)) {
507
+ el.dataset.dfnFor = "globalThis";
508
+ // TODO: this doesn't capture the arguments
509
+ el.dataset.dfnType = "method";
510
+ }
511
+
512
+ // Mark well-known symbols as "const"
513
+ // for lack of a better type, and as the WebIDL spec has been doing
514
+ if (el.textContent.match(/^@@[a-z]*$/i)) {
515
+ el.dataset.dfnType = "const";
516
+ }
517
+ if (el.getAttribute("variants")) {
518
+ el.dataset.lt = (el.dataset.lt ?? el.textContent.trim()) + "|" + el.getAttribute("variants");
519
+ }
520
+
521
+ // Skip definitions that have already been identified
522
+ // with a more specific typing
523
+ if (!el.dataset.dfnType) {
524
+ // we already have a matching typed definition
525
+ if (definitionNames.has(el.textContent.trim())) return;
526
+ }
527
+
528
+ // If the <dfn> has no id, we attach it the one from the closest
529
+ // <emu-clause> with an id
530
+ // Note that this means several definitions can share the same id
531
+ if (!el.getAttribute("id")) {
532
+ if (el.closest("emu-clause[id]")) {
533
+ el.setAttribute("id", el.closest("emu-clause").getAttribute("id"));
534
+ }
535
+ }
536
+
537
+ // Any generic <dfn> not previously filtered out
538
+ // is deemed to be exported, scoped to ECMAScript
539
+ if (!el.dataset.dfnType) {
540
+ if (!el.dataset.dfnFor) {
541
+ el.dataset.dfnFor = "ECMAScript";
542
+ }
543
+ el.dataset.export = "";
544
+ }
545
+ });
546
+ // Another pass of clean up for duplicates
547
+ // This cannot be done in the first pass
548
+ // because %Foo.prototype% does not necessarily get identified before
549
+ // the equivalent " prototype object" dfn
550
+
551
+ [...document.querySelectorAll("dfn[id][data-export]")]
552
+ .filter(legacySectionFilter)
553
+ .forEach(dfn => {
554
+ // we have the syntactic equivalent %x.prototype%
555
+ let m = dfn.textContent.trim().match(/^(.*) prototype( object)?$/);
556
+ if (m && definitionNames.has(`%${m[1].trim()}.prototype%`)) {
557
+ dfn.removeAttribute("id");
558
+ delete dfn.dataset.export;
559
+ return;
560
+ }
561
+ });
562
+ }
563
+
564
+ function preProcessHTML() {
565
+ const headingSelector = [
566
+ 'h2[id]:not([data-dfn-type]) dfn',
567
+ 'h3[id]:not([data-dfn-type]) dfn',
568
+ 'h4[id]:not([data-dfn-type]) dfn',
569
+ 'h5[id]:not([data-dfn-type]) dfn',
570
+ 'h6[id]:not([data-dfn-type]) dfn'
571
+ ].join(',');
572
+
573
+ // we copy the id on the dfn when it is set on the surrounding heading
574
+ document.querySelectorAll(headingSelector)
575
+ .forEach(el => {
576
+ const headingId = el.closest("h2, h3, h4, h5, h6").id;
577
+ if (!el.id) {
578
+ el.id = headingId;
579
+ }
580
+ });
581
+
582
+ // all the definitions in indices.html are non-normative, so we skip them
583
+ // to avoid having to properly type them
584
+ // they're not all that interesting
585
+ document.querySelectorAll('section[data-reffy-page$="indices.html"] dfn[id]')
586
+ .forEach(el => {
587
+ el.dataset.dfnSkip = true;
588
+ });
589
+
590
+ document.querySelectorAll("dfn[id]:not([data-dfn-type]):not([data-skip])")
591
+ .forEach(el => {
592
+ // Hard coded rules for special ids
593
+ // dom-style is defined elsewhere
594
+ if (el.id === "dom-style") {
595
+ el.dataset.dfnType = 'attribute';
596
+ el.dataset.dfnFor = 'HTMLElement';
597
+ el.dataset.noexport = "";
598
+ return;
599
+ }
600
+
601
+ // If there is a link, we assume this documents an imported definition
602
+ // so we make it ignored by removing the id
603
+ if (el.querySelector('a[href^="http"]')) {
604
+ return;
605
+ }
606
+ });
607
+ }
608
+
609
+ function preProcessSVG2() {
610
+ const idl = extractWebIdl();
611
+ const idlTree = parse(idl);
612
+ const idlInterfaces = idlTree.filter(item => item.type === "interface" || item.type === "interface mixin");
613
+
614
+ // the only element definition not properly marked up in the SVG spec
615
+ const linkHeading = document.getElementById("LinkElement");
616
+ if (linkHeading && !linkHeading.dataset.dfnType) {
617
+ linkHeading.dataset.dfnType = "element";
618
+ linkHeading.dataset.lt = "link";
619
+ }
620
+
621
+ document.querySelectorAll(".attrdef dfn[id]:not([data-dfn-type]):not([data-skip])")
622
+ .forEach(el => {
623
+ el.dataset.dfnType = "element-attr";
624
+ const attrDesc = document.querySelector('[data-reffy-page$="attindex.html"] th span.attr-name a[href$="#' + el.id + '"]');
625
+ if (attrDesc) {
626
+ el.dataset.dfnFor = attrDesc.closest('tr').querySelector('td').textContent;
627
+ } else {
628
+ console.error("Could not find description for " + el.textContent);
629
+ }
630
+ });
631
+ document.querySelectorAll("dt[id] > .adef, dt[id] > .property")
632
+ .forEach(el => {
633
+ const dt = el.parentNode;
634
+ const newDt = document.createElement("dt");
635
+ const dfn = document.createElement("dfn");
636
+ dfn.id = dt.id;
637
+ dfn.dataset.dfnType = el.classList.contains("adef") ? "element-attr" : "property";
638
+ const indexPage = el.classList.contains("adef") ? "attindex.html" : "propidx.html";
639
+ const attrDesc = document.querySelector('[data-reffy-page$="' + indexPage + '"] th a[href$="#' + dfn.id + '"]');
640
+ if (attrDesc) {
641
+ // TODO: this doesn't deal with grouping of elements, e.g. "text content elements"
642
+ dfn.dataset.dfnFor = [...attrDesc.closest('tr').querySelectorAll('span.element-name a')].map (n => n.textContent).join(',');
643
+ } else {
644
+ console.error("Could not find description for " + el.textContent + "/" + dfn.id);
645
+ }
646
+ dfn.textContent = el.textContent;
647
+ newDt.appendChild(dfn);
648
+ dt.replaceWith(newDt);
649
+ });
650
+ document.querySelectorAll('b[id^="__svg__"]').forEach(el => {
651
+ const [,, containername, membername] = el.id.split('__');
652
+ if (containername && membername) {
653
+ let container = idlTree.find(i => i.name === containername);
654
+ if (container) {
655
+ let member = container.members.find(m => m.name === membername);
656
+ if (member) {
657
+ const dfn = document.createElement("dfn");
658
+ dfn.id = el.id;
659
+ dfn.textContent = el.textContent;
660
+ dfn.dataset.dfnFor = containername;
661
+ dfn.dataset.dfnType = member.type === "operation" ? "method" : member.type;
662
+ el.replaceWith(dfn);
663
+ }
664
+ }
665
+ }
666
+ });
667
+ document.querySelectorAll('h3[id^="Interface"]:not([data-dfn-type])').forEach(el => {
668
+ const name = el.id.slice("Interface".length);
669
+ if (idlTree.find(i => i.name === name && i.type === "interface")) {
670
+ el.dataset.dfnType = "interface";
671
+ el.dataset.lt = name;
672
+ }
673
+ });
674
+ document.querySelectorAll('b[id]:not([data-dfn-type])').forEach(el => {
675
+ const name = el.textContent;
676
+ const idlItem = idlTree.find(i => i.name === name) ;
677
+ if (idlItem) {
678
+ const dfn = document.createElement("dfn");
679
+ dfn.id = el.id;
680
+ dfn.dataset.dfnType = idlItem.type;
681
+ dfn.textContent = el.textContent;
682
+ el.replaceWith(dfn);
683
+ }
684
+ });
685
+
686
+ }