@surea11y/core 1.5.0 → 1.7.0

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 (157) hide show
  1. package/CHANGELOG.md +240 -149
  2. package/README.md +51 -44
  3. package/docs/ACT_RULE_MAPPING.md +245 -0
  4. package/docs/API_STABILITY.md +53 -5
  5. package/docs/BINDING_AUTHORS_GUIDE.md +106 -4
  6. package/docs/DESIGN_CHALLENGES.md +367 -0
  7. package/docs/EARL.md +100 -0
  8. package/docs/ENGINE_OPTIONS.md +42 -4
  9. package/docs/I18N.md +4 -4
  10. package/docs/INTEGRATION.md +4 -2
  11. package/docs/LIMITATIONS.md +9 -5
  12. package/docs/OUTPUT_SCHEMA.md +44 -6
  13. package/docs/POLICY.md +1 -1
  14. package/docs/REPORT.md +1 -1
  15. package/docs/RULE_AUTHORING.md +63 -36
  16. package/docs/RULE_CATALOG.md +1928 -169
  17. package/docs/RULE_HELPERS.md +333 -0
  18. package/docs/RULE_TAXONOMY.md +27 -6
  19. package/docs/SARIF.md +21 -2
  20. package/docs/TROUBLESHOOTING.md +2 -2
  21. package/docs/WCAG_CONFORMANCE.md +34 -10
  22. package/package.json +11 -9
  23. package/src/baseline.js +3 -3
  24. package/src/checks/automatic/area-alt-present.js +2 -2
  25. package/src/checks/automatic/aria-allowed-attr.js +74 -10
  26. package/src/checks/automatic/aria-allowed-role.js +34 -25
  27. package/src/checks/automatic/aria-braille-equivalent.js +21 -13
  28. package/src/checks/automatic/aria-conditional-attr.js +22 -15
  29. package/src/checks/automatic/aria-deprecated-role.js +13 -1
  30. package/src/checks/automatic/aria-hidden-body.js +3 -3
  31. package/src/checks/automatic/aria-hidden-focus.js +5 -5
  32. package/src/checks/automatic/aria-prohibited-attr.js +23 -18
  33. package/src/checks/automatic/aria-prohibited-children.js +136 -43
  34. package/src/checks/automatic/aria-required-attr.js +119 -24
  35. package/src/checks/automatic/aria-required-children.js +54 -30
  36. package/src/checks/automatic/aria-required-parent.js +93 -15
  37. package/src/checks/automatic/aria-role-name-present.js +37 -23
  38. package/src/checks/automatic/aria-roles-valid.js +52 -21
  39. package/src/checks/automatic/aria-valid-attr-value.js +89 -33
  40. package/src/checks/automatic/aria-valid-attr.js +15 -10
  41. package/src/checks/automatic/autocomplete-valid.js +2 -2
  42. package/src/checks/automatic/avoid-inline-spacing.js +133 -6
  43. package/src/checks/automatic/binary-control-name-present.js +27 -5
  44. package/src/checks/automatic/button-name-present.js +92 -6
  45. package/src/checks/automatic/combobox-name-present.js +26 -6
  46. package/src/checks/automatic/contrast-computable.js +42 -0
  47. package/src/checks/automatic/contrast-enhanced.js +33 -1
  48. package/src/checks/automatic/contrast-minimum.js +33 -1
  49. package/src/checks/automatic/css-orientation-lock.js +138 -24
  50. package/src/checks/automatic/definition-list-children-valid.js +7 -8
  51. package/src/checks/automatic/deprecated-elements-not-used.js +1 -1
  52. package/src/checks/automatic/dialog-name-present.js +20 -2
  53. package/src/checks/automatic/duplicate-id-aria.js +10 -3
  54. package/src/checks/automatic/duplicate-id.js +203 -0
  55. package/src/checks/automatic/embed-text-alternative-present.js +2 -2
  56. package/src/checks/automatic/form-control-programmatic-label-present.js +19 -2
  57. package/src/checks/automatic/form-control-single-label.js +10 -1
  58. package/src/checks/automatic/identical-iframes-same-purpose.js +229 -0
  59. package/src/checks/automatic/iframe-focusable-content.js +68 -7
  60. package/src/checks/automatic/iframe-name-present.js +37 -3
  61. package/src/checks/automatic/iframe-title-unique.js +1 -1
  62. package/src/checks/automatic/img-alt-present.js +12 -4
  63. package/src/checks/automatic/label-in-name.js +204 -68
  64. package/src/checks/automatic/link-in-text-block.js +285 -29
  65. package/src/checks/automatic/link-name-present.js +22 -1
  66. package/src/checks/automatic/list-children-valid.js +6 -6
  67. package/src/checks/automatic/listbox-name-present.js +28 -8
  68. package/src/checks/automatic/listitem-parent-valid.js +4 -4
  69. package/src/checks/automatic/menuitem-name-present.js +20 -2
  70. package/src/checks/automatic/meta-refresh-no-exceptions.js +25 -14
  71. package/src/checks/automatic/meta-refresh-timing-absent.js +14 -7
  72. package/src/checks/automatic/meter-name-present.js +23 -4
  73. package/src/checks/automatic/nested-interactive-controls-absent.js +5 -5
  74. package/src/checks/automatic/option-name-present.js +23 -4
  75. package/src/checks/automatic/page-title-present.js +21 -3
  76. package/src/checks/automatic/presentational-children-focusable-absent.js +330 -0
  77. package/src/checks/automatic/progressbar-name-present.js +23 -4
  78. package/src/checks/automatic/{role-img-alt-present.js → role-img-text-alternative-present.js} +64 -16
  79. package/src/checks/automatic/searchbox-name-present.js +28 -8
  80. package/src/checks/automatic/server-side-image-map-absent.js +1 -1
  81. package/src/checks/automatic/slider-name-present.js +27 -6
  82. package/src/checks/automatic/spinbutton-name-present.js +28 -8
  83. package/src/checks/automatic/summary-name-present.js +18 -2
  84. package/src/checks/automatic/svg-image-text-alternative-present.js +1 -1
  85. package/src/checks/automatic/svg-text-alternative-present.js +13 -10
  86. package/src/checks/automatic/tab-name-present.js +21 -2
  87. package/src/checks/automatic/table-headers-attr-valid.js +43 -8
  88. package/src/checks/automatic/table-th-has-data-cells.js +61 -5
  89. package/src/checks/automatic/target-size-minimum.js +155 -58
  90. package/src/checks/automatic/td-has-header.js +24 -23
  91. package/src/checks/automatic/textbox-name-present.js +28 -8
  92. package/src/checks/automatic/tooltip-name-present.js +21 -2
  93. package/src/checks/automatic/treeitem-name-present.js +23 -4
  94. package/src/checks/automatic/valid-lang.js +92 -7
  95. package/src/checks/automatic/video-poster-text-alternative-present.js +1 -1
  96. package/src/checks/manual/accesskeys-manual.js +3 -3
  97. package/src/checks/manual/area-alt-decorative-manual.js +7 -0
  98. package/src/checks/manual/area-alt-quality-manual.js +6 -0
  99. package/src/checks/manual/aria-checked-state-mismatch-manual.js +5 -5
  100. package/src/checks/manual/aria-text-manual.js +4 -4
  101. package/src/checks/manual/bypass-blocks-present-manual.js +44 -26
  102. package/src/checks/manual/canvas-text-alternative-quality-manual.js +8 -0
  103. package/src/checks/manual/css-focus-indicator-suppressed-manual.js +444 -0
  104. package/src/checks/manual/embed-text-alternative-quality-manual.js +8 -0
  105. package/src/checks/manual/empty-heading-manual.js +58 -11
  106. package/src/checks/manual/empty-table-header-manual.js +8 -8
  107. package/src/checks/manual/focus-order-semantics-manual.js +15 -15
  108. package/src/checks/manual/form-control-label-quality-manual.js +563 -0
  109. package/src/checks/manual/form-control-programmatic-label-quality-manual.js +2 -2
  110. package/src/checks/manual/heading-order-manual.js +3 -3
  111. package/src/checks/manual/heading-quality-manual.js +338 -0
  112. package/src/checks/manual/identical-links-same-purpose-manual.js +73 -12
  113. package/src/checks/manual/image-redundant-alt-manual.js +4 -4
  114. package/src/checks/manual/img-alt-decorative-manual.js +211 -52
  115. package/src/checks/manual/img-alt-quality-manual.js +7 -0
  116. package/src/checks/manual/input-image-alt-decorative-manual.js +8 -0
  117. package/src/checks/manual/input-image-alt-quality-manual.js +5 -0
  118. package/src/checks/manual/label-title-only-manual.js +4 -4
  119. package/src/checks/manual/landmark-banner-is-top-level-manual.js +7 -7
  120. package/src/checks/manual/landmark-complementary-is-top-level-manual.js +231 -0
  121. package/src/checks/manual/landmark-contentinfo-is-top-level-manual.js +9 -9
  122. package/src/checks/manual/landmark-main-is-top-level-manual.js +6 -6
  123. package/src/checks/manual/landmark-no-duplicate-banner-manual.js +6 -6
  124. package/src/checks/manual/landmark-no-duplicate-contentinfo-manual.js +6 -6
  125. package/src/checks/manual/landmark-no-duplicate-main-manual.js +2 -2
  126. package/src/checks/manual/landmark-one-main-manual.js +6 -6
  127. package/src/checks/manual/landmark-unique-manual.js +9 -9
  128. package/src/checks/manual/link-name-quality-manual.js +161 -32
  129. package/src/checks/manual/media-transcript-present-manual.js +2 -3
  130. package/src/checks/manual/meta-viewport-large-manual.js +2 -2
  131. package/src/checks/manual/mouse-only-event-handlers-manual.js +9 -9
  132. package/src/checks/manual/no-autoplay-audio-manual.js +3 -3
  133. package/src/checks/manual/object-text-alternative-quality-manual.js +8 -0
  134. package/src/checks/manual/p-as-heading-manual.js +4 -4
  135. package/src/checks/manual/page-has-heading-one-manual.js +6 -6
  136. package/src/checks/manual/page-title-patterns-manual.js +26 -3
  137. package/src/checks/manual/password-paste-enabled-manual.js +255 -0
  138. package/src/checks/manual/presentation-role-conflict-manual.js +55 -25
  139. package/src/checks/manual/region-manual.js +19 -19
  140. package/src/checks/manual/scope-attr-valid-manual.js +2 -2
  141. package/src/checks/manual/scrollable-region-focusable-manual.js +7 -7
  142. package/src/checks/manual/skip-link-manual.js +5 -5
  143. package/src/checks/manual/svg-text-alternative-quality-manual.js +9 -0
  144. package/src/checks/manual/tabindex-manual.js +2 -2
  145. package/src/checks/manual/table-duplicate-name-manual.js +2 -2
  146. package/src/checks/manual/table-fake-caption-manual.js +2 -2
  147. package/src/checks/manual/video-caption-manual.js +3 -3
  148. package/src/checks/manual-review.js +17 -1
  149. package/src/core.js +8880 -41883
  150. package/src/earl.js +144 -0
  151. package/src/report.js +2 -2
  152. package/src/sarif.js +22 -2
  153. package/surea11y.browser.js +10 -37882
  154. package/surea11y.i18n.de.js +2 -21
  155. package/surea11y.i18n.es.js +2 -21
  156. package/surea11y.i18n.fr.js +2 -21
  157. package/bin/surea11y-core.js +0 -20
