@surea11y/core 1.2.0 → 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 (157) hide show
  1. package/CHANGELOG.md +37 -7
  2. package/LICENSE +373 -21
  3. package/README.md +67 -1
  4. package/bin/core.js +144 -19
  5. package/docs/API_STABILITY.md +1 -1
  6. package/docs/BINDING_AUTHORS_GUIDE.md +9 -9
  7. package/docs/CI_INTEGRATIONS.md +103 -0
  8. package/docs/CLI.md +54 -1
  9. package/docs/ENGINE_OPTIONS.md +2 -0
  10. package/docs/INTEGRATION.md +18 -0
  11. package/docs/OUTPUT_SCHEMA.md +1 -1
  12. package/docs/RULE_CATALOG.md +1 -1
  13. package/docs/SARIF.md +59 -0
  14. package/package.json +15 -4
  15. package/src/baseline.js +0 -0
  16. package/src/catalogs/composites.wcag.js +414 -450
  17. package/src/checks/automatic/area-alt-present.js +59 -25
  18. package/src/checks/automatic/aria-allowed-attr.js +193 -33
  19. package/src/checks/automatic/aria-allowed-role.js +21 -7
  20. package/src/checks/automatic/aria-braille-equivalent.js +32 -10
  21. package/src/checks/automatic/aria-conditional-attr.js +24 -7
  22. package/src/checks/automatic/aria-deprecated-role.js +22 -8
  23. package/src/checks/automatic/aria-hidden-body.js +42 -19
  24. package/src/checks/automatic/aria-hidden-focus.js +408 -53
  25. package/src/checks/automatic/aria-prohibited-attr.js +296 -22
  26. package/src/checks/automatic/aria-prohibited-children.js +55 -16
  27. package/src/checks/automatic/aria-required-attr.js +23 -8
  28. package/src/checks/automatic/aria-required-children.js +37 -14
  29. package/src/checks/automatic/aria-required-parent.js +48 -14
  30. package/src/checks/automatic/aria-role-name-present.js +47 -21
  31. package/src/checks/automatic/aria-roles-valid.js +22 -12
  32. package/src/checks/automatic/aria-valid-attr-value.js +28 -7
  33. package/src/checks/automatic/aria-valid-attr.js +17 -5
  34. package/src/checks/automatic/autocomplete-valid.js +74 -16
  35. package/src/checks/automatic/avoid-inline-spacing.js +20 -6
  36. package/src/checks/automatic/binary-control-name-present.js +60 -50
  37. package/src/checks/automatic/button-name-present.js +48 -18
  38. package/src/checks/automatic/bypass-blocks-present.js +42 -25
  39. package/src/checks/automatic/canvas-text-alternative-present.js +57 -26
  40. package/src/checks/automatic/combobox-name-present.js +38 -45
  41. package/src/checks/automatic/contrast-computable.js +361 -341
  42. package/src/checks/automatic/contrast-enhanced.js +487 -466
  43. package/src/checks/automatic/contrast-minimum.js +486 -465
  44. package/src/checks/automatic/css-orientation-lock.js +30 -8
  45. package/src/checks/automatic/definition-list-children-valid.js +40 -19
  46. package/src/checks/automatic/deprecated-elements-not-used.js +21 -7
  47. package/src/checks/automatic/dialog-name-present.js +37 -75
  48. package/src/checks/automatic/dlitem-parent-valid.js +23 -8
  49. package/src/checks/automatic/duplicate-id-aria.js +24 -6
  50. package/src/checks/automatic/embed-text-alternative-present.js +86 -35
  51. package/src/checks/automatic/form-control-programmatic-label-present.js +79 -196
  52. package/src/checks/automatic/form-control-single-label.js +47 -10
  53. package/src/checks/automatic/html-xml-lang-mismatch.js +34 -18
  54. package/src/checks/automatic/iframe-focusable-content.js +26 -11
  55. package/src/checks/automatic/iframe-name-present.js +31 -9
  56. package/src/checks/automatic/iframe-title-unique.js +29 -8
  57. package/src/checks/automatic/img-alt-present.js +47 -43
  58. package/src/checks/automatic/input-image-alt-present.js +143 -112
  59. package/src/checks/automatic/label-in-name.js +50 -22
  60. package/src/checks/automatic/language-page-present.js +109 -109
  61. package/src/checks/automatic/link-in-text-block.js +59 -19
  62. package/src/checks/automatic/link-name-present.js +45 -14
  63. package/src/checks/automatic/list-children-valid.js +26 -9
  64. package/src/checks/automatic/listbox-name-present.js +39 -19
  65. package/src/checks/automatic/listitem-parent-valid.js +18 -6
  66. package/src/checks/automatic/menuitem-name-present.js +39 -61
  67. package/src/checks/automatic/meta-refresh-no-exceptions.js +28 -7
  68. package/src/checks/automatic/meta-refresh-timing-absent.js +20 -6
  69. package/src/checks/automatic/meta-viewport-zoom-enabled.js +24 -7
  70. package/src/checks/automatic/meter-name-present.js +36 -33
  71. package/src/checks/automatic/nested-interactive-controls-absent.js +31 -10
  72. package/src/checks/automatic/object-text-alternative-present.js +91 -39
  73. package/src/checks/automatic/option-name-present.js +38 -21
  74. package/src/checks/automatic/page-title-present.js +17 -6
  75. package/src/checks/automatic/progressbar-name-present.js +41 -34
  76. package/src/checks/automatic/role-img-alt-present.js +209 -157
  77. package/src/checks/automatic/searchbox-name-present.js +39 -19
  78. package/src/checks/automatic/server-side-image-map-absent.js +23 -8
  79. package/src/checks/automatic/slider-name-present.js +40 -47
  80. package/src/checks/automatic/spinbutton-name-present.js +39 -19
  81. package/src/checks/automatic/summary-name-present.js +37 -17
  82. package/src/checks/automatic/svg-image-text-alternative-present.js +114 -47
  83. package/src/checks/automatic/svg-text-alternative-present.js +246 -226
  84. package/src/checks/automatic/tab-name-present.js +37 -60
  85. package/src/checks/automatic/table-headers-attr-valid.js +24 -8
  86. package/src/checks/automatic/table-th-has-data-cells.js +22 -8
  87. package/src/checks/automatic/target-size-minimum.js +118 -48
  88. package/src/checks/automatic/td-has-header.js +29 -11
  89. package/src/checks/automatic/textbox-name-present.js +39 -19
  90. package/src/checks/automatic/tooltip-name-present.js +37 -18
  91. package/src/checks/automatic/treeitem-name-present.js +38 -21
  92. package/src/checks/automatic/valid-lang.js +20 -6
  93. package/src/checks/automatic/video-poster-text-alternative-present.js +79 -36
  94. package/src/checks/manual/accesskeys-manual.js +14 -5
  95. package/src/checks/manual/area-alt-decorative-manual.js +192 -193
  96. package/src/checks/manual/area-alt-quality-manual.js +182 -141
  97. package/src/checks/manual/aria-checked-state-mismatch-manual.js +34 -11
  98. package/src/checks/manual/aria-text-manual.js +14 -6
  99. package/src/checks/manual/canvas-text-alternative-quality-manual.js +149 -114
  100. package/src/checks/manual/css-hidden-focus.js +196 -165
  101. package/src/checks/manual/embed-text-alternative-quality-manual.js +171 -160
  102. package/src/checks/manual/empty-heading-manual.js +24 -7
  103. package/src/checks/manual/empty-table-header-manual.js +17 -6
  104. package/src/checks/manual/focus-order-semantics-manual.js +45 -10
  105. package/src/checks/manual/form-control-programmatic-label-quality-manual.js +207 -246
  106. package/src/checks/manual/heading-order-manual.js +22 -7
  107. package/src/checks/manual/identical-links-same-purpose-manual.js +34 -12
  108. package/src/checks/manual/image-redundant-alt-manual.js +17 -7
  109. package/src/checks/manual/img-alt-decorative-manual.js +131 -96
  110. package/src/checks/manual/img-alt-quality-manual.js +176 -127
  111. package/src/checks/manual/input-image-alt-decorative-manual.js +125 -92
  112. package/src/checks/manual/input-image-alt-quality-manual.js +125 -92
  113. package/src/checks/manual/label-title-only-manual.js +14 -5
  114. package/src/checks/manual/landmark-banner-is-top-level-manual.js +66 -16
  115. package/src/checks/manual/landmark-contentinfo-is-top-level-manual.js +53 -16
  116. package/src/checks/manual/landmark-main-is-top-level-manual.js +35 -10
  117. package/src/checks/manual/landmark-no-duplicate-banner-manual.js +29 -10
  118. package/src/checks/manual/landmark-no-duplicate-contentinfo-manual.js +29 -10
  119. package/src/checks/manual/landmark-no-duplicate-main-manual.js +17 -8
  120. package/src/checks/manual/landmark-one-main-manual.js +26 -20
  121. package/src/checks/manual/landmark-unique-manual.js +41 -15
  122. package/src/checks/manual/link-name-quality-manual.js +43 -12
  123. package/src/checks/manual/media-transcript-present-manual.js +35 -22
  124. package/src/checks/manual/meta-viewport-large-manual.js +16 -5
  125. package/src/checks/manual/mouse-only-event-handlers-manual.js +38 -11
  126. package/src/checks/manual/no-autoplay-audio-manual.js +20 -6
  127. package/src/checks/manual/object-text-alternative-quality-manual.js +175 -154
  128. package/src/checks/manual/p-as-heading-manual.js +22 -7
  129. package/src/checks/manual/page-has-heading-one-manual.js +31 -22
  130. package/src/checks/manual/page-title-patterns-manual.js +78 -50
  131. package/src/checks/manual/presentation-role-conflict-manual.js +59 -19
  132. package/src/checks/manual/region-manual.js +241 -48
  133. package/src/checks/manual/scope-attr-valid-manual.js +10 -3
  134. package/src/checks/manual/scrollable-region-focusable-manual.js +37 -11
  135. package/src/checks/manual/skip-link-manual.js +35 -12
  136. package/src/checks/manual/svg-text-alternative-quality-manual.js +206 -165
  137. package/src/checks/manual/tabindex-manual.js +10 -3
  138. package/src/checks/manual/table-duplicate-name-manual.js +17 -7
  139. package/src/checks/manual/table-fake-caption-manual.js +24 -7
  140. package/src/checks/manual/video-caption-manual.js +15 -4
  141. package/src/checks/manual-review.js +56 -12
  142. package/src/core/aria-helpers.js +1127 -886
  143. package/src/core/contrast-helpers.js +1217 -1062
  144. package/src/core/dom-helpers.js +4175 -3917
  145. package/src/core/dom-runner.js +720 -604
  146. package/src/core/frame-messaging.js +189 -138
  147. package/src/core/frame-scan.js +94 -82
  148. package/src/core/rollup-composites.js +94 -102
  149. package/src/core/rule-meta.js +58 -41
  150. package/src/core.js +36552 -29111
  151. package/src/i18n/en.js +1194 -889
  152. package/src/i18n/fr.js +1136 -795
  153. package/src/policy/contracts.js +13 -13
  154. package/src/policy/resolvePolicy.js +48 -44
  155. package/src/report.js +63 -43
  156. package/src/sarif.js +175 -0
  157. package/surea11y.browser.js +36042 -0
