@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
@@ -22,7 +22,8 @@ const id = 'svg-image-text-alternative-present';
22
22
 
23
23
  const meta = {
24
24
  title: 'SVG <image> must have a text alternative',
25
- description: 'Checks that SVG <image> elements provide a text alternative via <title>/<desc> or an ARIA accessible name.',
25
+ description:
26
+ 'Checks that SVG <image> elements provide a text alternative via <title>/<desc> or an ARIA accessible name.',
26
27
  i18n: {
27
28
  titleKey: 'svgImage_textAltPresent_title',
28
29
  descriptionKey: 'svgImage_textAltPresent_description'
@@ -31,7 +32,13 @@ const meta = {
31
32
  tags: ['wcag2a', 'wcag111', 'nontext', 'svg', 'image', 'atomic', 'automatic'],
32
33
  wcagSc: ['1.1.1'],
33
34
  normativeMappings: [
34
- { standard: 'WCAG', version: '2.2', requirement: '1.1.1', title: 'Non-text Content', conformanceLevel: 'A' }
35
+ {
36
+ standard: 'WCAG',
37
+ version: '2.2',
38
+ requirement: '1.1.1',
39
+ title: 'Non-text Content',
40
+ conformanceLevel: 'A'
41
+ }
35
42
  ],
36
43
  defaultSeverity: 'serious',
37
44
  category: 'perceivable',
@@ -48,31 +55,38 @@ function runInPage(ctx) {
48
55
  const { document, root, helpers, rule } = ctx;
49
56
  const safeRoot = root || document;
50
57
 
51
- const queryAllSmart = helpers && typeof helpers.queryAllSmart === 'function' ? helpers.queryAllSmart : null;
52
- const queryAll = helpers && typeof helpers.queryAll === 'function'
53
- ? helpers.queryAll
54
- : (sel) => {
55
- try { return safeRoot && safeRoot.querySelectorAll ? Array.from(safeRoot.querySelectorAll(sel)) : []; }
56
- catch { return []; }
57
- };
58
-
59
- const getEligibilityInfo = helpers && typeof helpers.getEligibilityInfo === 'function'
60
- ? helpers.getEligibilityInfo
61
- : null;
62
-
63
- const isAccTreeEligible = helpers && typeof helpers.isAccTreeEligible === 'function'
64
- ? helpers.isAccTreeEligible
65
- : null;
66
-
67
- const isFocusableInfo = helpers && typeof helpers.getFocusableInfo === 'function'
68
- ? helpers.getFocusableInfo
69
- : null;
70
-
71
- const getAccessibleNameInfo = helpers && typeof helpers.getAccessibleNameInfo === 'function'
72
- ? helpers.getAccessibleNameInfo
73
- : null;
74
-
75
- function trim(v) { return (v == null ? '' : String(v)).trim(); }
58
+ const queryAllSmart =
59
+ helpers && typeof helpers.queryAllSmart === 'function' ? helpers.queryAllSmart : null;
60
+ const queryAll =
61
+ helpers && typeof helpers.queryAll === 'function'
62
+ ? helpers.queryAll
63
+ : (sel) => {
64
+ try {
65
+ return safeRoot && safeRoot.querySelectorAll
66
+ ? Array.from(safeRoot.querySelectorAll(sel))
67
+ : [];
68
+ } catch {
69
+ return [];
70
+ }
71
+ };
72
+
73
+ const getEligibilityInfo =
74
+ helpers && typeof helpers.getEligibilityInfo === 'function' ? helpers.getEligibilityInfo : null;
75
+
76
+ const isAccTreeEligible =
77
+ helpers && typeof helpers.isAccTreeEligible === 'function' ? helpers.isAccTreeEligible : null;
78
+
79
+ const isFocusableInfo =
80
+ helpers && typeof helpers.getFocusableInfo === 'function' ? helpers.getFocusableInfo : null;
81
+
82
+ const getAccessibleNameInfo =
83
+ helpers && typeof helpers.getAccessibleNameInfo === 'function'
84
+ ? helpers.getAccessibleNameInfo
85
+ : null;
86
+
87
+ function trim(v) {
88
+ return (v == null ? '' : String(v)).trim();
89
+ }
76
90
 
77
91
  // Per SVG accessible-name conventions, only a <title> that is literally
78
92
  // the first child element is used by assistive technologies as the
@@ -116,16 +130,27 @@ function runInPage(ctx) {
116
130
  }
117
131
 
118
132
  const rawImages = (() => {
119
- try { return Array.from((queryAllSmart ? queryAllSmart('image') : queryAll('image')) || []); }
120
- catch { return queryAll('svg image'); }
133
+ try {
134
+ return Array.from((queryAllSmart ? queryAllSmart('image') : queryAll('image')) || []);
135
+ } catch {
136
+ return queryAll('svg image');
137
+ }
121
138
  })();
122
139
 
123
140
  const images = rawImages.filter((el) => {
124
- try { return el && (el.namespaceURI === 'http://www.w3.org/2000/svg') && (String(el.localName).toLowerCase() === 'image'); }
125
- catch { return false; }
141
+ try {
142
+ return (
143
+ el &&
144
+ el.namespaceURI === 'http://www.w3.org/2000/svg' &&
145
+ String(el.localName).toLowerCase() === 'image'
146
+ );
147
+ } catch {
148
+ return false;
149
+ }
126
150
  });
127
151
 
128
- if (!images.length) return { ruleId: rule.ruleId, outcome: 'notApplicable', severity: 'minor', occurrences: [] };
152
+ if (!images.length)
153
+ return { ruleId: rule.ruleId, outcome: 'notApplicable', severity: 'minor', occurrences: [] };
129
154
 
130
155
  const occurrences = [];
131
156
  let applicableCount = 0;
@@ -135,20 +160,31 @@ function runInPage(ctx) {
135
160
 
136
161
  if (isAccTreeEligible) {
137
162
  const elig = (() => {
138
- try { return isAccTreeEligible(el, ctx); } catch { return { eligible: true, reasons: [] }; }
163
+ try {
164
+ return isAccTreeEligible(el, ctx);
165
+ } catch {
166
+ return { eligible: true, reasons: [] };
167
+ }
139
168
  })();
140
169
  if (elig && elig.eligible === false) continue;
141
170
  }
142
171
 
143
172
  const role = trim(el.getAttribute('role')).toLowerCase();
144
173
  if (role === 'presentation' || role === 'none') {
145
- let focusable = false;
174
+ let focusable;
146
175
  if (isFocusableInfo) {
147
- const fi = (() => { try { return isFocusableInfo(el, ctx); } catch { return null; } })();
176
+ const fi = (() => {
177
+ try {
178
+ return isFocusableInfo(el, ctx);
179
+ } catch {
180
+ return null;
181
+ }
182
+ })();
148
183
  focusable = !!(fi && fi.focusable);
149
184
  } else {
150
185
  const tabindex = el.getAttribute('tabindex');
151
- focusable = tabindex != null && trim(tabindex) !== '' && !Number.isNaN(Number(trim(tabindex)));
186
+ focusable =
187
+ tabindex != null && trim(tabindex) !== '' && !Number.isNaN(Number(trim(tabindex)));
152
188
  }
153
189
  if (!focusable) continue;
154
190
  }
@@ -162,25 +198,49 @@ function runInPage(ctx) {
162
198
  if (desc) continue;
163
199
 
164
200
  // Only now check “accessible name” (but scoped to allowed mechanisms)
165
- const ariaLabelRaw = (() => { try { return el.getAttribute('aria-label'); } catch { return null; } })();
166
- const ariaLabelledbyRaw = (() => { try { return el.getAttribute('aria-labelledby'); } catch { return null; } })();
167
- const titleAttrRaw = (() => { try { return el.getAttribute('title'); } catch { return null; } })();
201
+ const ariaLabelRaw = (() => {
202
+ try {
203
+ return el.getAttribute('aria-label');
204
+ } catch {
205
+ return null;
206
+ }
207
+ })();
208
+ const ariaLabelledbyRaw = (() => {
209
+ try {
210
+ return el.getAttribute('aria-labelledby');
211
+ } catch {
212
+ return null;
213
+ }
214
+ })();
215
+ const titleAttrRaw = (() => {
216
+ try {
217
+ return el.getAttribute('title');
218
+ } catch {
219
+ return null;
220
+ }
221
+ })();
168
222
 
169
223
  const ariaLabel = trim(ariaLabelRaw);
170
224
  const ariaLabelledby = trim(ariaLabelledbyRaw);
171
225
  const titleAttr = trim(titleAttrRaw);
172
226
 
173
227
  const hasMechanism =
174
- (ariaLabelRaw !== null && ariaLabel.length > 0) ||
175
- (ariaLabelledbyRaw !== null && ariaLabelledby.length > 0) ||
176
- (titleAttrRaw !== null && titleAttr.length > 0);
228
+ (ariaLabelRaw !== null && ariaLabel.length > 0) ||
229
+ (ariaLabelledbyRaw !== null && ariaLabelledby.length > 0) ||
230
+ (titleAttrRaw !== null && titleAttr.length > 0);
177
231
 
178
- let nameInfo = null;
232
+ let nameInfo;
179
233
  let hasName = false;
180
234
 
181
235
  if (hasMechanism) {
182
236
  if (getAccessibleNameInfo) {
183
- nameInfo = (() => { try { return getAccessibleNameInfo(el, ctx); } catch { return null; } })();
237
+ nameInfo = (() => {
238
+ try {
239
+ return getAccessibleNameInfo(el, ctx);
240
+ } catch {
241
+ return null;
242
+ }
243
+ })();
184
244
  hasName = !!(nameInfo && nameInfo.present && trim(nameInfo.value));
185
245
  } else {
186
246
  // Without helper, accept presence of non-empty allowed attributes
@@ -212,9 +272,16 @@ function runInPage(ctx) {
212
272
  }
213
273
  }
214
274
 
215
- if (applicableCount === 0) return { ruleId: rule.ruleId, outcome: 'notApplicable', severity: 'minor', occurrences: [] };
216
- if (!occurrences.length) return { ruleId: rule.ruleId, outcome: 'pass', severity: 'minor', occurrences: [] };
217
- return { ruleId: rule.ruleId, outcome: 'fail', severity: rule.defaultSeverity || 'minor', occurrences };
275
+ if (applicableCount === 0)
276
+ return { ruleId: rule.ruleId, outcome: 'notApplicable', severity: 'minor', occurrences: [] };
277
+ if (!occurrences.length)
278
+ return { ruleId: rule.ruleId, outcome: 'pass', severity: 'minor', occurrences: [] };
279
+ return {
280
+ ruleId: rule.ruleId,
281
+ outcome: 'fail',
282
+ severity: rule.defaultSeverity || 'minor',
283
+ occurrences
284
+ };
218
285
  }
219
286
 
220
287
  module.exports = { id, meta, runInPage };