@@ -52,7 +52,16 @@ function runInPage(ctx) { /* see Runtime Contract */ }
52
52
  module.exports = { id, meta, runInPage };
53
53
  ```
54
54
 
55
- No other exports.
55
+ One optional fourth export: `applicability(ctx)`, a predicate the engine calls before
56
+ `runInPage` to decide whether the rule is in scope for this run at all. Fourteen rules
57
+ use it today (see §11.2). Export it alongside the other three when you need it:
58
+
59
+ ```js
60
+ module.exports = { id, meta, runInPage, applicability };
61
+ ```
62
+
63
+ Nothing else. `npm run validate:rules` enforces exactly this set, and rejects a fifth
64
+ export.
56
65
 
57
66
  ---
58
67
 
@@ -78,7 +87,7 @@ Examples observed:
78
87
 
79
88
  ## 4) Meta Contract (all keys used by current rules)
80
89
 
81
- Every rule defines a `meta` object. In the rule set you uploaded, the union of meta keys is:
90
+ Every rule defines a `meta` object. Across the shipped ruleset, the union of meta keys is:
82
91
 
83
92
  ### 4.1 Required top-level keys
84
93
 
@@ -177,8 +186,7 @@ occurrences.push(helpers.reportOccurrence(el, { summary: '…', hint: '…' }));
177
186
  ```