@@ -61,7 +61,8 @@ const id = 'aria-required-children';
61
61
 
62
62
  const meta = {
63
63
  title: 'Container roles must own at least one required child role',
64
- description: 'Checks that container roles with a documented "required owned elements" entry (list, listbox, menu, radiogroup, table, grid, tablist, tree, row, ...) contain at least one descendant or aria-owns-referenced element with an acceptable owned role.',
64
+ description:
65
+ 'Checks that container roles with a documented "required owned elements" entry (list, listbox, menu, radiogroup, table, grid, tablist, tree, row, ...) contain at least one descendant or aria-owns-referenced element with an acceptable owned role.',
65
66
  i18n: {
66
67
  titleKey: 'ariaRequiredChildren_title',
67
68
  descriptionKey: 'ariaRequiredChildren_description'
@@ -70,7 +71,13 @@ const meta = {
70
71
  tags: ['wcag2a', 'wcag412', 'aria', 'structure', 'atomic', 'automatic'],
71
72
  wcagSc: ['4.1.2'],
72
73
  normativeMappings: [
73
- { standard: 'WCAG', version: '2.2', requirement: '4.1.2', title: 'Name, Role, Value', conformanceLevel: 'A' }
74
+ {
75
+ standard: 'WCAG',
76
+ version: '2.2',
77
+ requirement: '4.1.2',
78
+ title: 'Name, Role, Value',
79
+ conformanceLevel: 'A'
80
+ }
74
81
  ],
75
82
  defaultSeverity: 'moderate',
76
83
  category: 'robust',
@@ -80,7 +87,7 @@ const meta = {
80
87
  };
81
88
 
82
89
  function runInPage(ctx) {
83
- const { document, helpers, rule } = ctx;
90
+ const { helpers, rule } = ctx;
84
91
 
85
92
  const ariaHelpers = helpers && helpers.aria ? helpers.aria : null;
86
93
  if (!ariaHelpers) {
@@ -88,7 +95,8 @@ function runInPage(ctx) {
88
95
  }
89
96
 
90
97
  function isEligibleAcc(el) {
91
- const fn = helpers && typeof helpers.isAccTreeEligible === 'function' ? helpers.isAccTreeEligible : null;
98
+ const fn =
99
+ helpers && typeof helpers.isAccTreeEligible === 'function' ? helpers.isAccTreeEligible : null;
92
100
  if (!fn) return true;
93
101
  try {
94
102
  const r = fn(el, ctx);
@@ -109,9 +117,12 @@ function runInPage(ctx) {
109
117
  // (kept in sync with aria-helpers.js NATIVE_CONTAINMENT_ROLE_BY_ELEMENT).
110
118
  // Declared inside runInPage — see scripts/build-core.js header
111
119
  // ("runInPage MUST be self-contained").
112
- const CANDIDATE_SELECTOR = '[role], li, option, tr, td, th, thead, tbody, tfoot, ul, ol, table, select, input[type="radio"]';
120
+ const CANDIDATE_SELECTOR =
121
+ '[role], li, option, tr, td, th, thead, tbody, tfoot, ul, ol, table, select, input[type="radio"]';
113
122
 
114
- const nodes = helpers.queryAllSmart ? helpers.queryAllSmart('[role]') : helpers.queryAll('[role]');
123
+ const nodes = helpers.queryAllSmart
124
+ ? helpers.queryAllSmart('[role]')
125
+ : helpers.queryAll('[role]');
115
126
 
116
127
  const occurrences = [];
117
128
  let applicableCount = 0;
@@ -139,8 +150,11 @@ function runInPage(ctx) {
139
150
  if (out.length >= limit) return;
140
151
  if (seen.has(child)) continue;
141
152
 
142
- if ((child.tagName || '').toLowerCase() === 'slot' && typeof child.assignedElements === 'function') {
143
- let assigned = [];
153
+ if (
154
+ (child.tagName || '').toLowerCase() === 'slot' &&
155
+ typeof child.assignedElements === 'function'
156
+ ) {
157
+ let assigned;
144
158
  try {
145
159
  assigned = child.assignedElements({ flatten: true }) || [];
146
160
  } catch {
@@ -184,7 +198,7 @@ function runInPage(ctx) {
184
198
  // selector, exactly as before this fix — covers the overwhelming
185
199
  // majority of containers (no shadow DOM involved at all) with zero
186
200
  // added cost.
187
- let descendants = [];
201
+ let descendants;
188
202
  try {
189
203
  descendants = el.querySelectorAll(CANDIDATE_SELECTOR);
190
204
  } catch {
@@ -202,7 +216,7 @@ function runInPage(ctx) {
202
216
  // <slot> somewhere in the subtree to expand — bounds the extra cost to
203
217
  // exactly the containers that could possibly need it.
204
218
  if (!found) {
205
- let hasSlot = false;
219
+ let hasSlot;
206
220
  try {
207
221
  hasSlot = !!el.querySelector('slot');
208
222
  } catch {
@@ -243,7 +257,7 @@ function runInPage(ctx) {
243
257
  if (found) continue;
244
258
 
245
259
  const stableSelector = helpers.buildSelector ? helpers.buildSelector(el) : 'html';
246
- const html = helpers.getOuterHtmlSnippet ? helpers.getOuterHtmlSnippet(el) : (el.outerHTML || '');
260
+ const html = helpers.getOuterHtmlSnippet ? helpers.getOuterHtmlSnippet(el) : el.outerHTML || '';
247
261
 
248
262
  occurrences.push({
249
263
  selector: stableSelector,
@@ -256,7 +270,11 @@ function runInPage(ctx) {
256
270
  params: { role, requiredRoles: requiredOwned.join(', ') }
257
271
  },
258
272
  data: {
259
- details: { reasonCode: 'ARIA_REQUIRED_CHILD_MISSING', role, requiredOwnedRoles: requiredOwned }
273
+ details: {
274
+ reasonCode: 'ARIA_REQUIRED_CHILD_MISSING',
275
+ role,
276
+ requiredOwnedRoles: requiredOwned
277
+ }
260
278
  }
261
279
  });
262
280
  }
@@ -265,9 +283,14 @@ function runInPage(ctx) {
265
283
  return { ruleId: rule.ruleId, outcome: 'notApplicable', severity: 'minor', occurrences: [] };
266
284
  }
267
285
  if (occurrences.length) {
268
- return { ruleId: rule.ruleId, outcome: 'fail', severity: rule.defaultSeverity || 'moderate', occurrences };
286
+ return {
287
+ ruleId: rule.ruleId,
288
+ outcome: 'fail',
289
+ severity: rule.defaultSeverity || 'moderate',
290
+ occurrences
291
+ };
269
292
  }
270
293
  return { ruleId: rule.ruleId, outcome: 'pass', severity: 'minor', occurrences: [] };
271
294
  }
272
295
 
273
- module.exports = { id, meta, runInPage };
296
+ module.exports = { id, meta, runInPage };
@@ -47,7 +47,8 @@ const id = 'aria-required-parent';
47
47
 
48
48
  const meta = {
49
49
  title: 'Roles requiring a specific context role must be in that context',
50
- description: 'Checks that roles with a documented "required context role" entry (listitem, option, tab, treeitem, row, cell, ...) have an ancestor or aria-owns owner with an acceptable context role.',
50
+ description:
51
+ 'Checks that roles with a documented "required context role" entry (listitem, option, tab, treeitem, row, cell, ...) have an ancestor or aria-owns owner with an acceptable context role.',
51
52
  i18n: {
52
53
  titleKey: 'ariaRequiredParent_title',
53
54
  descriptionKey: 'ariaRequiredParent_description'
@@ -56,7 +57,13 @@ const meta = {
56
57
  tags: ['wcag2a', 'wcag412', 'aria', 'structure', 'atomic', 'automatic'],
57
58
  wcagSc: ['4.1.2'],
58
59
  normativeMappings: [
59
- { standard: 'WCAG', version: '2.2', requirement: '4.1.2', title: 'Name, Role, Value', conformanceLevel: 'A' }
60
+ {
61
+ standard: 'WCAG',
62
+ version: '2.2',
63
+ requirement: '4.1.2',
64
+ title: 'Name, Role, Value',
65
+ conformanceLevel: 'A'
66
+ }
60
67
  ],
61
68
  defaultSeverity: 'moderate',
62
69
  category: 'robust',
@@ -66,7 +73,7 @@ const meta = {
66
73
  };
67
74
 
68
75
  function runInPage(ctx) {
69
- const { document, helpers, rule } = ctx;
76
+ const { helpers, rule } = ctx;
70
77
 
71
78
  const ariaHelpers = helpers && helpers.aria ? helpers.aria : null;
72
79
  if (!ariaHelpers) {
@@ -74,7 +81,8 @@ function runInPage(ctx) {
74
81
  }
75
82
 
76
83
  function isEligibleAcc(el) {
77
- const fn = helpers && typeof helpers.isAccTreeEligible === 'function' ? helpers.isAccTreeEligible : null;
84
+ const fn =
85
+ helpers && typeof helpers.isAccTreeEligible === 'function' ? helpers.isAccTreeEligible : null;
78
86
  if (!fn) return true;
79
87
  try {
80
88
  const r = fn(el, ctx);
@@ -120,14 +128,25 @@ function runInPage(ctx) {
120
128
  // non-Element node (a ShadowRoot, nodeType 11) when climbing out of a
121
129
  // shadow tree that has no further light-DOM parent — skip those and keep
122
130
  // climbing rather than treating them as a (roleless) context.
123
- const getComposedParent = helpers && typeof helpers.composedParent === 'function'
124
- ? helpers.composedParent
125
- : function (n) { return n && n.parentElement ? n.parentElement : null; };
131
+ const getComposedParent =
132
+ helpers && typeof helpers.composedParent === 'function'
133
+ ? helpers.composedParent
134
+ : function (n) {
135
+ return n && n.parentElement ? n.parentElement : null;
136
+ };
126
137
 
127
138
  function hasAcceptableAncestorContext(el, acceptableRoles, ownRole) {
128
139
  const allowsGroup = acceptableRoles.has('group');
129
140
  let cur = getComposedParent(el);
130
141
  let guard = 0;
142
+ // Mutable working copy: passing a transparent "group" ancestor also
143
+ // makes the element's OWN role an acceptable context from that point on
144
+ // (a nested treeitem-under-group-under-treeitem chain is a normal,
145
+ // arbitrarily-deep ARIA tree/list, not just one level) — mirroring that
146
+ // reference engine's getMissingContext, which pushes explicitRole into
147
+ // reqContext at the same point. Cloned lazily so the caller's Set (built
148
+ // once per element in runInPage) is never mutated.
149
+ let roles = acceptableRoles;
131
150
  while (cur && guard++ < 200) {
132
151
  if (cur.nodeType !== 1) {
133
152
  cur = getComposedParent(cur);
@@ -139,10 +158,12 @@ function runInPage(ctx) {
139
158
  continue;
140
159
  }
141
160
  if (role === 'group' && allowsGroup && GROUP_TRANSPARENT_FOR_ROLES.has(ownRole)) {
161
+ if (roles === acceptableRoles) roles = new Set(acceptableRoles);
162
+ roles.add(ownRole);
142
163
  cur = getComposedParent(cur);
143
164
  continue;
144
165
  }
145
- return acceptableRoles.has(role);
166
+ return roles.has(role);
146
167
  }
147
168
  return false;
148
169
  }
@@ -152,7 +173,9 @@ function runInPage(ctx) {
152
173
  const idTok = elId && String(elId).trim();
153
174
  if (!idTok) return false;
154
175
 
155
- const owners = helpers.queryAllSmart ? helpers.queryAllSmart('[aria-owns]') : helpers.queryAll('[aria-owns]');
176
+ const owners = helpers.queryAllSmart
177
+ ? helpers.queryAllSmart('[aria-owns]')
178
+ : helpers.queryAll('[aria-owns]');
156
179
  for (const owner of owners) {
157
180
  if (!owner || !owner.getAttribute) continue;
158
181
  const ownsAttr = owner.getAttribute('aria-owns') || '';
@@ -165,7 +188,9 @@ function runInPage(ctx) {
165
188
  return false;
166
189
  }
167
190
 
168
- const nodes = helpers.queryAllSmart ? helpers.queryAllSmart('[role]') : helpers.queryAll('[role]');
191
+ const nodes = helpers.queryAllSmart
192
+ ? helpers.queryAllSmart('[role]')
193
+ : helpers.queryAll('[role]');
169
194
 
170
195
  const occurrences = [];
171
196
  let applicableCount = 0;
@@ -191,7 +216,7 @@ function runInPage(ctx) {
191
216
  if (hasContext) continue;
192
217
 
193
218
  const stableSelector = helpers.buildSelector ? helpers.buildSelector(el) : 'html';
194
- const html = helpers.getOuterHtmlSnippet ? helpers.getOuterHtmlSnippet(el) : (el.outerHTML || '');
219
+ const html = helpers.getOuterHtmlSnippet ? helpers.getOuterHtmlSnippet(el) : el.outerHTML || '';
195
220
 
196
221
  occurrences.push({
197
222
  selector: stableSelector,
@@ -204,7 +229,11 @@ function runInPage(ctx) {
204
229
  params: { role, requiredRoles: requiredContext.join(', ') }
205
230
  },
206
231
  data: {
207
- details: { reasonCode: 'ARIA_REQUIRED_PARENT_MISSING', role, requiredContextRoles: requiredContext }
232
+ details: {
233
+ reasonCode: 'ARIA_REQUIRED_PARENT_MISSING',
234
+ role,
235
+ requiredContextRoles: requiredContext
236
+ }
208
237
  }
209
238
  });
210
239
  }
@@ -213,9 +242,14 @@ function runInPage(ctx) {
213
242
  return { ruleId: rule.ruleId, outcome: 'notApplicable', severity: 'minor', occurrences: [] };
214
243
  }
215
244
  if (occurrences.length) {
216
- return { ruleId: rule.ruleId, outcome: 'fail', severity: rule.defaultSeverity || 'moderate', occurrences };
245
+ return {
246
+ ruleId: rule.ruleId,
247
+ outcome: 'fail',
248
+ severity: rule.defaultSeverity || 'moderate',
249
+ occurrences
250
+ };
217
251
  }
218
252
  return { ruleId: rule.ruleId, outcome: 'pass', severity: 'minor', occurrences: [] };
219
253
  }
220
254
 
221
- module.exports = { id, meta, runInPage };
255
+ module.exports = { id, meta, runInPage };
@@ -15,7 +15,8 @@ const id = 'aria-role-name-present';
15
15
 
16
16
  const meta = {
17
17
  title: 'ARIA widget/container roles have an accessible name',
18
- description: 'Checks that selected ARIA widget/container roles expose a non-empty accessible name.',
18
+ description:
19
+ 'Checks that selected ARIA widget/container roles expose a non-empty accessible name.',
19
20
  i18n: {
20
21
  titleKey: 'ariaRoleNamePresent_title',
21
22
  descriptionKey: 'ariaRoleNamePresent_description'
@@ -24,7 +25,13 @@ const meta = {
24
25
  tags: ['wcag2a', 'wcag412', 'navigation', 'atomic', 'automatic', 'name'],
25
26
  wcagSc: ['4.1.2'],
26
27
  normativeMappings: [
27
- { standard: 'WCAG', version: '2.2', requirement: '4.1.2', title: 'Name, Role, Value', conformanceLevel: 'A' }
28
+ {
29
+ standard: 'WCAG',
30
+ version: '2.2',
31
+ requirement: '4.1.2',
32
+ title: 'Name, Role, Value',
33
+ conformanceLevel: 'A'
34
+ }
28
35
  ],
29
36
  defaultSeverity: 'serious',
30
37
  category: 'robust',
@@ -41,17 +48,19 @@ function runInPage(ctx) {
41
48
  const { document, root, helpers, rule } = ctx;
42
49
  const safeRoot = root || document;
43
50
 
44
- const queryAllSmart = helpers && typeof helpers.queryAllSmart === 'function' ? helpers.queryAllSmart : null;
45
- const queryAll = helpers && typeof helpers.queryAll === 'function'
46
- ? helpers.queryAll
47
- : (sel, rt) => {
48
- try {
49
- const scope = rt || safeRoot;
50
- return scope && scope.querySelectorAll ? Array.from(scope.querySelectorAll(sel)) : [];
51
- } catch {
52
- return [];
53
- }
54
- };
51
+ const queryAllSmart =
52
+ helpers && typeof helpers.queryAllSmart === 'function' ? helpers.queryAllSmart : null;
53
+ const queryAll =
54
+ helpers && typeof helpers.queryAll === 'function'
55
+ ? helpers.queryAll
56
+ : (sel, rt) => {
57
+ try {
58
+ const scope = rt || safeRoot;
59
+ return scope && scope.querySelectorAll ? Array.from(scope.querySelectorAll(sel)) : [];
60
+ } catch {
61
+ return [];
62
+ }
63
+ };
55
64
 
56
65
  const isAccTreeEligible =
57
66
  helpers && typeof helpers.isAccTreeEligible === 'function' ? helpers.isAccTreeEligible : null;
@@ -60,9 +69,14 @@ function runInPage(ctx) {
60
69
  helpers && typeof helpers.getEligibilityInfo === 'function' ? helpers.getEligibilityInfo : null;
61
70
 
62
71
  const getAriaLabelledByInfo =
63
- helpers && typeof helpers.getAriaLabelledByInfo === 'function' ? helpers.getAriaLabelledByInfo : null;
72
+ helpers && typeof helpers.getAriaLabelledByInfo === 'function'
73
+ ? helpers.getAriaLabelledByInfo
74
+ : null;
64
75
 
65
- const normalizeWs = (s) => String(s || '').replace(/\s+/g, ' ').trim();
76
+ const normalizeWs = (s) =>
77
+ String(s || '')
78
+ .replace(/\s+/g, ' ')
79
+ .trim();
66
80
 
67
81
  const getAttr = (el, name) => {
68
82
  try {
@@ -73,7 +87,6 @@ function runInPage(ctx) {
73
87
  }
74
88
  };
75
89
 
76
-
77
90
  // aria-labelledby resolution: delegate to the shared helper (used by
78
91
  // button-name-present et al.), which correctly includes hidden/aria-hidden
79
92
  // referenced nodes per the Accessible Name and Description Computation
@@ -120,7 +133,9 @@ function runInPage(ctx) {
120
133
 
121
134
  const nodes = (() => {
122
135
  try {
123
- return (queryAllSmart ? queryAllSmart(selector, safeRoot) : queryAll(selector, safeRoot)) || [];
136
+ return (
137
+ (queryAllSmart ? queryAllSmart(selector, safeRoot) : queryAll(selector, safeRoot)) || []
138
+ );
124
139
  } catch {
125
140
  return queryAll(selector, safeRoot);
126
141
  }
@@ -158,9 +173,15 @@ function runInPage(ctx) {
158
173
  const ok = !!(ariaLabel || labelled || title);
159
174
  if (ok) continue;
160
175
 
161
- const eligInfo = getEligibilityInfo ? (() => {
162
- try { return getEligibilityInfo(el, ctx, { targetSet: 'acc' }); } catch { return null; }
163
- })() : null;
176
+ const eligInfo = getEligibilityInfo
177
+ ? (() => {
178
+ try {
179
+ return getEligibilityInfo(el, ctx, { targetSet: 'acc' });
180
+ } catch {
181
+ return null;
182
+ }
183
+ })()
184
+ : null;
164
185
 
165
186
  const baseOccurrence = {
166
187
  summary: 'This element has no accessible name.',
@@ -195,7 +216,12 @@ function runInPage(ctx) {
195
216
  return { ruleId: rule.ruleId, outcome: 'pass', severity: 'minor', occurrences: [] };
196
217
  }
197
218
 
198
- return { ruleId: rule.ruleId, outcome: 'fail', severity: rule.defaultSeverity || 'minor', occurrences };
219
+ return {
220
+ ruleId: rule.ruleId,
221
+ outcome: 'fail',
222
+ severity: rule.defaultSeverity || 'minor',
223
+ occurrences
224
+ };
199
225
  }
200
226
 
201
227
  module.exports = { id, meta, runInPage };
@@ -25,7 +25,8 @@ const id = 'aria-roles-valid';
25
25
 
26
26
  const meta = {
27
27
  title: 'role attribute must be a valid, non-abstract ARIA role',
28
- description: 'Checks that an explicit role="" attribute resolves to a real, non-abstract WAI-ARIA role.',
28
+ description:
29
+ 'Checks that an explicit role="" attribute resolves to a real, non-abstract WAI-ARIA role.',
29
30
  i18n: {
30
31
  titleKey: 'ariaRolesValid_title',
31
32
  descriptionKey: 'ariaRolesValid_description'
@@ -34,7 +35,13 @@ const meta = {
34
35
  tags: ['wcag2a', 'wcag412', 'aria', 'structure', 'atomic', 'automatic'],
35
36
  wcagSc: ['4.1.2'],
36
37
  normativeMappings: [
37
- { standard: 'WCAG', version: '2.2', requirement: '4.1.2', title: 'Name, Role, Value', conformanceLevel: 'A' }
38
+ {
39
+ standard: 'WCAG',
40
+ version: '2.2',
41
+ requirement: '4.1.2',
42
+ title: 'Name, Role, Value',
43
+ conformanceLevel: 'A'
44
+ }
38
45
  ],
39
46
  defaultSeverity: 'serious',
40
47
  category: 'robust',
@@ -44,14 +51,16 @@ const meta = {
44
51
  };
45
52
 
46
53
  function runInPage(ctx) {
47
- const { document, helpers, rule } = ctx;
54
+ const { helpers, rule } = ctx;
48
55
 
49
56
  const ariaHelpers = helpers && helpers.aria ? helpers.aria : null;
50
57
  if (!ariaHelpers) {
51
58
  return { ruleId: rule.ruleId, outcome: 'notApplicable', severity: 'minor', occurrences: [] };
52
59
  }
53
60
 
54
- const nodes = helpers.queryAllSmart ? helpers.queryAllSmart('[role]') : helpers.queryAll('[role]');
61
+ const nodes = helpers.queryAllSmart
62
+ ? helpers.queryAllSmart('[role]')
63
+ : helpers.queryAll('[role]');
55
64
 
56
65
  const occurrences = [];
57
66
  let applicableCount = 0;
@@ -71,7 +80,7 @@ function runInPage(ctx) {
71
80
 
72
81
  const reasonCode = !isKnown ? 'ARIA_ROLE_INVALID' : 'ARIA_ROLE_ABSTRACT';
73
82
  const stableSelector = helpers.buildSelector ? helpers.buildSelector(el) : 'html';
74
- const html = helpers.getOuterHtmlSnippet ? helpers.getOuterHtmlSnippet(el) : (el.outerHTML || '');
83
+ const html = helpers.getOuterHtmlSnippet ? helpers.getOuterHtmlSnippet(el) : el.outerHTML || '';
75
84
 
76
85
  occurrences.push({
77
86
  selector: stableSelector,
@@ -83,12 +92,8 @@ function runInPage(ctx) {
83
92
  ? 'Use a valid ARIA role token, or remove the role attribute if none applies.'
84
93
  : 'Replace this abstract role with a concrete role appropriate for the widget/structure.',
85
94
  i18n: {
86
- summaryKey: !isKnown
87
- ? 'ariaRolesValid_summary_invalid'
88
- : 'ariaRolesValid_summary_abstract',
89
- hintKey: !isKnown
90
- ? 'ariaRolesValid_hint_invalid'
91
- : 'ariaRolesValid_hint_abstract',
95
+ summaryKey: !isKnown ? 'ariaRolesValid_summary_invalid' : 'ariaRolesValid_summary_abstract',
96
+ hintKey: !isKnown ? 'ariaRolesValid_hint_invalid' : 'ariaRolesValid_hint_abstract',
92
97
  params: { role }
93
98
  },
94
99
  data: {
@@ -101,7 +106,12 @@ function runInPage(ctx) {
101
106
  return { ruleId: rule.ruleId, outcome: 'notApplicable', severity: 'minor', occurrences: [] };
102
107
  }
103
108
  if (occurrences.length) {
104
- return { ruleId: rule.ruleId, outcome: 'fail', severity: rule.defaultSeverity || 'serious', occurrences };
109
+ return {
110
+ ruleId: rule.ruleId,
111
+ outcome: 'fail',
112
+ severity: rule.defaultSeverity || 'serious',
113
+ occurrences
114
+ };
105
115
  }
106
116
  return { ruleId: rule.ruleId, outcome: 'pass', severity: 'minor', occurrences: [] };
107
117
  }
@@ -38,7 +38,8 @@ const id = 'aria-valid-attr-value';
38
38
 
39
39
  const meta = {
40
40
  title: 'aria-* attribute values must match their declared type',
41
- description: 'Checks that every recognized aria-* attribute has a value conforming to its WAI-ARIA-declared value type (boolean, tristate, token, integer, number, or ID reference).',
41
+ description:
42
+ 'Checks that every recognized aria-* attribute has a value conforming to its WAI-ARIA-declared value type (boolean, tristate, token, integer, number, or ID reference).',
42
43
  i18n: {
43
44
  titleKey: 'ariaValidAttrValue_title',
44
45
  descriptionKey: 'ariaValidAttrValue_description'
@@ -47,7 +48,13 @@ const meta = {
47
48
  tags: ['wcag2a', 'wcag412', 'aria', 'structure', 'atomic', 'automatic'],
48
49
  wcagSc: ['4.1.2'],
49
50
  normativeMappings: [
50
- { standard: 'WCAG', version: '2.2', requirement: '4.1.2', title: 'Name, Role, Value', conformanceLevel: 'A' }
51
+ {
52
+ standard: 'WCAG',
53
+ version: '2.2',
54
+ requirement: '4.1.2',
55
+ title: 'Name, Role, Value',
56
+ conformanceLevel: 'A'
57
+ }
51
58
  ],
52
59
  defaultSeverity: 'serious',
53
60
  category: 'robust',
@@ -57,7 +64,7 @@ const meta = {
57
64
  };
58
65
 
59
66
  function runInPage(ctx) {
60
- const { document, helpers, rule } = ctx;
67
+ const { helpers, rule } = ctx;
61
68
 
62
69
  const ariaHelpers = helpers && helpers.aria ? helpers.aria : null;
63
70
  if (!ariaHelpers) {
@@ -85,14 +92,18 @@ function runInPage(ctx) {
85
92
  const result = ariaHelpers.validateAttrValue(name, rawValue);
86
93
  if (!result.valid) {
87
94
  if (!invalid) invalid = [];
88
- invalid.push({ name, value: rawValue == null ? '' : String(rawValue), reason: result.reason });
95
+ invalid.push({
96
+ name,
97
+ value: rawValue == null ? '' : String(rawValue),
98
+ reason: result.reason
99
+ });
89
100
  }
90
101
  }
91
102
 
92
103
  if (!invalid || !invalid.length) continue;
93
104
 
94
105
  const stableSelector = helpers.buildSelector ? helpers.buildSelector(el) : 'html';
95
- const html = helpers.getOuterHtmlSnippet ? helpers.getOuterHtmlSnippet(el) : (el.outerHTML || '');
106
+ const html = helpers.getOuterHtmlSnippet ? helpers.getOuterHtmlSnippet(el) : el.outerHTML || '';
96
107
 
97
108
  for (const item of invalid) {
98
109
  occurrences.push({
@@ -106,7 +117,12 @@ function runInPage(ctx) {
106
117
  params: { attr: item.name, value: item.value }
107
118
  },
108
119
  data: {
109
- details: { reasonCode: 'ARIA_ATTR_VALUE_INVALID', attr: item.name, value: item.value, valueReason: item.reason }
120
+ details: {
121
+ reasonCode: 'ARIA_ATTR_VALUE_INVALID',
122
+ attr: item.name,
123
+ value: item.value,
124
+ valueReason: item.reason
125
+ }
110
126
  }
111
127
  });
112
128
  }
@@ -116,7 +132,12 @@ function runInPage(ctx) {
116
132
  return { ruleId: rule.ruleId, outcome: 'notApplicable', severity: 'minor', occurrences: [] };
117
133
  }
118
134
  if (occurrences.length) {
119
- return { ruleId: rule.ruleId, outcome: 'fail', severity: rule.defaultSeverity || 'serious', occurrences };
135
+ return {
136
+ ruleId: rule.ruleId,
137
+ outcome: 'fail',
138
+ severity: rule.defaultSeverity || 'serious',
139
+ occurrences
140
+ };
120
141
  }
121
142
  return { ruleId: rule.ruleId, outcome: 'pass', severity: 'minor', occurrences: [] };
122
143
  }
@@ -27,7 +27,8 @@ const id = 'aria-valid-attr';
27
27
 
28
28
  const meta = {
29
29
  title: 'aria-* attributes must be real, defined ARIA attributes',
30
- description: 'Checks that every aria-* attribute name present in the DOM is a real attribute defined by the WAI-ARIA specification.',
30
+ description:
31
+ 'Checks that every aria-* attribute name present in the DOM is a real attribute defined by the WAI-ARIA specification.',
31
32
  i18n: {
32
33
  titleKey: 'ariaValidAttr_title',
33
34
  descriptionKey: 'ariaValidAttr_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: 'serious',
42
49
  category: 'robust',
@@ -46,7 +53,7 @@ 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) {
@@ -78,7 +85,7 @@ function runInPage(ctx) {
78
85
  if (!invalidNames || !invalidNames.length) continue;
79
86
 
80
87
  const stableSelector = helpers.buildSelector ? helpers.buildSelector(el) : 'html';
81
- const html = helpers.getOuterHtmlSnippet ? helpers.getOuterHtmlSnippet(el) : (el.outerHTML || '');
88
+ const html = helpers.getOuterHtmlSnippet ? helpers.getOuterHtmlSnippet(el) : el.outerHTML || '';
82
89
 
83
90
  for (const name of invalidNames) {
84
91
  occurrences.push({
@@ -102,7 +109,12 @@ function runInPage(ctx) {
102
109
  return { ruleId: rule.ruleId, outcome: 'notApplicable', severity: 'minor', occurrences: [] };
103
110
  }
104
111
  if (occurrences.length) {
105
- return { ruleId: rule.ruleId, outcome: 'fail', severity: rule.defaultSeverity || 'serious', occurrences };
112
+ return {
113
+ ruleId: rule.ruleId,
114
+ outcome: 'fail',
115
+ severity: rule.defaultSeverity || 'serious',
116
+ occurrences
117
+ };
106
118
  }
107
119
  return { ruleId: rule.ruleId, outcome: 'pass', severity: 'minor', occurrences: [] };
108
120
  }