reffy 6.6.0 → 7.0.2

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