@surea11y/core 1.2.0 → 1.4.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 (168) hide show
  1. package/CHANGELOG.md +81 -7
  2. package/LICENSE +373 -21
  3. package/README.md +175 -35
  4. package/bin/surea11y-core.js +20 -0
  5. package/docs/API_STABILITY.md +27 -1
  6. package/docs/BINDING_AUTHORS_GUIDE.md +9 -9
  7. package/docs/CI_INTEGRATIONS.md +103 -0
  8. package/docs/ENGINE_OPTIONS.md +2 -0
  9. package/docs/I18N.md +12 -9
  10. package/docs/INTEGRATION.md +19 -1
  11. package/docs/LIMITATIONS.md +1 -1
  12. package/docs/OUTPUT_SCHEMA.md +1 -1
  13. package/docs/REPORT.md +1 -1
  14. package/docs/RULE_CATALOG.md +1 -1
  15. package/docs/SARIF.md +59 -0
  16. package/package.json +63 -18
  17. package/src/baseline.js +0 -0
  18. package/src/checks/automatic/area-alt-present.js +63 -31
  19. package/src/checks/automatic/aria-allowed-attr.js +204 -80
  20. package/src/checks/automatic/aria-allowed-role.js +23 -7
  21. package/src/checks/automatic/aria-braille-equivalent.js +34 -10
  22. package/src/checks/automatic/aria-conditional-attr.js +32 -14
  23. package/src/checks/automatic/aria-deprecated-role.js +26 -11
  24. package/src/checks/automatic/aria-hidden-body.js +48 -23
  25. package/src/checks/automatic/aria-hidden-focus.js +420 -66
  26. package/src/checks/automatic/aria-prohibited-attr.js +327 -60
  27. package/src/checks/automatic/aria-prohibited-children.js +111 -103
  28. package/src/checks/automatic/aria-required-attr.js +29 -15
  29. package/src/checks/automatic/aria-required-children.js +44 -24
  30. package/src/checks/automatic/aria-required-parent.js +64 -35
  31. package/src/checks/automatic/aria-role-name-present.js +49 -21
  32. package/src/checks/automatic/aria-roles-valid.js +24 -12
  33. package/src/checks/automatic/aria-valid-attr-value.js +46 -22
  34. package/src/checks/automatic/aria-valid-attr.js +19 -5
  35. package/src/checks/automatic/autocomplete-valid.js +76 -16
  36. package/src/checks/automatic/avoid-inline-spacing.js +23 -8
  37. package/src/checks/automatic/binary-control-name-present.js +62 -50
  38. package/src/checks/automatic/button-name-present.js +54 -24
  39. package/src/checks/automatic/bypass-blocks-present.js +51 -32
  40. package/src/checks/automatic/canvas-text-alternative-present.js +59 -26
  41. package/src/checks/automatic/combobox-name-present.js +40 -45
  42. package/src/checks/automatic/contrast-computable.js +363 -341
  43. package/src/checks/automatic/contrast-enhanced.js +489 -466
  44. package/src/checks/automatic/contrast-minimum.js +488 -465
  45. package/src/checks/automatic/css-orientation-lock.js +51 -35
  46. package/src/checks/automatic/definition-list-children-valid.js +46 -25
  47. package/src/checks/automatic/deprecated-elements-not-used.js +25 -9
  48. package/src/checks/automatic/dialog-name-present.js +47 -85
  49. package/src/checks/automatic/dlitem-parent-valid.js +25 -8
  50. package/src/checks/automatic/duplicate-id-aria.js +28 -9
  51. package/src/checks/automatic/embed-text-alternative-present.js +88 -35
  52. package/src/checks/automatic/form-control-programmatic-label-present.js +81 -196
  53. package/src/checks/automatic/form-control-single-label.js +50 -14
  54. package/src/checks/automatic/html-xml-lang-mismatch.js +36 -18
  55. package/src/checks/automatic/iframe-focusable-content.js +265 -22
  56. package/src/checks/automatic/iframe-name-present.js +33 -9
  57. package/src/checks/automatic/iframe-title-unique.js +32 -9
  58. package/src/checks/automatic/img-alt-present.js +54 -52
  59. package/src/checks/automatic/input-image-alt-present.js +141 -112
  60. package/src/checks/automatic/label-in-name.js +65 -41
  61. package/src/checks/automatic/language-page-present.js +111 -109
  62. package/src/checks/automatic/link-in-text-block.js +61 -19
  63. package/src/checks/automatic/link-name-present.js +47 -14
  64. package/src/checks/automatic/list-children-valid.js +40 -33
  65. package/src/checks/automatic/listbox-name-present.js +41 -19
  66. package/src/checks/automatic/listitem-parent-valid.js +48 -13
  67. package/src/checks/automatic/menuitem-name-present.js +41 -61
  68. package/src/checks/automatic/meta-refresh-no-exceptions.js +32 -11
  69. package/src/checks/automatic/meta-refresh-timing-absent.js +22 -6
  70. package/src/checks/automatic/meta-viewport-zoom-enabled.js +26 -7
  71. package/src/checks/automatic/meter-name-present.js +40 -36
  72. package/src/checks/automatic/nested-interactive-controls-absent.js +58 -15
  73. package/src/checks/automatic/object-text-alternative-present.js +93 -39
  74. package/src/checks/automatic/option-name-present.js +40 -21
  75. package/src/checks/automatic/page-title-present.js +19 -6
  76. package/src/checks/automatic/progressbar-name-present.js +49 -44
  77. package/src/checks/automatic/role-img-alt-present.js +211 -159
  78. package/src/checks/automatic/searchbox-name-present.js +41 -19
  79. package/src/checks/automatic/server-side-image-map-absent.js +27 -11
  80. package/src/checks/automatic/slider-name-present.js +42 -47
  81. package/src/checks/automatic/spinbutton-name-present.js +41 -19
  82. package/src/checks/automatic/summary-name-present.js +39 -17
  83. package/src/checks/automatic/svg-image-text-alternative-present.js +116 -47
  84. package/src/checks/automatic/svg-text-alternative-present.js +262 -230
  85. package/src/checks/automatic/tab-name-present.js +39 -60
  86. package/src/checks/automatic/table-headers-attr-valid.js +27 -10
  87. package/src/checks/automatic/table-th-has-data-cells.js +24 -8
  88. package/src/checks/automatic/target-size-minimum.js +123 -48
  89. package/src/checks/automatic/td-has-header.js +53 -12
  90. package/src/checks/automatic/textbox-name-present.js +41 -19
  91. package/src/checks/automatic/tooltip-name-present.js +39 -18
  92. package/src/checks/automatic/treeitem-name-present.js +40 -21
  93. package/src/checks/automatic/valid-lang.js +22 -6
  94. package/src/checks/automatic/video-poster-text-alternative-present.js +81 -36
  95. package/src/checks/manual/accesskeys-manual.js +17 -6
  96. package/src/checks/manual/area-alt-decorative-manual.js +194 -193
  97. package/src/checks/manual/area-alt-quality-manual.js +184 -141
  98. package/src/checks/manual/aria-checked-state-mismatch-manual.js +48 -34
  99. package/src/checks/manual/aria-text-manual.js +20 -11
  100. package/src/checks/manual/canvas-text-alternative-quality-manual.js +151 -114
  101. package/src/checks/manual/css-hidden-focus.js +375 -169
  102. package/src/checks/manual/embed-text-alternative-quality-manual.js +178 -162
  103. package/src/checks/manual/empty-heading-manual.js +41 -24
  104. package/src/checks/manual/empty-table-header-manual.js +69 -31
  105. package/src/checks/manual/focus-order-semantics-manual.js +60 -13
  106. package/src/checks/manual/form-control-programmatic-label-quality-manual.js +209 -246
  107. package/src/checks/manual/heading-order-manual.js +50 -8
  108. package/src/checks/manual/identical-links-same-purpose-manual.js +36 -12
  109. package/src/checks/manual/image-redundant-alt-manual.js +38 -8
  110. package/src/checks/manual/img-alt-decorative-manual.js +133 -96
  111. package/src/checks/manual/img-alt-quality-manual.js +178 -127
  112. package/src/checks/manual/input-image-alt-decorative-manual.js +127 -92
  113. package/src/checks/manual/input-image-alt-quality-manual.js +127 -92
  114. package/src/checks/manual/label-title-only-manual.js +44 -28
  115. package/src/checks/manual/landmark-banner-is-top-level-manual.js +95 -38
  116. package/src/checks/manual/landmark-contentinfo-is-top-level-manual.js +85 -32
  117. package/src/checks/manual/landmark-main-is-top-level-manual.js +69 -27
  118. package/src/checks/manual/landmark-no-duplicate-banner-manual.js +45 -33
  119. package/src/checks/manual/landmark-no-duplicate-contentinfo-manual.js +43 -31
  120. package/src/checks/manual/landmark-no-duplicate-main-manual.js +27 -21
  121. package/src/checks/manual/landmark-one-main-manual.js +38 -43
  122. package/src/checks/manual/landmark-unique-manual.js +78 -67
  123. package/src/checks/manual/link-name-quality-manual.js +45 -12
  124. package/src/checks/manual/media-transcript-present-manual.js +37 -22
  125. package/src/checks/manual/meta-viewport-large-manual.js +19 -6
  126. package/src/checks/manual/mouse-only-event-handlers-manual.js +40 -11
  127. package/src/checks/manual/no-autoplay-audio-manual.js +22 -6
  128. package/src/checks/manual/object-text-alternative-quality-manual.js +177 -154
  129. package/src/checks/manual/p-as-heading-manual.js +24 -7
  130. package/src/checks/manual/page-has-heading-one-manual.js +42 -32
  131. package/src/checks/manual/page-title-patterns-manual.js +80 -50
  132. package/src/checks/manual/presentation-role-conflict-manual.js +101 -47
  133. package/src/checks/manual/region-manual.js +244 -60
  134. package/src/checks/manual/scope-attr-valid-manual.js +13 -4
  135. package/src/checks/manual/scrollable-region-focusable-manual.js +39 -11
  136. package/src/checks/manual/skip-link-manual.js +42 -18
  137. package/src/checks/manual/svg-text-alternative-quality-manual.js +208 -165
  138. package/src/checks/manual/tabindex-manual.js +13 -4
  139. package/src/checks/manual/table-duplicate-name-manual.js +22 -11
  140. package/src/checks/manual/table-fake-caption-manual.js +48 -10
  141. package/src/checks/manual/video-caption-manual.js +17 -4
  142. package/src/checks/manual-review.js +58 -12
  143. package/src/core.js +41705 -29650
  144. package/src/index.js +2 -0
  145. package/src/report.js +109 -47
  146. package/src/sarif.js +190 -0
  147. package/surea11y.browser.js +37774 -0
  148. package/bin/core.js +0 -348
  149. package/docs/CLI.md +0 -75
  150. package/src/catalogs/composites.wcag.js +0 -490
  151. package/src/checks/rules-and-tags.full.csv +0 -19
  152. package/src/checks/rules-and-tags.full.json +0 -259
  153. package/src/core/aria-helpers.js +0 -970
  154. package/src/core/contrast-helpers.js +0 -1147
  155. package/src/core/dom-helpers.js +0 -4235
  156. package/src/core/dom-runner.js +0 -671
  157. package/src/core/frame-messaging.js +0 -210
  158. package/src/core/frame-scan.js +0 -178
  159. package/src/core/rollup-composites.js +0 -135
  160. package/src/core/rule-meta.js +0 -159
  161. package/src/coverage/wcag-facets.js +0 -1079
  162. package/src/coverage/wcag-version-map.js +0 -84
  163. package/src/i18n/en.js +0 -923
  164. package/src/i18n/fr.js +0 -844
  165. package/src/policy/contracts.js +0 -18
  166. package/src/policy/resolvePolicy.js +0 -55
  167. package/src/policy/schemas/engine-options.schema.json +0 -103
  168. package/src/policy/schemas/policy-contract.schema.json +0 -40