178
187
 
179
188
  It attaches the element for the engine to finalize, which is how `selector`,
180
- `html` and `structuralPath` get filled in centrally instead of in each of the
181
- 124 rules.
189
+ `html` and `structuralPath` get filled in centrally instead of in each rule.
182
190
 
183
191
  **This is a performance contract, not just a convenience.** Every occurrence
184
192
  gets a `structuralPath`. Given the element, the engine computes it directly.
@@ -246,17 +254,15 @@ that one is on you.
246
254
 
247
255
  ## 6) Helpers contract used by rules (ctx.helpers)
248
256
 
249
- Rules use helpers returned by `createDomHelpers()`.
257
+ Rules use helpers returned by `createDomHelpers()`. The most load-bearing ones —
258
+ `queryAllSmart` (query with shadow/hidden/exclude handling built in),
259
+ `getAccessibleNameInfo`/`getAccessibleDescriptionInfo`/`getTextAlternativeInfo` (naming),
260
+ `isAccTreeEligible`/`getEligibilityInfo` (visibility), `getRoleInfo`/`getFocusableInfo`
261
+ (role/focus) — cover most rules.
250
262
 
251
- Helpers observed in this repo include:
252
- - `queryAll`, `queryAllDeep`, `queryAllSmart`
253
- - `getOuterHtmlSnippet`
254
- - `buildSimpleSelector`, `buildSelector`
255
- - `isAccTreeEligible`, `getEligibilityInfo`
256
- - `resolveIdRefs`, `getTextFromIdRefs`
257
- - `getAccessibleNameInfo`, `getAccessibleDescriptionInfo`
258
- - `getTextAlternativeInfo`
259
- - `getRoleInfo`, `getFocusableInfo`
263
+ **See [`RULE_HELPERS.md`](./RULE_HELPERS.md) for the full reference** (~35 helpers plus
264
+ the `contrast.*`/`aria.*` namespaces), with what each one does and when to reach for it
265
+ instead of reimplementing the logic in a new rule.
260
266
 
