@surea11y/core 1.1.2 → 1.3.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 (160) hide show
  1. package/CHANGELOG.md +48 -4
  2. package/LICENSE +373 -21
  3. package/README.md +70 -1
  4. package/bin/core.js +240 -11
  5. package/docs/API_STABILITY.md +61 -0
  6. package/docs/BASELINE.md +66 -0
  7. package/docs/BINDING_AUTHORS_GUIDE.md +9 -9
  8. package/docs/CI_INTEGRATIONS.md +103 -0
  9. package/docs/CLI.md +80 -1
  10. package/docs/ENGINE_OPTIONS.md +4 -0
  11. package/docs/INTEGRATION.md +19 -1
  12. package/docs/OUTPUT_SCHEMA.md +2 -2
  13. package/docs/REPORT.md +33 -0
  14. package/docs/RULE_AUTHORING.md +31 -0
  15. package/docs/RULE_CATALOG.md +1 -1
  16. package/docs/SARIF.md +59 -0
  17. package/package.json +15 -4
  18. package/src/baseline.js +0 -0
  19. package/src/catalogs/composites.wcag.js +414 -450
  20. package/src/checks/automatic/area-alt-present.js +59 -25
  21. package/src/checks/automatic/aria-allowed-attr.js +193 -33
  22. package/src/checks/automatic/aria-allowed-role.js +21 -7
  23. package/src/checks/automatic/aria-braille-equivalent.js +32 -10
  24. package/src/checks/automatic/aria-conditional-attr.js +24 -7
  25. package/src/checks/automatic/aria-deprecated-role.js +22 -8
  26. package/src/checks/automatic/aria-hidden-body.js +50 -19
  27. package/src/checks/automatic/aria-hidden-focus.js +408 -53
  28. package/src/checks/automatic/aria-prohibited-attr.js +296 -22
  29. package/src/checks/automatic/aria-prohibited-children.js +125 -29
  30. package/src/checks/automatic/aria-required-attr.js +23 -8
  31. package/src/checks/automatic/aria-required-children.js +37 -14
  32. package/src/checks/automatic/aria-required-parent.js +48 -14
  33. package/src/checks/automatic/aria-role-name-present.js +47 -21
  34. package/src/checks/automatic/aria-roles-valid.js +22 -12
  35. package/src/checks/automatic/aria-valid-attr-value.js +28 -7
  36. package/src/checks/automatic/aria-valid-attr.js +17 -5
  37. package/src/checks/automatic/autocomplete-valid.js +74 -16
  38. package/src/checks/automatic/avoid-inline-spacing.js +20 -6
  39. package/src/checks/automatic/binary-control-name-present.js +60 -50
  40. package/src/checks/automatic/button-name-present.js +48 -18
  41. package/src/checks/automatic/bypass-blocks-present.js +50 -25
  42. package/src/checks/automatic/canvas-text-alternative-present.js +57 -26
  43. package/src/checks/automatic/combobox-name-present.js +38 -45
  44. package/src/checks/automatic/contrast-computable.js +361 -341
  45. package/src/checks/automatic/contrast-enhanced.js +487 -466
  46. package/src/checks/automatic/contrast-minimum.js +486 -465
  47. package/src/checks/automatic/css-orientation-lock.js +39 -9
  48. package/src/checks/automatic/definition-list-children-valid.js +40 -19
  49. package/src/checks/automatic/deprecated-elements-not-used.js +21 -7
  50. package/src/checks/automatic/dialog-name-present.js +37 -75
  51. package/src/checks/automatic/dlitem-parent-valid.js +23 -8
  52. package/src/checks/automatic/duplicate-id-aria.js +24 -6
  53. package/src/checks/automatic/embed-text-alternative-present.js +86 -35
  54. package/src/checks/automatic/form-control-programmatic-label-present.js +79 -196
  55. package/src/checks/automatic/form-control-single-label.js +47 -10
  56. package/src/checks/automatic/html-xml-lang-mismatch.js +43 -19
  57. package/src/checks/automatic/iframe-focusable-content.js +26 -11
  58. package/src/checks/automatic/iframe-name-present.js +31 -9
  59. package/src/checks/automatic/iframe-title-unique.js +29 -8
  60. package/src/checks/automatic/img-alt-present.js +47 -43
  61. package/src/checks/automatic/input-image-alt-present.js +143 -112
  62. package/src/checks/automatic/label-in-name.js +50 -22
  63. package/src/checks/automatic/language-page-present.js +117 -109
  64. package/src/checks/automatic/link-in-text-block.js +59 -19
  65. package/src/checks/automatic/link-name-present.js +45 -14
  66. package/src/checks/automatic/list-children-valid.js +26 -9
  67. package/src/checks/automatic/listbox-name-present.js +39 -19
  68. package/src/checks/automatic/listitem-parent-valid.js +18 -6
  69. package/src/checks/automatic/menuitem-name-present.js +39 -61
  70. package/src/checks/automatic/meta-refresh-no-exceptions.js +37 -8
  71. package/src/checks/automatic/meta-refresh-timing-absent.js +28 -6
  72. package/src/checks/automatic/meta-viewport-zoom-enabled.js +32 -7
  73. package/src/checks/automatic/meter-name-present.js +36 -33
  74. package/src/checks/automatic/nested-interactive-controls-absent.js +31 -10
  75. package/src/checks/automatic/object-text-alternative-present.js +91 -39
  76. package/src/checks/automatic/option-name-present.js +38 -21
  77. package/src/checks/automatic/page-title-present.js +26 -7
  78. package/src/checks/automatic/progressbar-name-present.js +41 -34
  79. package/src/checks/automatic/role-img-alt-present.js +209 -157
  80. package/src/checks/automatic/searchbox-name-present.js +39 -19
  81. package/src/checks/automatic/server-side-image-map-absent.js +23 -8
  82. package/src/checks/automatic/slider-name-present.js +40 -47
  83. package/src/checks/automatic/spinbutton-name-present.js +39 -19
  84. package/src/checks/automatic/summary-name-present.js +37 -17
  85. package/src/checks/automatic/svg-image-text-alternative-present.js +114 -47
  86. package/src/checks/automatic/svg-text-alternative-present.js +246 -226
  87. package/src/checks/automatic/tab-name-present.js +37 -60
  88. package/src/checks/automatic/table-headers-attr-valid.js +24 -8
  89. package/src/checks/automatic/table-th-has-data-cells.js +22 -8
  90. package/src/checks/automatic/target-size-minimum.js +118 -48
  91. package/src/checks/automatic/td-has-header.js +29 -11
  92. package/src/checks/automatic/textbox-name-present.js +39 -19
  93. package/src/checks/automatic/tooltip-name-present.js +37 -18
  94. package/src/checks/automatic/treeitem-name-present.js +38 -21
  95. package/src/checks/automatic/valid-lang.js +20 -6
  96. package/src/checks/automatic/video-poster-text-alternative-present.js +79 -36
  97. package/src/checks/manual/accesskeys-manual.js +14 -5
  98. package/src/checks/manual/area-alt-decorative-manual.js +192 -193
  99. package/src/checks/manual/area-alt-quality-manual.js +182 -141
  100. package/src/checks/manual/aria-checked-state-mismatch-manual.js +34 -11
  101. package/src/checks/manual/aria-text-manual.js +14 -6
  102. package/src/checks/manual/canvas-text-alternative-quality-manual.js +149 -114
  103. package/src/checks/manual/css-hidden-focus.js +196 -165
  104. package/src/checks/manual/embed-text-alternative-quality-manual.js +171 -160
  105. package/src/checks/manual/empty-heading-manual.js +24 -12
  106. package/src/checks/manual/empty-table-header-manual.js +21 -14
  107. package/src/checks/manual/focus-order-semantics-manual.js +45 -10
  108. package/src/checks/manual/form-control-programmatic-label-quality-manual.js +207 -246
  109. package/src/checks/manual/heading-order-manual.js +22 -12
  110. package/src/checks/manual/identical-links-same-purpose-manual.js +34 -12
  111. package/src/checks/manual/image-redundant-alt-manual.js +17 -7
  112. package/src/checks/manual/img-alt-decorative-manual.js +131 -96
  113. package/src/checks/manual/img-alt-quality-manual.js +176 -127
  114. package/src/checks/manual/input-image-alt-decorative-manual.js +125 -92
  115. package/src/checks/manual/input-image-alt-quality-manual.js +125 -92
  116. package/src/checks/manual/label-title-only-manual.js +14 -5
  117. package/src/checks/manual/landmark-banner-is-top-level-manual.js +95 -29
  118. package/src/checks/manual/landmark-contentinfo-is-top-level-manual.js +82 -29
  119. package/src/checks/manual/landmark-main-is-top-level-manual.js +64 -23
  120. package/src/checks/manual/landmark-no-duplicate-banner-manual.js +54 -23
  121. package/src/checks/manual/landmark-no-duplicate-contentinfo-manual.js +54 -23
  122. package/src/checks/manual/landmark-no-duplicate-main-manual.js +17 -8
  123. package/src/checks/manual/landmark-one-main-manual.js +35 -21
  124. package/src/checks/manual/landmark-unique-manual.js +73 -45
  125. package/src/checks/manual/link-name-quality-manual.js +43 -12
  126. package/src/checks/manual/media-transcript-present-manual.js +35 -22
  127. package/src/checks/manual/meta-viewport-large-manual.js +25 -6
  128. package/src/checks/manual/mouse-only-event-handlers-manual.js +38 -11
  129. package/src/checks/manual/no-autoplay-audio-manual.js +20 -6
  130. package/src/checks/manual/object-text-alternative-quality-manual.js +175 -154
  131. package/src/checks/manual/p-as-heading-manual.js +22 -7
  132. package/src/checks/manual/page-has-heading-one-manual.js +40 -23
  133. package/src/checks/manual/page-title-patterns-manual.js +87 -51
  134. package/src/checks/manual/presentation-role-conflict-manual.js +59 -19
  135. package/src/checks/manual/region-manual.js +275 -62
  136. package/src/checks/manual/scope-attr-valid-manual.js +11 -9
  137. package/src/checks/manual/scrollable-region-focusable-manual.js +37 -11
  138. package/src/checks/manual/skip-link-manual.js +35 -12
  139. package/src/checks/manual/svg-text-alternative-quality-manual.js +206 -165
  140. package/src/checks/manual/tabindex-manual.js +11 -9
  141. package/src/checks/manual/table-duplicate-name-manual.js +17 -7
  142. package/src/checks/manual/table-fake-caption-manual.js +24 -7
  143. package/src/checks/manual/video-caption-manual.js +15 -4
  144. package/src/checks/manual-review.js +56 -12
  145. package/src/core/aria-helpers.js +1128 -823
  146. package/src/core/contrast-helpers.js +1217 -1062
  147. package/src/core/dom-helpers.js +4176 -3886
  148. package/src/core/dom-runner.js +720 -596
  149. package/src/core/frame-messaging.js +189 -138
  150. package/src/core/frame-scan.js +94 -82
  151. package/src/core/rollup-composites.js +94 -102
  152. package/src/core/rule-meta.js +71 -35
  153. package/src/core.js +37939 -29121
  154. package/src/i18n/en.js +1194 -887
  155. package/src/i18n/fr.js +1136 -793
  156. package/src/policy/contracts.js +13 -13
  157. package/src/policy/resolvePolicy.js +48 -44
  158. package/src/report.js +502 -0
  159. package/src/sarif.js +175 -0
  160. package/surea11y.browser.js +36042 -0