@@ -1,36 +1,30 @@
1
+ /* SPDX-License-Identifier: MPL-2.0 */
2
+
1
3
  'use strict';
2
4
 
3
5
  /**
4
6
  * @check empty-table-header
5
7
  * @atomic true
6
8
  * @summary Table header cells must not be empty
7
- * @standard Best Practices (a widely-used reference engine's classification; no formal WCAG Success Criterion — see ROADMAP.md Tier 1b)
9
+ * @standard Best Practices (no formal WCAG Success Criterion — see ROADMAP.md Tier 1b)
8
10
  * @applicability
9
11
  * Applies to <th> elements that don't carry a conflicting explicit role,
10
12
  * plus any element (native <th> or not) with role="columnheader" or
11
- * role="rowheader" — matches a widely-used reference engine's own
12
- * `empty-table-header` selector exactly (`th:not([role]), [role="columnheader"], [role="rowheader"]`,
13
- * verified directly against its rule descriptor): a <th> that explicitly
14
- * restates role="columnheader"/"rowheader" is still covered via the second
15
- * clause, but a <th role="presentation"> (no longer meaningfully a header)
16
- * is correctly excluded, and an ARIA-role-only header (e.g. a <div
17
- * role="columnheader"> in a role="grid"/role="table" widget) is caught too
18
- * — found missing entirely via the cross-engine diff tool 2026-07-23.
13
+ * role="rowheader" (`th:not([role]), [role="columnheader"], [role="rowheader"]`):
14
+ * a <th> that explicitly restates role="columnheader"/"rowheader" is still
15
+ * covered via the second clause, but a <th role="presentation"> (no longer
16
+ * meaningfully a header) is correctly excluded, and an ARIA-role-only header
17
+ * (e.g. a <div role="columnheader"> in a role="grid"/role="table" widget) is
18
+ * caught too.
19
19
  * @expectation
20
20
  * The header cell has visible text content. A <th> named only via
21
21
  * aria-label/aria-labelledby (no visible text) is ALSO flagged, not
22
- * treated as equivalent — real screen-reader/browser testing (found via
23
- * a live-DOM cross-engine run 2026-07-21, verified against
24
- * https://butterpep.com/table-header-naming.html and
25
- * https://html5accessibility.com/stuff/2024/05/22/not-so-short-note-on-aria-label-usage-big-table-edition/)
26
- * confirms aria-label support on <th> is genuinely inconsistent in
27
- * practice: NVDA+Firefox and iOS VoiceOver+Safari ignore it entirely
28
- * (only visible text is announced), JAWS+Chrome/IE11 also only announce
29
- * visible text in the header cell itself. Visible text is the one
30
- * mechanism confirmed to work across every tested combination. A widely-used
31
- * reference engine's own equivalent check (`has-visible-text` only, no aria-label/
32
- * aria-labelledby alternative — confirmed directly against its rule
33
- * descriptor) reaches the same conclusion.
22
+ * treated as equivalent — aria-label support on <th> is genuinely
23
+ * inconsistent in practice: NVDA+Firefox and iOS VoiceOver+Safari ignore
24
+ * it entirely (only visible text is announced), JAWS+Chrome/IE11 also only
25
+ * announce visible text in the header cell itself. Visible text is the one
26
+ * mechanism confirmed to work across every tested combination. See
27
+ * https://html5accessibility.com/stuff/2024/05/22/not-so-short-note-on-aria-label-usage-big-table-edition/.
34
28
  * @implementation-notes
35
29
  * - Not WCAG-normative — authored as an advisory, cantTell-capped
36
30
  * `type: 'manual'` rule; see landmark-banner-is-top-level's
@@ -46,7 +40,8 @@ const id = 'empty-table-header';
46
40
 
47
41
  const meta = {
48
42
  title: 'Table header cells must not be empty',
49
- description: 'Checks that table header cells (<th>, or any element with role="columnheader"/"rowheader") have visible text content — a header named only via aria-label/aria-labelledby is also flagged, since real screen-reader/browser support for that is inconsistent.',
43
+ description:
44
+ 'Checks that table header cells (<th>, or any element with role="columnheader"/"rowheader") have visible text content — a header named only via aria-label/aria-labelledby is also flagged, since real screen-reader/browser support for that is inconsistent.',
50
45
  i18n: {
51
46
  titleKey: 'emptyTableHeader_title',
52
47
  descriptionKey: 'emptyTableHeader_description'
@@ -66,11 +61,41 @@ function runInPage(ctx) {
66
61
  const { document, helpers, rule } = ctx;
67
62
 
68
63
  function normalizeWs(s) {
69
- return String(s || '').replace(/\s+/g, ' ').trim();
64
+ return String(s || '')
65
+ .replace(/\s+/g, ' ')
66
+ .trim();
67
+ }
68
+
69
+ const isAccTreeEligible =
70
+ helpers && typeof helpers.isAccTreeEligible === 'function' ? helpers.isAccTreeEligible : null;
71
+
72
+ function isEligible(node) {
73
+ if (!isAccTreeEligible) return true;
74
+ try {
75
+ const r = isAccTreeEligible(node, ctx);
76
+ return !!(r && r.eligible);
77
+ } catch {
78
+ return true;
79
+ }
70
80
  }
71
81
 
82
+ // Plain el.textContent includes text from aria-hidden descendants, which
83
+ // a real screen reader never announces. A <th> whose only text comes from
84
+ // an aria-hidden descendant (e.g. <th><span aria-hidden="true">Name</span
85
+ // ></th>) would otherwise be treated as having visible text, even though
86
+ // AT announces nothing for it.
72
87
  function getVisibleText(el) {
73
- return normalizeWs(el.textContent);
88
+ function walk(node) {
89
+ if (node.nodeType === 3) return node.nodeValue || '';
90
+ if (node.nodeType !== 1) return '';
91
+ if (!isEligible(node)) return '';
92
+ let text = '';
93
+ for (const child of node.childNodes || []) text += walk(child);
94
+ return text;
95
+ }
96
+ let text = '';
97
+ for (const child of el.childNodes || []) text += walk(child);
98
+ return normalizeWs(text);
74
99
  }
75
100
 
76
101
  function getAriaOnlyName(el) {
@@ -94,30 +119,38 @@ function runInPage(ctx) {
94
119
  return '';
95
120
  }
96
121
 
97
- // Matches a widely-used reference engine's own empty-table-header selector exactly: a <th> with
98
- // no conflicting explicit role, plus any element carrying an explicit
99
- // columnheader/rowheader role (native or not).
122
+ // A <th> with no conflicting explicit role, plus any element carrying an
123
+ // explicit columnheader/rowheader role (native or not).
100
124
  const selector = 'th:not([role]), [role="columnheader"], [role="rowheader"]';
101
- const nodes = helpers.queryAllSmart ? helpers.queryAllSmart(selector) : helpers.queryAll(selector);
125
+ const nodes = helpers.queryAllSmart
126
+ ? helpers.queryAllSmart(selector)
127
+ : helpers.queryAll(selector);
102
128
 
103
129
  const occurrences = [];
104
130
  let applicableCount = 0;
105
131
 
106
132
  for (const el of nodes) {
107
133
  if (!el) continue;
134
+
135
+ // A fully aria-hidden header cell isn't part of the AT-perceived
136
+ // table structure at all -- queryAllSmart's default hidden-content
137
+ // policy only excludes "hard" CSS-based hiding, not aria-hidden.
138
+ if (!isEligible(el)) continue;
139
+
108
140
  applicableCount += 1;
109
141
 
110
142
  if (getVisibleText(el)) continue;
111
143
 
112
144
  const stableSelector = helpers.buildSelector ? helpers.buildSelector(el) : 'html';
113
- const html = helpers.getOuterHtmlSnippet ? helpers.getOuterHtmlSnippet(el) : (el.outerHTML || '');
145
+ const html = helpers.getOuterHtmlSnippet ? helpers.getOuterHtmlSnippet(el) : el.outerHTML || '';
114
146
 
115
147
  const ariaName = getAriaOnlyName(el);
116
148
  if (ariaName) {
117
149
  occurrences.push({
118
150
  selector: stableSelector,
119
151
  html,
120
- summary: 'This table header cell has no visible text — its only accessible name comes from aria-label/aria-labelledby, which real screen-reader/browser combinations (e.g. NVDA+Firefox, iOS VoiceOver+Safari) are known to ignore on <th> elements.',
152
+ summary:
153
+ 'This table header cell has no visible text — its only accessible name comes from aria-label/aria-labelledby, which real screen-reader/browser combinations (e.g. NVDA+Firefox, iOS VoiceOver+Safari) are known to ignore on <th> elements.',
121
154
  hint: 'Add visible text content to this header cell (in addition to, or instead of, aria-label/aria-labelledby) — visible text is the only naming mechanism confirmed to work across tested screen readers.',
122
155
  i18n: {
123
156
  summaryKey: 'emptyTableHeader_summary_cantTell_ariaOnly',
@@ -151,7 +184,12 @@ function runInPage(ctx) {
151
184
  return { ruleId: rule.ruleId, outcome: 'notApplicable', severity: 'minor', occurrences: [] };
152
185
  }
153
186
  if (occurrences.length) {
154
- return { ruleId: rule.ruleId, outcome: 'cantTell', severity: rule.defaultSeverity || 'minor', occurrences };
187
+ return {
188
+ ruleId: rule.ruleId,
189
+ outcome: 'cantTell',
190
+ severity: rule.defaultSeverity || 'minor',
191
+ occurrences
192
+ };
155
193
  }
156
194
  return { ruleId: rule.ruleId, outcome: 'notApplicable', severity: 'minor', occurrences: [] };
157
195
  }
@@ -1,10 +1,12 @@
1
+ /* SPDX-License-Identifier: MPL-2.0 */
2
+
1
3
  'use strict';