261
267
  ### 6.1 Shadow DOM scanning
262
268
 
@@ -268,15 +274,19 @@ const nodes = helpers.queryAllSmart
268
274
  : helpers.queryAll('img');
269
275
  ```
270
276
 
271
- Shadow traversal is opt-in via engine option:
277
+ Shadow traversal is on by default. It is the caller who opts out:
272
278
  ```js
273
- engineOptions: { includeShadowDom: true }
279
+ engineOptions: { includeShadowDom: false } // light DOM only
274
280
  ```
281
+ So write the rule assuming open shadow roots are in scope; `queryAllSmart` honours the
282
+ caller's choice for you. Closed roots are unreachable either way.
275
283
 
276
284
  ### 6.2 Reporting note for Shadow DOM
277
285
 
278
- Selectors do not pierce shadow boundaries, so a `selector` may not uniquely locate nodes in Shadow DOM.
279
- Therefore: **always include `html` in occurrences**.
286
+ Selectors do not pierce shadow boundaries, so a `selector` may not uniquely locate a node
287
+ inside a shadow root — which is why `html` matters as the "which element" signal there.
288
+ You get both for free by reporting the element through `helpers.reportOccurrence` (§4.3);
289
+ there is nothing extra to do for shadow DOM specifically.
280
290
 
281
291
  ---
282
292
 
@@ -290,7 +300,8 @@ data: {
290
300
  }
291
301
  ```
