@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
@@ -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 };