2
4
 
3
5
  /**
4
6
  * @check focus-order-semantics
5
7
  * @atomic true
6
8
  * @summary Elements added to the tab order should have interactive semantics
7
- * @standard Best Practices (a widely-used reference engine's classification; no formal WCAG Success Criterion — see ROADMAP.md Tier 1b)
9
+ * @standard Best Practices (no formal WCAG Success Criterion — see ROADMAP.md Tier 1b)
8
10
  * @applicability
9
11
  * Elements with an explicit `tabindex` of `0` or greater (in the tab
10
12
  * order) AND an explicit `role` attribute that is one of a curated set
@@ -16,8 +18,8 @@
16
18
  * announce, which is confusing for keyboard users who land on it and
17
19
  * get no indication of what activating it (if anything) would do.
18
20
  * @implementation-notes
19
- * - Not WCAG-normative by a widely-used reference engine's own classification — authored as an
20
- * advisory, cantTell-capped `type: 'manual'` rule.
21
+ * - Not WCAG-normative — authored as an advisory, cantTell-capped
22
+ * `type: 'manual'` rule.
21
23
  * - The non-interactive role list is deliberately curated and
22
24
  * conservative (structural/document roles only) — legitimate custom
23
25
  * widget patterns using `tabindex` with a genuinely interactive role
@@ -25,13 +27,25 @@
25
27
  * `tabindex` and NO role at all are also not flagged: native semantics
26
28
  * or an intentionally generic custom-interactive pattern cannot be
27
29
  * distinguished from markup alone with the same confidence.
30
+ * - `region` is deliberately NOT in the non-interactive role list: a
31
+ * tabbable `role="region"` is a real, WCAG 2.1.1/2.1.3-grounded pattern
32
+ * this engine's own `scrollable-region-focusable` check exists to
33
+ * RECOMMEND (a scrollable landmark with no other focusable content
34
+ * needs `tabindex="0"` to be keyboard-reachable at all) — flagging it
35
+ * here would be internally inconsistent with that sibling check. A
36
+ * `role="region"` is also commonly made tabbable on its own merits
37
+ * (e.g. a cookie-consent banner or notification/toast region a keyboard
38
+ * user should be able to reach directly, as in
39
+ * `<div role="region" tabindex="0">`). Scoped to `region` only;
40
+ * navigation/status/tabpanel remain flagged pending their own evidence.
28
41
  */