292
302
 
293
- This is consistent across your uploaded rule family.
303
+ Pass the `eligInfo` you already computed for the element; the fallback object above is
304
+ for the case where a rule has none to give.
294
305
 
295
306
  ---
296
307
 
@@ -319,34 +330,48 @@ Manual:
319
330
 
320
331
  ---
321
332
 
322
- ## 9) Occurrence object shape (repo reality)
333
+ ## 9) Occurrence object shape
323
334
 
324
- Typical pattern:
335
+ Report the element and let the engine finish the object (§4.3):
325
336
 
326
337
  ```js
327
- occurrences.push({
328
- selector,
329
- html,
330
- summary: '…',
331
- hint: '…',
332
- i18n: { summaryKey: '…', hintKey: '…', params: { element: 'img' } },
333
- data: { visibilityFilter: eligInfo || { targetSet: 'acc', accEligible: null, reasons: [] } }
334
- });
338
+ occurrences.push(
339
+ helpers.reportOccurrence(el, {
340
+ summary: '…',
341
+ hint: '…',
342
+ i18n: { summaryKey: '…', hintKey: '…', params: { element: 'img' } },
343
+ data: { visibilityFilter: eligInfo || { targetSet: 'acc', accEligible: null, reasons: [] } }
344
+ })
345
+ );
335
346
  ```
336
347
 
337
- Observed properties:
338
- - `selector` (or sometimes `selectorStr`)
339
- - `html`
348
+ What a rule supplies:
340
349
  - `summary`
341
350
  - `hint`
342
351
  - `i18n` (`summaryKey`, `hintKey`, `params`)
343
352
  - `data` (includes `visibilityFilter`)
344
353
 
354
+ What the engine fills in from the reported element:
355
+ - `selector`
356
+ - `html`
357
+ - `structuralPath`
358
+
359
+ Setting `selector`/`html` yourself still works and still wins — a handful of rules whose
360
+ finding is not a single element (the contrast rules report text runs) do exactly that. It
361
+ is the exception, not the pattern to copy.
362
+
345
363
  ---
346
364
 
347
365
  ## 10) Structured doc comment block
348
366
 
349
- Keep the structured header comment (`@rule`, `@atomic`, `@summary`, `@standard`, `@sc`, `@applicability`, `@expectation`).
367
+ Keep the structured header comment (`@check`, `@atomic`, `@summary`, `@standard`, `@sc`,
368
+ `@applicability`, `@expectation`). The id goes on `@check` — `@rule` is not a tag this
369
+ repo uses. `docs/RULE_TEMPLATE.js` has the full block to copy.
370
+
371
+ `@applicability` and `@expectation` are consumer-facing: `scripts/generate-rule-catalog.js`
372
+ reads them straight from the source and publishes them per rule in
373
+ [`RULE_CATALOG.md`](./RULE_CATALOG.md#rule-reference). Write them for someone deciding
374
+ whether a result applies to their page, and rerun `npm run docs:rule-catalog` after editing them.
350
375
 
351
376
  ---
352
377
 
@@ -452,8 +477,10 @@ After adding or changing any fixture, regenerate the index:
452
477
  npm run fixtures:index
453
478
  ```
454
479
 
455
- This writes `tests/fixtures/INDEX.md` (human-readable) and `tests/fixtures/index.json`
480
+ This writes `tests/fixtures/INDEX.md` (human-readable), `tests/fixtures/index.json`
456
481
  (machine-readable — every rule, its fixture path, and parsed pass/fail/cantTell case
457
- counts, for external tooling to enumerate and load fixtures directly). Commit both
482
+ counts, for external tooling to enumerate and load fixtures directly) and
483
+ `tests/fixtures/index.html` (the same listing as a browsable page). Commit all three
458
484
  alongside the fixture and test changes. A rule shipped without its fixture is treated
459
- the same as a rule shipped without tests — not done.
485
+ the same as a rule shipped without tests — not done. `npm run fixtures:check` reports
486
+ a stale index without rewriting it, and CI fails on one.