@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,3 +1,5 @@
1
+ /* SPDX-License-Identifier: MPL-2.0 */
2
+
1
3
  'use strict';
2
4
 
3
5
  /**
@@ -45,7 +47,13 @@ const meta = {
45
47
  tags: ['wcag2a', 'wcag131', 'structure', 'atomic', 'automatic'],
46
48
  wcagSc: ['1.3.1'],
47
49
  normativeMappings: [
48
- { standard: 'WCAG', version: '2.2', requirement: '1.3.1', title: 'Info and Relationships', conformanceLevel: 'A' }
50
+ {
51
+ standard: 'WCAG',
52
+ version: '2.2',
53
+ requirement: '1.3.1',
54
+ title: 'Info and Relationships',
55
+ conformanceLevel: 'A'
56
+ }
49
57
  ],
50
58
  defaultSeverity: 'serious',
51
59
  category: 'perceivable',
@@ -55,11 +63,26 @@ const meta = {
55
63
  };
56
64
 
57
65
  function runInPage(ctx) {
58
- const { document, helpers, rule } = ctx;
66
+ const { helpers, rule } = ctx;
59
67
 
60
68
  const MIN_SIZE = 4;
61
69
 
62
- function trim(v) { return (v == null ? '' : String(v)).trim(); }
70
+ function trim(v) {
71
+ return (v == null ? '' : String(v)).trim();
72
+ }
73
+
74
+ const isAccTreeEligible =
75
+ helpers && typeof helpers.isAccTreeEligible === 'function' ? helpers.isAccTreeEligible : null;
76
+
77
+ function isEligible(node) {
78
+ if (!isAccTreeEligible) return true;
79
+ try {
80
+ const r = isAccTreeEligible(node, ctx);
81
+ return !!(r && r.eligible);
82
+ } catch {
83
+ return true;
84
+ }
85
+ }
63
86
 
64
87
  const tables = helpers.queryAllSmart ? helpers.queryAllSmart('table') : helpers.queryAll('table');
65
88
 
@@ -76,17 +99,23 @@ function runInPage(ctx) {
76
99
  const maxCols = rowCells.reduce((m, cells) => Math.max(m, cells.length), 0);
77
100
  if (maxCols < MIN_SIZE) continue;
78
101
 
79
- const hasSpan = rowCells.some((cells) => cells.some((c) => {
80
- const cs = Number.parseInt(c.getAttribute('colspan') || '1', 10);
81
- const rs = Number.parseInt(c.getAttribute('rowspan') || '1', 10);
82
- return (Number.isFinite(cs) && cs > 1) || (Number.isFinite(rs) && rs > 1);
83
- }));
102
+ const hasSpan = rowCells.some((cells) =>
103
+ cells.some((c) => {
104
+ const cs = Number.parseInt(c.getAttribute('colspan') || '1', 10);
105
+ const rs = Number.parseInt(c.getAttribute('rowspan') || '1', 10);
106
+ return (Number.isFinite(cs) && cs > 1) || (Number.isFinite(rs) && rs > 1);
107
+ })
108
+ );
84
109
  if (hasSpan) continue;
85
110
 
86
111
  applicableCount += 1;
87
112
 
113
+ // An aria-hidden <th> is removed from the accessibility tree entirely
114
+ // -- a screen reader never announces it, so it can't actually serve as
115
+ // another cell's row/column header, even though it's still structurally
116
+ // a <th>.
88
117
  function isHeaderCell(cell) {
89
- return !!(cell && cell.tagName && cell.tagName.toLowerCase() === 'th');
118
+ return !!(cell && cell.tagName && cell.tagName.toLowerCase() === 'th' && isEligible(cell));
90
119
  }
91
120
 
92
121
  function hasColumnHeaderAbove(r, c) {
@@ -111,6 +140,10 @@ function runInPage(ctx) {
111
140
  const cell = cells[c];
112
141
  if (!cell || isHeaderCell(cell)) continue;
113
142
 
143
+ // An aria-hidden data cell isn't exposed to AT either, so it has
144
+ // no need for an accessible header association.
145
+ if (!isEligible(cell)) continue;
146
+
114
147
  const headersAttr = trim(cell.getAttribute('headers'));
115
148
  if (headersAttr) continue;
116
149
 
@@ -118,12 +151,15 @@ function runInPage(ctx) {
118
151
  if (hasRowHeaderBefore(r, c)) continue;
119
152
 
120
153
  const stableSelector = helpers.buildSelector ? helpers.buildSelector(cell) : 'html';
121
- const html = helpers.getOuterHtmlSnippet ? helpers.getOuterHtmlSnippet(cell) : (cell.outerHTML || '');
154
+ const html = helpers.getOuterHtmlSnippet
155
+ ? helpers.getOuterHtmlSnippet(cell)
156
+ : cell.outerHTML || '';
122
157
 
123
158
  occurrences.push({
124
159
  selector: stableSelector,
125
160
  html,
126
- summary: 'This data cell has no associated header (no headers attribute, no column <th> above it, no row <th> to its left).',
161
+ summary:
162
+ 'This data cell has no associated header (no headers attribute, no column <th> above it, no row <th> to its left).',
127
163
  hint: 'Add a headers attribute referencing the relevant <th> id(s), or restructure the table so this cell has an implicit row/column header.',
128
164
  i18n: {
129
165
  summaryKey: 'tdHasHeader_summary_fail',
@@ -142,7 +178,12 @@ function runInPage(ctx) {
142
178
  return { ruleId: rule.ruleId, outcome: 'notApplicable', severity: 'minor', occurrences: [] };
143
179
  }
144
180
  if (occurrences.length) {
145
- return { ruleId: rule.ruleId, outcome: 'fail', severity: rule.defaultSeverity || 'serious', occurrences };
181
+ return {
182
+ ruleId: rule.ruleId,
183
+ outcome: 'fail',
184
+ severity: rule.defaultSeverity || 'serious',
185
+ occurrences
186
+ };
146
187
  }
147
188
  return { ruleId: rule.ruleId, outcome: 'pass', severity: 'minor', occurrences: [] };
148
189
  }
@@ -1,3 +1,5 @@
1
+ /* SPDX-License-Identifier: MPL-2.0 */
2
+
1
3
  'use strict';