29
42
 
30
43
  const id = 'focus-order-semantics';
31
44
 
32
45
  const meta = {
33
46
  title: 'Elements added to the tab order should have interactive semantics',
34
- description: 'Flags elements with tabindex >= 0 whose explicit role is a non-interactive structural/document role (e.g. heading, list, region, presentation), for manual review.',
47
+ description:
48
+ 'Flags elements with tabindex >= 0 whose explicit role is a non-interactive structural/document role (e.g. heading, list, region, presentation), for manual review.',
35
49
  i18n: {
36
50
  titleKey: 'focusOrderSemantics_title',
37
51
  descriptionKey: 'focusOrderSemantics_description'
@@ -48,17 +62,45 @@ const meta = {
48
62
  };
49
63
 
50
64
  function runInPage(ctx) {
51
- const { document, helpers, rule } = ctx;
65
+ const { helpers, rule } = ctx;
52
66
 
53
67
  const NON_INTERACTIVE_ROLES = new Set([
54
- 'generic', 'group', 'text', 'presentation', 'none', 'img', 'heading',
55
- 'region', 'article', 'banner', 'contentinfo', 'main', 'navigation',
56
- 'complementary', 'document', 'note', 'list', 'listitem', 'table',
57
- 'row', 'cell', 'columnheader', 'rowheader', 'figure', 'term',
58
- 'definition', 'paragraph', 'caption', 'status', 'alert', 'log', 'tooltip'
68
+ 'generic',
69
+ 'group',
70
+ 'text',
71
+ 'presentation',
72
+ 'none',
73
+ 'img',
74
+ 'heading',
75
+ 'article',
76
+ 'banner',
77
+ 'contentinfo',
78
+ 'main',
79
+ 'navigation',
80
+ 'complementary',
81
+ 'document',
82
+ 'note',
83
+ 'list',
84
+ 'listitem',
85
+ 'table',
86
+ 'row',
87
+ 'cell',
88
+ 'columnheader',
89
+ 'rowheader',
90
+ 'figure',
91
+ 'term',
92
+ 'definition',
93
+ 'paragraph',
94
+ 'caption',
95
+ 'status',
96
+ 'alert',
97
+ 'log',
98
+ 'tooltip'
59
99
  ]);
60
100
 
61
- const nodes = helpers.queryAllSmart ? helpers.queryAllSmart('[tabindex][role]') : helpers.queryAll('[tabindex][role]');
101
+ const nodes = helpers.queryAllSmart
102
+ ? helpers.queryAllSmart('[tabindex][role]')
103
+ : helpers.queryAll('[tabindex][role]');
62
104
 
63
105
  const occurrences = [];
64
106
  let applicableCount = 0;
@@ -78,7 +120,7 @@ function runInPage(ctx) {
78
120
  if (!NON_INTERACTIVE_ROLES.has(role)) continue;
79
121
 
80
122
  const stableSelector = helpers.buildSelector ? helpers.buildSelector(el) : 'html';
81
- const html = helpers.getOuterHtmlSnippet ? helpers.getOuterHtmlSnippet(el) : (el.outerHTML || '');
123
+ const html = helpers.getOuterHtmlSnippet ? helpers.getOuterHtmlSnippet(el) : el.outerHTML || '';
82
124
 
83
125
  const baseOccurrence = {
84
126
  selector: stableSelector,
@@ -107,7 +149,12 @@ function runInPage(ctx) {
107
149
  }
108
150
 
109
151
  if (occurrences.length) {
110
- return { ruleId: rule.ruleId, outcome: 'cantTell', severity: rule.defaultSeverity || 'minor', occurrences };
152
+ return {
153
+ ruleId: rule.ruleId,
154
+ outcome: 'cantTell',
155
+ severity: rule.defaultSeverity || 'minor',
156
+ occurrences
157
+ };
111
158
  }
112
159
 
113
160
  return { ruleId: rule.ruleId, outcome: 'notApplicable', severity: 'minor', occurrences: [] };