@@ -20,7 +20,8 @@ const id = 'area-alt-present';
20
20
 
21
21
  const meta = {
22
22
  title: '<area> must have an alt attribute',
23
- description: 'Checks that <area> elements provide an alt attribute to support a text alternative mechanism.',
23
+ description:
24
+ 'Checks that <area> elements provide an alt attribute to support a text alternative mechanism.',
24
25
  i18n: {
25
26
  titleKey: 'area_altPresent_title',
26
27
  descriptionKey: 'area_altPresent_description'
@@ -29,7 +30,13 @@ const meta = {
29
30
  tags: ['wcag2a', 'wcag111', 'nontext', 'images', 'imagemap', 'atomic', 'automatic'],
30
31
  wcagSc: ['1.1.1'],
31
32
  normativeMappings: [
32
- { standard: 'WCAG', version: '2.2', requirement: '1.1.1', title: 'Non-text Content', conformanceLevel: 'A' }
33
+ {
34
+ standard: 'WCAG',
35
+ version: '2.2',
36
+ requirement: '1.1.1',
37
+ title: 'Non-text Content',
38
+ conformanceLevel: 'A'
39
+ }
33
40
  ],
34
41
  defaultSeverity: 'serious',
35
42
  category: 'perceivable',
@@ -46,25 +53,29 @@ function runInPage(ctx) {
46
53
  const { document, root, helpers, rule } = ctx;
47
54
  const safeRoot = root || document;
48
55
 
49
- const queryAllSmart = helpers && typeof helpers.queryAllSmart === 'function' ? helpers.queryAllSmart : null;
50
- const queryAll = helpers && typeof helpers.queryAll === 'function'
56
+ const queryAllSmart =
57
+ helpers && typeof helpers.queryAllSmart === 'function' ? helpers.queryAllSmart : null;
58
+ const queryAll =
59
+ helpers && typeof helpers.queryAll === 'function'
51
60
  ? helpers.queryAll
52
61
  : (sel) => {
53
- try { return safeRoot && safeRoot.querySelectorAll ? Array.from(safeRoot.querySelectorAll(sel)) : []; }
54
- catch { return []; }
55
- };
62
+ try {
63
+ return safeRoot && safeRoot.querySelectorAll
64
+ ? Array.from(safeRoot.querySelectorAll(sel))
65
+ : [];
66
+ } catch {
67
+ return [];
68
+ }
69
+ };
56
70
 
57
- const getEligibilityInfo = helpers && typeof helpers.getEligibilityInfo === 'function'
58
- ? helpers.getEligibilityInfo
59
- : null;
71
+ const getEligibilityInfo =
72
+ helpers && typeof helpers.getEligibilityInfo === 'function' ? helpers.getEligibilityInfo : null;
60
73
 
61
- const isAccTreeEligible = helpers && typeof helpers.isAccTreeEligible === 'function'
62
- ? helpers.isAccTreeEligible
63
- : null;
74
+ const isAccTreeEligible =
75
+ helpers && typeof helpers.isAccTreeEligible === 'function' ? helpers.isAccTreeEligible : null;
64
76
 
65
- const getAriaNameInfo = helpers && typeof helpers.getAriaNameInfo === 'function'
66
- ? helpers.getAriaNameInfo
67
- : null;
77
+ const getAriaNameInfo =
78
+ helpers && typeof helpers.getAriaNameInfo === 'function' ? helpers.getAriaNameInfo : null;
68
79
 
69
80
  // --- image-map semantics (rule-local) ---
70
81
 
@@ -72,7 +83,7 @@ function runInPage(ctx) {
72
83
  try {
73
84
  const s = String(val || '').trim();
74
85
  if (!s) return '';
75
- return (s[0] === '#') ? s.slice(1).trim().toLowerCase() : s.toLowerCase();
86
+ return s[0] === '#' ? s.slice(1).trim().toLowerCase() : s.toLowerCase();
76
87
  } catch {
77
88
  return '';
78
89
  }
@@ -92,7 +103,8 @@ function runInPage(ctx) {
92
103
  const __usemapIndex = (() => {
93
104
  const idx = new Map();
94
105
  try {
95
- const imgs = document && document.querySelectorAll ? document.querySelectorAll('img[usemap]') : [];
106
+ const imgs =
107
+ document && document.querySelectorAll ? document.querySelectorAll('img[usemap]') : [];
96
108
  for (const img of imgs) {
97
109
  if (!img || !img.getAttribute) continue;
98
110
  const u = normUsemap(img.getAttribute('usemap'));
@@ -120,8 +132,11 @@ function runInPage(ctx) {
120
132
  }
121
133
 
122
134
  const areas = (() => {
123
- try { return Array.from((queryAllSmart ? queryAllSmart('area') : queryAll('area')) || []); }
124
- catch { return queryAll('area'); }
135
+ try {
136
+ return Array.from((queryAllSmart ? queryAllSmart('area') : queryAll('area')) || []);
137
+ } catch {
138
+ return queryAll('area');
139
+ }
125
140
  })();
126
141
 
127
142
  if (!areas.length) {
@@ -143,7 +158,11 @@ function runInPage(ctx) {
143
158
  // This is the "visibility of map/area doesn't matter; the image does" policy.
144
159
  if (isAccTreeEligible) {
145
160
  const imgElig = (() => {
146
- try { return isAccTreeEligible(img, ctx); } catch { return { eligible: true, reasons: [] }; }
161
+ try {
162
+ return isAccTreeEligible(img, ctx);
163
+ } catch {
164
+ return { eligible: true, reasons: [] };
165
+ }
147
166
  })();
148
167
  if (imgElig && imgElig.eligible === false) continue;
149
168
  }
@@ -151,7 +170,11 @@ function runInPage(ctx) {
151
170
  // 2) The <area> itself must be eligible (aria-hidden/inert exceptions handled by helper).
152
171
  if (isAccTreeEligible) {
153
172
  const elig = (() => {
154
- try { return isAccTreeEligible(el, ctx); } catch { return { eligible: true, reasons: [] }; }
173
+ try {
174
+ return isAccTreeEligible(el, ctx);
175
+ } catch {
176
+ return { eligible: true, reasons: [] };
177
+ }
155
178
  })();
156
179
  if (elig && elig.eligible === false) continue;
157
180
  }
@@ -166,7 +189,7 @@ function runInPage(ctx) {
166
189
  // text-alternative mechanism for <area> (HTML-AAM accessible name
167
190
  // computation includes ARIA naming before falling back to alt).
168
191
  if (getAriaNameInfo) {
169
- let ariaName = null;
192
+ let ariaName;
170
193
  try {
171
194
  ariaName = getAriaNameInfo(el, ctx);
172
195
  } catch {
@@ -182,7 +205,13 @@ function runInPage(ctx) {
182
205
  // sibling fix (2026-07-23, AliExpress's title-only logo <img>) for
183
206
  // the real page this was found via -- same gap, same fix, different
184
207
  // element.
185
- const titleRaw = (() => { try { return el.getAttribute('title'); } catch { return null; } })();
208
+ const titleRaw = (() => {
209
+ try {
210
+ return el.getAttribute('title');
211
+ } catch {
212
+ return null;
213
+ }
214
+ })();
186
215
  if (titleRaw !== null && String(titleRaw).trim()) continue;
187
216
 
188
217
  const eligInfo = getEligibilityInfo ? getEligibilityInfo(el, ctx, { targetSet: 'acc' }) : null;
@@ -219,7 +248,12 @@ function runInPage(ctx) {
219
248
  return { ruleId: rule.ruleId, outcome: 'pass', severity: 'minor', occurrences: [] };
220
249
  }
221
250
 
222
- return { ruleId: rule.ruleId, outcome: 'fail', severity: rule.defaultSeverity || 'minor', occurrences };
251
+ return {
252
+ ruleId: rule.ruleId,
253
+ outcome: 'fail',
254
+ severity: rule.defaultSeverity || 'minor',
255
+ occurrences
256
+ };
223
257
  }
224
258
 
225
259
  module.exports = { id, meta, runInPage };
@@ -43,15 +43,15 @@
43
43
  * composite-widget properties, same family as the already-covered
44
44
  * `tablist`.
45
45
  * - SUPPORTED_ATTRS_BY_ROLE full-reconciliation pass 2026-07-28: the
46
- * 2026-07-21 pass deliberately deferred ~61 roles where the reference
47
- * engine (axe-core) allows `aria-expanded`, treating it as too thin/
46
+ * 2026-07-21 pass deliberately deferred ~61 roles where a widely-used
47
+ * reference engine allows `aria-expanded`, treating it as too thin/
48
48
  * near-universal to import blindly. Checked against `aria-query`
49
49
  * (tracks the published WAI-ARIA 1.2 Recommendation, 6 June 2023 — the
50
50
  * latest actually-published version, as opposed to the in-progress 1.3
51
- * Editor's Draft) instead of axe-core directly, because axe-core's own
51
+ * Editor's Draft) instead of that reference engine directly, because its own
52
52
  * source comments (`// Spec difference: Aria-expanded removed in 1.2`)
53
53
  * show that most of its `aria-expanded` allowances are deliberate
54
- * ARIA-1.1 legacy/AT-compat carryovers axe-core keeps on purpose, not
54
+ * ARIA-1.1 legacy/AT-compat carryovers it keeps on purpose, not
55
55
  * current-spec facts — importing them wholesale would have re-added
56
56
  * exactly the kind of unverified allowance the 07-21 pass was avoiding.
57
57
  * `aria-query`'s per-role tables (with superclass inheritance resolved,
@@ -79,7 +79,8 @@ const id = 'aria-allowed-attr';
79
79
 
80
80
  const meta = {
81
81
  title: 'aria-* attributes must be permitted for the element’s role',
82
- description: 'Checks that every recognized aria-* attribute present on an element with an explicit role is either globally supported or supported by that role.',
82
+ description:
83
+ 'Checks that every recognized aria-* attribute present on an element with an explicit role is either globally supported or supported by that role.',
83
84
  i18n: {
84
85
  titleKey: 'ariaAllowedAttr_title',
85
86
  descriptionKey: 'ariaAllowedAttr_description'
@@ -88,7 +89,13 @@ const meta = {
88
89
  tags: ['wcag2a', 'wcag412', 'aria', 'structure', 'atomic', 'automatic'],
89
90
  wcagSc: ['4.1.2'],
90
91
  normativeMappings: [
91
- { standard: 'WCAG', version: '2.2', requirement: '4.1.2', title: 'Name, Role, Value', conformanceLevel: 'A' }
92
+ {
93
+ standard: 'WCAG',
94
+ version: '2.2',
95
+ requirement: '4.1.2',
96
+ title: 'Name, Role, Value',
97
+ conformanceLevel: 'A'
98
+ }
92
99
  ],
93
100
  defaultSeverity: 'moderate',
94
101
  category: 'robust',
@@ -98,7 +105,7 @@ const meta = {
98
105
  };
99
106
 
100
107
  function runInPage(ctx) {
101
- const { document, helpers, rule } = ctx;
108
+ const { helpers, rule } = ctx;
102
109
 
103
110
  const ariaHelpers = helpers && helpers.aria ? helpers.aria : null;
104
111
  if (!ariaHelpers) {
@@ -111,12 +118,30 @@ function runInPage(ctx) {
111
118
  // build inlines only this function's own source text — see
112
119
  // scripts/build-core.js header ("runInPage MUST be self-contained").
113
120
  const GLOBAL_ATTRS = [
114
- 'aria-atomic', 'aria-braillelabel', 'aria-brailleroledescription', 'aria-busy',
115
- 'aria-controls', 'aria-current', 'aria-describedby', 'aria-description',
116
- 'aria-details', 'aria-disabled', 'aria-dropeffect', 'aria-errormessage',
117
- 'aria-flowto', 'aria-grabbed', 'aria-haspopup', 'aria-hidden', 'aria-invalid',
118
- 'aria-keyshortcuts', 'aria-label', 'aria-labelledby', 'aria-live', 'aria-owns',
119
- 'aria-relevant', 'aria-roledescription'
121
+ 'aria-atomic',
122
+ 'aria-braillelabel',
123
+ 'aria-brailleroledescription',
124
+ 'aria-busy',
125
+ 'aria-controls',
126
+ 'aria-current',
127
+ 'aria-describedby',
128
+ 'aria-description',
129
+ 'aria-details',
130
+ 'aria-disabled',
131
+ 'aria-dropeffect',
132
+ 'aria-errormessage',
133
+ 'aria-flowto',
134
+ 'aria-grabbed',
135
+ 'aria-haspopup',
136
+ 'aria-hidden',
137
+ 'aria-invalid',
138
+ 'aria-keyshortcuts',
139
+ 'aria-label',
140
+ 'aria-labelledby',
141
+ 'aria-live',
142
+ 'aria-owns',
143
+ 'aria-relevant',
144
+ 'aria-roledescription'
120
145
  ];
121
146
 
122
147
  // Per-role supported (non-global) states/properties. Deliberately
@@ -126,43 +151,173 @@ function runInPage(ctx) {
126
151
  const SUPPORTED_ATTRS_BY_ROLE = {
127
152
  alertdialog: ['aria-modal'],
128
153
  checkbox: ['aria-checked', 'aria-readonly', 'aria-required', 'aria-expanded'],
129
- columnheader: ['aria-sort', 'aria-colindex', 'aria-colspan', 'aria-readonly', 'aria-required', 'aria-rowindex', 'aria-rowspan', 'aria-selected', 'aria-expanded'],
130
- combobox: ['aria-expanded', 'aria-autocomplete', 'aria-readonly', 'aria-required', 'aria-activedescendant'],
154
+ columnheader: [
155
+ 'aria-sort',
156
+ 'aria-colindex',
157
+ 'aria-colspan',
158
+ 'aria-readonly',
159
+ 'aria-required',
160
+ 'aria-rowindex',
161
+ 'aria-rowspan',
162
+ 'aria-selected',
163
+ 'aria-expanded'
164
+ ],
165
+ combobox: [
166
+ 'aria-expanded',
167
+ 'aria-autocomplete',
168
+ 'aria-readonly',
169
+ 'aria-required',
170
+ 'aria-activedescendant'
171
+ ],
131
172
  dialog: ['aria-modal'],
132
- grid: ['aria-multiselectable', 'aria-readonly', 'aria-colcount', 'aria-rowcount', 'aria-activedescendant'],
133
- gridcell: ['aria-selected', 'aria-readonly', 'aria-required', 'aria-colindex', 'aria-colspan', 'aria-rowindex', 'aria-rowspan', 'aria-expanded'],
173
+ grid: [
174
+ 'aria-multiselectable',
175
+ 'aria-readonly',
176
+ 'aria-colcount',
177
+ 'aria-rowcount',
178
+ 'aria-activedescendant'
179
+ ],
180
+ gridcell: [
181
+ 'aria-selected',
182
+ 'aria-readonly',
183
+ 'aria-required',
184
+ 'aria-colindex',
185
+ 'aria-colspan',
186
+ 'aria-rowindex',
187
+ 'aria-rowspan',
188
+ 'aria-expanded'
189
+ ],
134
190
  heading: ['aria-level'],
135
- listbox: ['aria-multiselectable', 'aria-readonly', 'aria-required', 'aria-orientation', 'aria-expanded', 'aria-activedescendant'],
191
+ listbox: [
192
+ 'aria-multiselectable',
193
+ 'aria-readonly',
194
+ 'aria-required',
195
+ 'aria-orientation',
196
+ 'aria-expanded',
197
+ 'aria-activedescendant'
198
+ ],
136
199
  listitem: ['aria-level', 'aria-posinset', 'aria-setsize'],
137
200
  menu: ['aria-activedescendant', 'aria-orientation'],
138
201
  menubar: ['aria-activedescendant', 'aria-orientation'],
139
- menuitemcheckbox: ['aria-checked', 'aria-expanded', 'aria-readonly', 'aria-required', 'aria-posinset', 'aria-setsize'],
140
- menuitemradio: ['aria-checked', 'aria-expanded', 'aria-readonly', 'aria-required', 'aria-posinset', 'aria-setsize'],
202
+ menuitemcheckbox: [
203
+ 'aria-checked',
204
+ 'aria-expanded',
205
+ 'aria-readonly',
206
+ 'aria-required',
207
+ 'aria-posinset',
208
+ 'aria-setsize'
209
+ ],
210
+ menuitemradio: [
211
+ 'aria-checked',
212
+ 'aria-expanded',
213
+ 'aria-readonly',
214
+ 'aria-required',
215
+ 'aria-posinset',
216
+ 'aria-setsize'
217
+ ],
141
218
  meter: ['aria-valuenow', 'aria-valuemin', 'aria-valuemax', 'aria-valuetext'],
142
219
  option: ['aria-selected', 'aria-checked', 'aria-posinset', 'aria-setsize'],
143
220
  progressbar: ['aria-valuenow', 'aria-valuemin', 'aria-valuemax', 'aria-valuetext'],
144
221
  radio: ['aria-checked', 'aria-posinset', 'aria-setsize'],
145
222
  radiogroup: ['aria-readonly', 'aria-required', 'aria-orientation', 'aria-activedescendant'],
146
- row: ['aria-selected', 'aria-level', 'aria-posinset', 'aria-setsize', 'aria-colindex', 'aria-rowindex', 'aria-expanded', 'aria-activedescendant'],
147
- rowheader: ['aria-sort', 'aria-colindex', 'aria-colspan', 'aria-readonly', 'aria-required', 'aria-rowindex', 'aria-rowspan', 'aria-selected', 'aria-expanded'],
148
- scrollbar: ['aria-valuenow', 'aria-valuemin', 'aria-valuemax', 'aria-valuetext', 'aria-orientation', 'aria-controls'],
149
- searchbox: ['aria-activedescendant', 'aria-autocomplete', 'aria-multiline', 'aria-placeholder', 'aria-readonly', 'aria-required'],
150
- separator: ['aria-valuenow', 'aria-valuemin', 'aria-valuemax', 'aria-valuetext', 'aria-orientation'],
151
- slider: ['aria-valuenow', 'aria-valuemin', 'aria-valuemax', 'aria-valuetext', 'aria-orientation', 'aria-readonly'],
152
- spinbutton: ['aria-valuenow', 'aria-valuemin', 'aria-valuemax', 'aria-valuetext', 'aria-readonly', 'aria-required', 'aria-activedescendant'],
223
+ row: [
224
+ 'aria-selected',
225
+ 'aria-level',
226
+ 'aria-posinset',
227
+ 'aria-setsize',
228
+ 'aria-colindex',
229
+ 'aria-rowindex',
230
+ 'aria-expanded',
231
+ 'aria-activedescendant'
232
+ ],
233
+ rowheader: [
234
+ 'aria-sort',
235
+ 'aria-colindex',
236
+ 'aria-colspan',
237
+ 'aria-readonly',
238
+ 'aria-required',
239
+ 'aria-rowindex',
240
+ 'aria-rowspan',
241
+ 'aria-selected',
242
+ 'aria-expanded'
243
+ ],
244
+ scrollbar: [
245
+ 'aria-valuenow',
246
+ 'aria-valuemin',
247
+ 'aria-valuemax',
248
+ 'aria-valuetext',
249
+ 'aria-orientation',
250
+ 'aria-controls'
251
+ ],
252
+ searchbox: [
253
+ 'aria-activedescendant',
254
+ 'aria-autocomplete',
255
+ 'aria-multiline',
256
+ 'aria-placeholder',
257
+ 'aria-readonly',
258
+ 'aria-required'
259
+ ],
260
+ separator: [
261
+ 'aria-valuenow',
262
+ 'aria-valuemin',
263
+ 'aria-valuemax',
264
+ 'aria-valuetext',
265
+ 'aria-orientation'
266
+ ],
267
+ slider: [
268
+ 'aria-valuenow',
269
+ 'aria-valuemin',
270
+ 'aria-valuemax',
271
+ 'aria-valuetext',
272
+ 'aria-orientation',
273
+ 'aria-readonly'
274
+ ],
275
+ spinbutton: [
276
+ 'aria-valuenow',
277
+ 'aria-valuemin',
278
+ 'aria-valuemax',
279
+ 'aria-valuetext',
280
+ 'aria-readonly',
281
+ 'aria-required',
282
+ 'aria-activedescendant'
283
+ ],
153
284
  switch: ['aria-checked', 'aria-expanded', 'aria-readonly', 'aria-required'],
154
285
  tab: ['aria-selected', 'aria-expanded', 'aria-posinset', 'aria-setsize'],
155
286
  table: ['aria-colcount', 'aria-rowcount'],
156
287
  tablist: ['aria-multiselectable', 'aria-orientation', 'aria-level', 'aria-activedescendant'],
157
- textbox: ['aria-activedescendant', 'aria-autocomplete', 'aria-multiline', 'aria-placeholder', 'aria-readonly', 'aria-required'],
288
+ textbox: [
289
+ 'aria-activedescendant',
290
+ 'aria-autocomplete',
291
+ 'aria-multiline',
292
+ 'aria-placeholder',
293
+ 'aria-readonly',
294
+ 'aria-required'
295
+ ],
158
296
  toolbar: ['aria-activedescendant', 'aria-orientation'],
159
297
  tree: ['aria-multiselectable', 'aria-required', 'aria-orientation', 'aria-activedescendant'],
160
- treegrid: ['aria-multiselectable', 'aria-readonly', 'aria-required', 'aria-orientation', 'aria-colcount', 'aria-rowcount', 'aria-activedescendant'],
161
- treeitem: ['aria-checked', 'aria-selected', 'aria-expanded', 'aria-level', 'aria-posinset', 'aria-setsize']
298
+ treegrid: [
299
+ 'aria-multiselectable',
300
+ 'aria-readonly',
301
+ 'aria-required',
302
+ 'aria-orientation',
303
+ 'aria-colcount',
304
+ 'aria-rowcount',
305
+ 'aria-activedescendant'
306
+ ],
307
+ treeitem: [
308
+ 'aria-checked',
309
+ 'aria-selected',
310
+ 'aria-expanded',
311
+ 'aria-level',
312
+ 'aria-posinset',
313
+ 'aria-setsize'
314
+ ]
162
315
  };
163
316
 
164
317
  const globalSet = new Set(GLOBAL_ATTRS);
165
- const nodes = helpers.queryAllSmart ? helpers.queryAllSmart('[role]') : helpers.queryAll('[role]');
318
+ const nodes = helpers.queryAllSmart
319
+ ? helpers.queryAllSmart('[role]')
320
+ : helpers.queryAll('[role]');
166
321
 
167
322
  const occurrences = [];
168
323
  let applicableCount = 0;
@@ -198,7 +353,7 @@ function runInPage(ctx) {
198
353
  if (!disallowed || !disallowed.length) continue;
199
354
 
200
355
  const stableSelector = helpers.buildSelector ? helpers.buildSelector(el) : 'html';
201
- const html = helpers.getOuterHtmlSnippet ? helpers.getOuterHtmlSnippet(el) : (el.outerHTML || '');
356
+ const html = helpers.getOuterHtmlSnippet ? helpers.getOuterHtmlSnippet(el) : el.outerHTML || '';
202
357
 
203
358
  for (const name of disallowed) {
204
359
  occurrences.push({
@@ -222,7 +377,12 @@ function runInPage(ctx) {
222
377
  return { ruleId: rule.ruleId, outcome: 'notApplicable', severity: 'minor', occurrences: [] };
223
378
  }
224
379
  if (occurrences.length) {
225
- return { ruleId: rule.ruleId, outcome: 'fail', severity: rule.defaultSeverity || 'moderate', occurrences };
380
+ return {
381
+ ruleId: rule.ruleId,
382
+ outcome: 'fail',
383
+ severity: rule.defaultSeverity || 'moderate',
384
+ occurrences
385
+ };
226
386
  }
227
387
  return { ruleId: rule.ruleId, outcome: 'pass', severity: 'minor', occurrences: [] };
228
388
  }
@@ -27,7 +27,8 @@ const id = 'aria-allowed-role';
27
27
 
28
28
  const meta = {
29
29
  title: 'Explicit role must be permitted for its host element',
30
- description: 'Checks that an explicit role="" attribute is one of the roles the ARIA-in-HTML specification permits for the host element (e.g. role="tab" is not permitted on <nav>).',
30
+ description:
31
+ 'Checks that an explicit role="" attribute is one of the roles the ARIA-in-HTML specification permits for the host element (e.g. role="tab" is not permitted on <nav>).',
31
32
  i18n: {
32
33
  titleKey: 'ariaAllowedRole_title',
33
34
  descriptionKey: 'ariaAllowedRole_description'
@@ -36,7 +37,13 @@ const meta = {
36
37
  tags: ['wcag2a', 'wcag412', 'aria', 'structure', 'atomic', 'automatic'],
37
38
  wcagSc: ['4.1.2'],
38
39
  normativeMappings: [
39
- { standard: 'WCAG', version: '2.2', requirement: '4.1.2', title: 'Name, Role, Value', conformanceLevel: 'A' }
40
+ {
41
+ standard: 'WCAG',
42
+ version: '2.2',
43
+ requirement: '4.1.2',
44
+ title: 'Name, Role, Value',
45
+ conformanceLevel: 'A'
46
+ }
40
47
  ],
41
48
  defaultSeverity: 'moderate',
42
49
  category: 'robust',
@@ -46,14 +53,16 @@ const meta = {
46
53
  };
47
54
 
48
55
  function runInPage(ctx) {
49
- const { document, helpers, rule } = ctx;
56
+ const { helpers, rule } = ctx;
50
57
 
51
58
  const ariaHelpers = helpers && helpers.aria ? helpers.aria : null;
52
59
  if (!ariaHelpers) {
53
60
  return { ruleId: rule.ruleId, outcome: 'notApplicable', severity: 'minor', occurrences: [] };
54
61
  }
55
62
 
56
- const nodes = helpers.queryAllSmart ? helpers.queryAllSmart('[role]') : helpers.queryAll('[role]');
63
+ const nodes = helpers.queryAllSmart
64
+ ? helpers.queryAllSmart('[role]')
65
+ : helpers.queryAll('[role]');
57
66
 
58
67
  const occurrences = [];
59
68
  let applicableCount = 0;
@@ -72,7 +81,7 @@ function runInPage(ctx) {
72
81
  if (info.allowed) continue;
73
82
 
74
83
  const stableSelector = helpers.buildSelector ? helpers.buildSelector(el) : 'html';
75
- const html = helpers.getOuterHtmlSnippet ? helpers.getOuterHtmlSnippet(el) : (el.outerHTML || '');
84
+ const html = helpers.getOuterHtmlSnippet ? helpers.getOuterHtmlSnippet(el) : el.outerHTML || '';
76
85
  const tag = (el.tagName || '').toLowerCase();
77
86
 
78
87
  occurrences.push({
@@ -95,9 +104,14 @@ function runInPage(ctx) {
95
104
  return { ruleId: rule.ruleId, outcome: 'notApplicable', severity: 'minor', occurrences: [] };
96
105
  }
97
106
  if (occurrences.length) {
98
- return { ruleId: rule.ruleId, outcome: 'fail', severity: rule.defaultSeverity || 'moderate', occurrences };
107
+ return {
108
+ ruleId: rule.ruleId,
109
+ outcome: 'fail',
110
+ severity: rule.defaultSeverity || 'moderate',
111
+ occurrences
112
+ };
99
113
  }
100
114
  return { ruleId: rule.ruleId, outcome: 'pass', severity: 'minor', occurrences: [] };
101
115
  }
102
116
 
103
- module.exports = { id, meta, runInPage };
117
+ module.exports = { id, meta, runInPage };
@@ -42,7 +42,13 @@ const meta = {
42
42
  tags: ['wcag2a', 'wcag412', 'aria', 'structure', 'atomic', 'automatic'],
43
43
  wcagSc: ['4.1.2'],
44
44
  normativeMappings: [
45
- { standard: 'WCAG', version: '2.2', requirement: '4.1.2', title: 'Name, Role, Value', conformanceLevel: 'A' }
45
+ {
46
+ standard: 'WCAG',
47
+ version: '2.2',
48
+ requirement: '4.1.2',
49
+ title: 'Name, Role, Value',
50
+ conformanceLevel: 'A'
51
+ }
46
52
  ],
47
53
  defaultSeverity: 'serious',
48
54
  category: 'robust',
@@ -52,9 +58,11 @@ const meta = {
52
58
  };
53
59
 
54
60
  function runInPage(ctx) {
55
- const { document, helpers, rule } = ctx;
61
+ const { helpers, rule } = ctx;
56
62
 
57
- function trim(v) { return (v == null ? '' : String(v)).trim(); }
63
+ function trim(v) {
64
+ return (v == null ? '' : String(v)).trim();
65
+ }
58
66
 
59
67
  function getConservativeSubtreeText(container) {
60
68
  // "Name from content" — recurses into descendants and uses each one's
@@ -68,7 +76,7 @@ function runInPage(ctx) {
68
76
  const info = helpers.getContentNameInfo(container, ctx);
69
77
  return info && info.present ? info.value : '';
70
78
  }
71
- const t = (container && container.textContent) ? String(container.textContent) : '';
79
+ const t = container && container.textContent ? String(container.textContent) : '';
72
80
  return t.replace(/\s+/g, ' ').trim();
73
81
  }
74
82
 
@@ -91,21 +99,26 @@ function runInPage(ctx) {
91
99
  const missing = [];
92
100
 
93
101
  if (brailleLabel) {
94
- const nameInfo = helpers.getAccessibleNameInfo ? helpers.getAccessibleNameInfo(el, ctx) : null;
95
- const programmaticName = trim(nameInfo && typeof nameInfo.value === 'string' ? nameInfo.value : '');
102
+ const nameInfo = helpers.getAccessibleNameInfo
103
+ ? helpers.getAccessibleNameInfo(el, ctx)
104
+ : null;
105
+ const programmaticName = trim(
106
+ nameInfo && typeof nameInfo.value === 'string' ? nameInfo.value : ''
107
+ );
96
108
  const name = programmaticName || getConservativeSubtreeText(el);
97
109
  if (!name) missing.push({ attr: 'aria-braillelabel', requires: 'an accessible name' });
98
110
  }
99
111
 
100
112
  if (brailleRoleDesc) {
101
113
  const roleDesc = trim(el.getAttribute('aria-roledescription'));
102
- if (!roleDesc) missing.push({ attr: 'aria-brailleroledescription', requires: 'aria-roledescription' });
114
+ if (!roleDesc)
115
+ missing.push({ attr: 'aria-brailleroledescription', requires: 'aria-roledescription' });
103
116
  }
104
117
 
105
118
  if (!missing.length) continue;
106
119
 
107
120
  const stableSelector = helpers.buildSelector ? helpers.buildSelector(el) : 'html';
108
- const html = helpers.getOuterHtmlSnippet ? helpers.getOuterHtmlSnippet(el) : (el.outerHTML || '');
121
+ const html = helpers.getOuterHtmlSnippet ? helpers.getOuterHtmlSnippet(el) : el.outerHTML || '';
109
122
  const tag = (el.tagName || '').toLowerCase();
110
123
 
111
124
  for (const m of missing) {
@@ -120,7 +133,11 @@ function runInPage(ctx) {
120
133
  params: { element: tag, attr: m.attr, requires: m.requires }
121
134
  },
122
135
  data: {
123
- details: { reasonCode: 'BRAILLE_ATTR_WITHOUT_EQUIVALENT', attr: m.attr, requires: m.requires }
136
+ details: {
137
+ reasonCode: 'BRAILLE_ATTR_WITHOUT_EQUIVALENT',
138
+ attr: m.attr,
139
+ requires: m.requires
140
+ }
124
141
  }
125
142
  });
126
143
  }
@@ -130,7 +147,12 @@ function runInPage(ctx) {
130
147
  return { ruleId: rule.ruleId, outcome: 'notApplicable', severity: 'minor', occurrences: [] };
131
148
  }
132
149
  if (occurrences.length) {
133
- return { ruleId: rule.ruleId, outcome: 'fail', severity: rule.defaultSeverity || 'serious', occurrences };
150
+ return {
151
+ ruleId: rule.ruleId,
152
+ outcome: 'fail',
153
+ severity: rule.defaultSeverity || 'serious',
154
+ occurrences
155
+ };
134
156
  }
135
157
  return { ruleId: rule.ruleId, outcome: 'pass', severity: 'minor', occurrences: [] };
136
158
  }