2
4
 
3
5
  const id = 'textbox-name-present';
@@ -10,10 +12,16 @@ const meta = {
10
12
  descriptionKey: 'textboxNamePresent_description'
11
13
  },
12
14
  helpUrl: null,
13
- tags: ["wcag2a", "wcag412", "forms", "atomic", "automatic", "name", "textbox"],
15
+ tags: ['wcag2a', 'wcag412', 'forms', 'atomic', 'automatic', 'name', 'textbox'],
14
16
  wcagSc: ['4.1.2'],
15
17
  normativeMappings: [
16
- { standard: 'WCAG', version: '2.2', requirement: '4.1.2', title: 'Name, Role, Value', conformanceLevel: 'A' }
18
+ {
19
+ standard: 'WCAG',
20
+ version: '2.2',
21
+ requirement: '4.1.2',
22
+ title: 'Name, Role, Value',
23
+ conformanceLevel: 'A'
24
+ }
17
25
  ],
18
26
  defaultSeverity: 'serious',
19
27
  category: 'robust',
@@ -24,20 +32,22 @@ const meta = {
24
32
 
25
33
  function runInPage(ctx) {
26
34
  const { document, helpers, rule } = ctx;
27
- const getEligibilityInfo = helpers && typeof helpers.getEligibilityInfo === 'function'
28
- ? helpers.getEligibilityInfo
29
- : null;
30
-
35
+ const getEligibilityInfo =
36
+ helpers && typeof helpers.getEligibilityInfo === 'function' ? helpers.getEligibilityInfo : null;
31
37
 
32
38
  function normalizeWs(s) {
33
- return String(s || '').replace(/\s+/g, ' ').trim();
39
+ return String(s || '')
40
+ .replace(/\s+/g, ' ')
41
+ .trim();
34
42
  }
35
43
 
36
44
  function getAttr(el, name) {
37
45
  try {
38
46
  if (!el || !el.getAttribute) return '';
39
47
  return normalizeWs(el.getAttribute(name));
40
- } catch { return ''; }
48
+ } catch {
49
+ return '';
50
+ }
41
51
  }
42
52
 
43
53
  function getConservativeSubtreeText(document, container) {
@@ -52,7 +62,7 @@ function runInPage(ctx) {
52
62
  const info = helpers.getContentNameInfo(container, ctx);
53
63
  return info && info.present ? info.value : '';
54
64
  }
55
- const t = (container && container.textContent) ? String(container.textContent) : '';
65
+ const t = container && container.textContent ? String(container.textContent) : '';
56
66
  return t.replace(/\s+/g, ' ').trim();
57
67
  }
58
68
 
@@ -100,7 +110,8 @@ function runInPage(ctx) {
100
110
  }
101
111
 
102
112
  function isEligibleAcc(helpers, el, ctx) {
103
- const fn = helpers && typeof helpers.isAccTreeEligible === 'function' ? helpers.isAccTreeEligible : null;
113
+ const fn =
114
+ helpers && typeof helpers.isAccTreeEligible === 'function' ? helpers.isAccTreeEligible : null;
104
115
  if (!fn) return true;
105
116
  try {
106
117
  const r = fn(el, ctx);
@@ -126,12 +137,13 @@ function runInPage(ctx) {
126
137
  return map;
127
138
  }
128
139
 
129
-
130
140
  const occurrences = [];
131
141
  let applicableCount = 0;
132
142
 
133
- const selector = "[role=\"textbox\"]";
134
- const nodes = helpers.queryAllSmart ? helpers.queryAllSmart(selector) : helpers.queryAll(selector);
143
+ const selector = '[role="textbox"]';
144
+ const nodes = helpers.queryAllSmart
145
+ ? helpers.queryAllSmart(selector)
146
+ : helpers.queryAll(selector);
135
147
 
136
148
  // Precompute label[for] map for textbox elements that are labelable
137
149
  // native form controls (e.g. <input role="textbox">).
@@ -200,16 +212,22 @@ function runInPage(ctx) {
200
212
  if (res.ok) continue;
201
213
 
202
214
  const eligInfo = getEligibilityInfo
203
- ? (() => { try { return getEligibilityInfo(el, ctx, { targetSet: 'acc' }); } catch { return null; } })()
204
- : null;
215
+ ? (() => {
216
+ try {
217
+ return getEligibilityInfo(el, ctx, { targetSet: 'acc' });
218
+ } catch {
219
+ return null;
220
+ }
221
+ })()
222
+ : null;
205
223
  const stableSelector = helpers.buildSelector ? helpers.buildSelector(el) : 'html';
206
- const html = helpers.getOuterHtmlSnippet ? helpers.getOuterHtmlSnippet(el) : (el.outerHTML || '');
224
+ const html = helpers.getOuterHtmlSnippet ? helpers.getOuterHtmlSnippet(el) : el.outerHTML || '';
207
225
 
208
226
  occurrences.push({
209
227
  selector: stableSelector,
210
228
  html,
211
229
  summary: 'This element has no accessible name.',
212
- hint: 'Provide aria-label or aria-labelledby (preferred), or provide visible text that is not hidden from assistive technologies.',
230
+ hint: "Provide aria-label, aria-labelledby, or a title attribute — visible text content is not exposed as this textbox's accessible name.",
213
231
  i18n: {
214
232
  summaryKey: 'textboxNamePresent_summary_fail',
215
233
  hintKey: 'textboxNamePresent_hint_fail',
@@ -222,12 +240,16 @@ function runInPage(ctx) {
222
240
  });
223
241
  }
224
242
 
225
-
226
243
  if (applicableCount === 0) {
227
244
  return { ruleId: rule.ruleId, outcome: 'notApplicable', severity: 'minor', occurrences: [] };
228
245
  }
229
246
  if (occurrences.length) {
230
- return { ruleId: rule.ruleId, outcome: 'fail', severity: rule.defaultSeverity || 'minor', occurrences };
247
+ return {
248
+ ruleId: rule.ruleId,
249
+ outcome: 'fail',
250
+ severity: rule.defaultSeverity || 'minor',
251
+ occurrences
252
+ };
231
253
  }
232
254
  return { ruleId: rule.ruleId, outcome: 'pass', severity: 'minor', occurrences: [] };
233
255
  }
@@ -1,3 +1,5 @@
1
+ /* SPDX-License-Identifier: MPL-2.0 */
2
+
1
3
  'use strict';
2
4
 
3
5
  const id = 'tooltip-name-present';
@@ -13,7 +15,13 @@ const meta = {
13
15
  tags: ['wcag2a', 'wcag412', 'navigation', 'atomic', 'automatic', 'name'],
14
16
  wcagSc: ['4.1.2'],
15
17
  normativeMappings: [
16
- { standard: 'WCAG', version: '2.2', requirement: '4.1.2', title: 'Name, Role, Value', conformanceLevel: 'A' }
18
+ {
19
+ standard: 'WCAG',
20
+ version: '2.2',
21
+ requirement: '4.1.2',
22
+ title: 'Name, Role, Value',
23
+ conformanceLevel: 'A'
24
+ }
17
25
  ],
18
26
  defaultSeverity: 'serious',
19
27
  category: 'robust',
@@ -24,20 +32,22 @@ const meta = {
24
32
 
25
33
  function runInPage(ctx) {
26
34
  const { document, helpers, rule } = ctx;
27
- const getEligibilityInfo = helpers && typeof helpers.getEligibilityInfo === 'function'
28
- ? helpers.getEligibilityInfo
29
- : null;
30
-
35
+ const getEligibilityInfo =
36
+ helpers && typeof helpers.getEligibilityInfo === 'function' ? helpers.getEligibilityInfo : null;
31
37
 
32
38
  function normalizeWs(s) {
33
- return String(s || '').replace(/\s+/g, ' ').trim();
39
+ return String(s || '')
40
+ .replace(/\s+/g, ' ')
41
+ .trim();
34
42
  }
35
43
 
36
44
  function getAttr(el, name) {
37
45
  try {
38
46
  if (!el || !el.getAttribute) return '';
39
47
  return normalizeWs(el.getAttribute(name));
40
- } catch { return ''; }
48
+ } catch {
49
+ return '';
50
+ }
41
51
  }
42
52
 
43
53
  function getConservativeSubtreeText(document, container) {
@@ -52,7 +62,7 @@ function runInPage(ctx) {
52
62
  const info = helpers.getContentNameInfo(container, ctx);
53
63
  return info && info.present ? info.value : '';
54
64
  }
55
- const t = (container && container.textContent) ? String(container.textContent) : '';
65
+ const t = container && container.textContent ? String(container.textContent) : '';
56
66
  return t.replace(/\s+/g, ' ').trim();
57
67
  }
58
68
 
@@ -74,7 +84,8 @@ function runInPage(ctx) {
74
84
  }
75
85
 
76
86
  function isEligibleAcc(helpers, el, ctx) {
77
- const fn = helpers && typeof helpers.isAccTreeEligible === 'function' ? helpers.isAccTreeEligible : null;
87
+ const fn =
88
+ helpers && typeof helpers.isAccTreeEligible === 'function' ? helpers.isAccTreeEligible : null;
78
89
  if (!fn) return true;
79
90
  try {
80
91
  const r = fn(el, ctx);
@@ -85,13 +96,13 @@ function runInPage(ctx) {
85
96
  }
86
97
  }
87
98
 
88
-
89
99
  const occurrences = [];
90
100
  let applicableCount = 0;
91
101
 
92
102
  const selector = '[role="tooltip"]';
93
- const nodes = helpers.queryAllSmart ? helpers.queryAllSmart(selector) : helpers.queryAll(selector);
94
-
103
+ const nodes = helpers.queryAllSmart
104
+ ? helpers.queryAllSmart(selector)
105
+ : helpers.queryAll(selector);
95
106
 
96
107
  function evaluate(el) {
97
108
  const ariaLabel = getAttr(el, 'aria-label');
@@ -122,10 +133,16 @@ function runInPage(ctx) {
122
133
  if (res.ok) continue;
123
134
 
124
135
  const eligInfo = getEligibilityInfo
125
- ? (() => { try { return getEligibilityInfo(el, ctx, { targetSet: 'acc' }); } catch { return null; } })()
126
- : null;
136
+ ? (() => {
137
+ try {
138
+ return getEligibilityInfo(el, ctx, { targetSet: 'acc' });
139
+ } catch {
140
+ return null;
141
+ }
142
+ })()
143
+ : null;
127
144
  const stableSelector = helpers.buildSelector ? helpers.buildSelector(el) : 'html';
128
- const html = helpers.getOuterHtmlSnippet ? helpers.getOuterHtmlSnippet(el) : (el.outerHTML || '');
145
+ const html = helpers.getOuterHtmlSnippet ? helpers.getOuterHtmlSnippet(el) : el.outerHTML || '';
129
146
 
130
147
  occurrences.push({
131
148
  selector: stableSelector,
@@ -144,14 +161,18 @@ function runInPage(ctx) {
144
161
  });
145
162
  }
146
163
 
147
-
148
164
  if (applicableCount === 0) {
149
165
  return { ruleId: rule.ruleId, outcome: 'notApplicable', severity: 'minor', occurrences: [] };
150
166
  }
151
167
  if (occurrences.length) {
152
- return { ruleId: rule.ruleId, outcome: 'fail', severity: rule.defaultSeverity || 'minor', occurrences };
168
+ return {
169
+ ruleId: rule.ruleId,
170
+ outcome: 'fail',
171
+ severity: rule.defaultSeverity || 'minor',
172
+ occurrences
173
+ };
153
174
  }
154
175
  return { ruleId: rule.ruleId, outcome: 'pass', severity: 'minor', occurrences: [] };
155
176
  }
156
177
 
157
- module.exports = { id, meta, runInPage };
178
+ module.exports = { id, meta, runInPage };
@@ -1,3 +1,5 @@
1
+ /* SPDX-License-Identifier: MPL-2.0 */
2
+
1
3
  'use strict';
2
4
 
3
5
  const id = 'treeitem-name-present';
@@ -10,10 +12,16 @@ const meta = {
10
12
  descriptionKey: 'treeitemNamePresent_description'
11
13
  },
12
14
  helpUrl: null,
13
- tags: ["wcag2a", "wcag412", "navigation", "atomic", "automatic", "name", "treeitem"],
15
+ tags: ['wcag2a', 'wcag412', 'navigation', 'atomic', 'automatic', 'name', 'treeitem'],
14
16
  wcagSc: ['4.1.2'],
15
17
  normativeMappings: [
16
- { standard: 'WCAG', version: '2.2', requirement: '4.1.2', title: 'Name, Role, Value', conformanceLevel: 'A' }
18
+ {
19
+ standard: 'WCAG',
20
+ version: '2.2',
21
+ requirement: '4.1.2',
22
+ title: 'Name, Role, Value',
23
+ conformanceLevel: 'A'
24
+ }
17
25
  ],
18
26
  defaultSeverity: 'serious',
19
27
  category: 'robust',
@@ -24,20 +32,22 @@ const meta = {
24
32
 
25
33
  function runInPage(ctx) {
26
34
  const { document, helpers, rule } = ctx;
27
- const getEligibilityInfo = helpers && typeof helpers.getEligibilityInfo === 'function'
28
- ? helpers.getEligibilityInfo
29
- : null;
30
-
35
+ const getEligibilityInfo =
36
+ helpers && typeof helpers.getEligibilityInfo === 'function' ? helpers.getEligibilityInfo : null;
31
37
 
32
38
  function normalizeWs(s) {
33
- return String(s || '').replace(/\s+/g, ' ').trim();
39
+ return String(s || '')
40
+ .replace(/\s+/g, ' ')
41
+ .trim();
34
42
  }
35
43
 
36
44
  function getAttr(el, name) {
37
45
  try {
38
46
  if (!el || !el.getAttribute) return '';
39
47
  return normalizeWs(el.getAttribute(name));
40
- } catch { return ''; }
48
+ } catch {
49
+ return '';
50
+ }
41
51
  }
42
52
 
43
53
  function getConservativeSubtreeText(document, container) {
@@ -52,7 +62,7 @@ function runInPage(ctx) {
52
62
  const info = helpers.getContentNameInfo(container, ctx);
53
63
  return info && info.present ? info.value : '';
54
64
  }
55
- const t = (container && container.textContent) ? String(container.textContent) : '';
65
+ const t = container && container.textContent ? String(container.textContent) : '';
56
66
  return t.replace(/\s+/g, ' ').trim();
57
67
  }
58
68
 
@@ -74,7 +84,8 @@ function runInPage(ctx) {
74
84
  }
75
85
 
76
86
  function isEligibleAcc(helpers, el, ctx) {
77
- const fn = helpers && typeof helpers.isAccTreeEligible === 'function' ? helpers.isAccTreeEligible : null;
87
+ const fn =
88
+ helpers && typeof helpers.isAccTreeEligible === 'function' ? helpers.isAccTreeEligible : null;
78
89
  if (!fn) return true;
79
90
  try {
80
91
  const r = fn(el, ctx);
@@ -85,13 +96,13 @@ function runInPage(ctx) {
85
96
  }
86
97
  }
87
98
 
88
-
89
99
  const occurrences = [];
90
100
  let applicableCount = 0;
91
101
 
92
- const selector = "[role=\"treeitem\"]";
93
- const nodes = helpers.queryAllSmart ? helpers.queryAllSmart(selector) : helpers.queryAll(selector);
94
-
102
+ const selector = '[role="treeitem"]';
103
+ const nodes = helpers.queryAllSmart
104
+ ? helpers.queryAllSmart(selector)
105
+ : helpers.queryAll(selector);
95
106
 
96
107
  function hasName(el) {
97
108
  const ariaLabel = getAttr(el, 'aria-label');
@@ -103,11 +114,9 @@ function runInPage(ctx) {
103
114
  const title = getAttr(el, 'title');
104
115
  if (title) return { ok: true, method: 'title' };
105
116
 
106
-
107
117
  const t = getConservativeSubtreeText(document, el);
108
118
  if (t) return { ok: true, method: 'content' };
109
119
 
110
-
111
120
  return { ok: false, method: 'none' };
112
121
  }
113
122
 
@@ -121,10 +130,16 @@ function runInPage(ctx) {
121
130
  if (res.ok) continue;
122
131
 
123
132
  const eligInfo = getEligibilityInfo
124
- ? (() => { try { return getEligibilityInfo(el, ctx, { targetSet: 'acc' }); } catch { return null; } })()
125
- : null;
133
+ ? (() => {
134
+ try {
135
+ return getEligibilityInfo(el, ctx, { targetSet: 'acc' });
136
+ } catch {
137
+ return null;
138
+ }
139
+ })()
140
+ : null;
126
141
  const stableSelector = helpers.buildSelector ? helpers.buildSelector(el) : 'html';
127
- const html = helpers.getOuterHtmlSnippet ? helpers.getOuterHtmlSnippet(el) : (el.outerHTML || '');
142
+ const html = helpers.getOuterHtmlSnippet ? helpers.getOuterHtmlSnippet(el) : el.outerHTML || '';
128
143
 
129
144
  occurrences.push({
130
145
  selector: stableSelector,
@@ -143,12 +158,16 @@ function runInPage(ctx) {
143
158
  });
144
159
  }
145
160
 
146
-
147
161
  if (applicableCount === 0) {
148
162
  return { ruleId: rule.ruleId, outcome: 'notApplicable', severity: 'minor', occurrences: [] };
149
163
  }
150
164
  if (occurrences.length) {
151
- return { ruleId: rule.ruleId, outcome: 'fail', severity: rule.defaultSeverity || 'minor', occurrences };
165
+ return {
166
+ ruleId: rule.ruleId,
167
+ outcome: 'fail',
168
+ severity: rule.defaultSeverity || 'minor',
169
+ occurrences
170
+ };
152
171
  }
153
172
  return { ruleId: rule.ruleId, outcome: 'pass', severity: 'minor', occurrences: [] };
154
173
  }
@@ -1,3 +1,5 @@
1
+ /* SPDX-License-Identifier: MPL-2.0 */
2
+
1
3
  'use strict';
2
4
 
3
5
  /**
@@ -28,7 +30,8 @@ const id = 'valid-lang';
28
30
 
29
31
  const meta = {
30
32
  title: 'Element lang attribute must be syntactically valid',
31
- description: 'Checks that any element (other than the root <html>) with a non-empty lang attribute uses a syntactically valid language tag.',
33
+ description:
34
+ 'Checks that any element (other than the root <html>) with a non-empty lang attribute uses a syntactically valid language tag.',
32
35
  i18n: {
33
36
  titleKey: 'validLang_title',
34
37
  descriptionKey: 'validLang_description'
@@ -37,7 +40,13 @@ const meta = {
37
40
  tags: ['wcag2aa', 'wcag312', 'structure', 'language', 'atomic', 'automatic'],
38
41
  wcagSc: ['3.1.2'],
39
42
  normativeMappings: [
40
- { standard: 'WCAG', version: '2.2', requirement: '3.1.2', title: 'Language of Parts', conformanceLevel: 'AA' }
43
+ {
44
+ standard: 'WCAG',
45
+ version: '2.2',
46
+ requirement: '3.1.2',
47
+ title: 'Language of Parts',
48
+ conformanceLevel: 'AA'
49
+ }
41
50
  ],
42
51
  defaultSeverity: 'moderate',
43
52
  category: 'understandable',
@@ -47,11 +56,13 @@ const meta = {
47
56
  };
48
57
 
49
58
  function runInPage(ctx) {
50
- const { document, helpers, rule } = ctx;
59
+ const { helpers, rule } = ctx;
51
60
 
52
61
  const BCP47_RE = /^[a-zA-Z]{2,3}(-[a-zA-Z0-9]{2,8})*$/;
53
62
 
54
- const nodes = helpers.queryAllSmart ? helpers.queryAllSmart('[lang]') : helpers.queryAll('[lang]');
63
+ const nodes = helpers.queryAllSmart
64
+ ? helpers.queryAllSmart('[lang]')
65
+ : helpers.queryAll('[lang]');
55
66
 
56
67
  const occurrences = [];
57
68
  let applicableCount = 0;
@@ -69,7 +80,7 @@ function runInPage(ctx) {
69
80
 
70
81
  const tag = el.tagName.toLowerCase();
71
82
  const stableSelector = helpers.buildSelector ? helpers.buildSelector(el) : 'html';
72
- const html = helpers.getOuterHtmlSnippet ? helpers.getOuterHtmlSnippet(el) : (el.outerHTML || '');
83
+ const html = helpers.getOuterHtmlSnippet ? helpers.getOuterHtmlSnippet(el) : el.outerHTML || '';
73
84
 
74
85
  occurrences.push({
75
86
  selector: stableSelector,
@@ -91,7 +102,12 @@ function runInPage(ctx) {
91
102
  return { ruleId: rule.ruleId, outcome: 'notApplicable', severity: 'minor', occurrences: [] };
92
103
  }
93
104
  if (occurrences.length) {
94
- return { ruleId: rule.ruleId, outcome: 'fail', severity: rule.defaultSeverity || 'moderate', occurrences };
105
+ return {
106
+ ruleId: rule.ruleId,
107
+ outcome: 'fail',
108
+ severity: rule.defaultSeverity || 'moderate',
109
+ occurrences
110
+ };
95
111
  }
96
112
  return { ruleId: rule.ruleId, outcome: 'pass', severity: 'minor', occurrences: [] };
97
113
  }