@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
@@ -18,7 +18,8 @@ const id = 'canvas-text-alternative-present';
18
18
 
19
19
  const meta = {
20
20
  title: '<canvas> must provide a text alternative',
21
- description: 'Checks that <canvas> elements provide a text alternative via fallback content or an accessible name.',
21
+ description:
22
+ 'Checks that <canvas> elements provide a text alternative via fallback content or an accessible name.',
22
23
  i18n: {
23
24
  titleKey: 'canvas_textAltPresent_title',
24
25
  descriptionKey: 'canvas_textAltPresent_description'
@@ -27,7 +28,13 @@ const meta = {
27
28
  tags: ['wcag2a', 'wcag111', 'nontext', 'canvas', 'atomic', 'automatic'],
28
29
  wcagSc: ['1.1.1'],
29
30
  normativeMappings: [
30
- { standard: 'WCAG', version: '2.2', requirement: '1.1.1', title: 'Non-text Content', conformanceLevel: 'A' }
31
+ {
32
+ standard: 'WCAG',
33
+ version: '2.2',
34
+ requirement: '1.1.1',
35
+ title: 'Non-text Content',
36
+ conformanceLevel: 'A'
37
+ }
31
38
  ],
32
39
  defaultSeverity: 'serious',
33
40
  category: 'perceivable',
@@ -44,29 +51,38 @@ function runInPage(ctx) {
44
51
  const { document, root, helpers, rule } = ctx;
45
52
  const safeRoot = root || document;
46
53
 
47
- const queryAllSmart = helpers && typeof helpers.queryAllSmart === 'function' ? helpers.queryAllSmart : null;
48
- const queryAll = helpers && typeof helpers.queryAll === 'function'
49
- ? helpers.queryAll
50
- : (sel) => {
51
- try { return safeRoot && safeRoot.querySelectorAll ? Array.from(safeRoot.querySelectorAll(sel)) : []; }
52
- catch { return []; }
53
- };
54
-
55
- const getEligibilityInfo = helpers && typeof helpers.getEligibilityInfo === 'function'
56
- ? helpers.getEligibilityInfo
57
- : null;
58
-
59
- const isAccTreeEligible = helpers && typeof helpers.isAccTreeEligible === 'function'
60
- ? helpers.isAccTreeEligible
61
- : null;
62
-
63
- const getTextAlternativeInfo = helpers && typeof helpers.getTextAlternativeInfo === 'function'
64
- ? helpers.getTextAlternativeInfo
65
- : null;
54
+ const queryAllSmart =
55
+ helpers && typeof helpers.queryAllSmart === 'function' ? helpers.queryAllSmart : null;
56
+ const queryAll =
57
+ helpers && typeof helpers.queryAll === 'function'
58
+ ? helpers.queryAll
59
+ : (sel) => {
60
+ try {
61
+ return safeRoot && safeRoot.querySelectorAll
62
+ ? Array.from(safeRoot.querySelectorAll(sel))
63
+ : [];
64
+ } catch {
65
+ return [];
66
+ }
67
+ };
68
+
69
+ const getEligibilityInfo =
70
+ helpers && typeof helpers.getEligibilityInfo === 'function' ? helpers.getEligibilityInfo : null;
71
+
72
+ const isAccTreeEligible =
73
+ helpers && typeof helpers.isAccTreeEligible === 'function' ? helpers.isAccTreeEligible : null;
74
+
75
+ const getTextAlternativeInfo =
76
+ helpers && typeof helpers.getTextAlternativeInfo === 'function'
77
+ ? helpers.getTextAlternativeInfo
78
+ : null;
66
79
 
67
80
  const canvases = (() => {
68
- try { return Array.from((queryAllSmart ? queryAllSmart('canvas') : queryAll('canvas')) || []); }
69
- catch { return queryAll('canvas'); }
81
+ try {
82
+ return Array.from((queryAllSmart ? queryAllSmart('canvas') : queryAll('canvas')) || []);
83
+ } catch {
84
+ return queryAll('canvas');
85
+ }
70
86
  })();
71
87
 
72
88
  if (!canvases.length) {
@@ -82,7 +98,11 @@ function runInPage(ctx) {
82
98
  // Applicability: eligible in the accessibility tree (with focusable/IDREF exceptions handled by helper).
83
99
  if (isAccTreeEligible) {
84
100
  const elig = (() => {
85
- try { return isAccTreeEligible(el, ctx); } catch { return { eligible: true, reasons: [] }; }
101
+ try {
102
+ return isAccTreeEligible(el, ctx);
103
+ } catch {
104
+ return { eligible: true, reasons: [] };
105
+ }
86
106
  })();
87
107
  if (elig && elig.eligible === false) continue;
88
108
  }
@@ -91,7 +111,13 @@ function runInPage(ctx) {
91
111
 
92
112
  // Expectation: must provide a text alternative.
93
113
  const ti = getTextAlternativeInfo
94
- ? (() => { try { return getTextAlternativeInfo(el, ctx); } catch { return null; } })()
114
+ ? (() => {
115
+ try {
116
+ return getTextAlternativeInfo(el, ctx);
117
+ } catch {
118
+ return null;
119
+ }
120
+ })()
95
121
  : null;
96
122
 
97
123
  const hasTextAlt = !!(ti && ti.present);
@@ -134,7 +160,12 @@ function runInPage(ctx) {
134
160
  return { ruleId: rule.ruleId, outcome: 'pass', severity: 'minor', occurrences: [] };
135
161
  }
136
162
 
137
- return { ruleId: rule.ruleId, outcome: 'fail', severity: rule.defaultSeverity || 'minor', occurrences };
163
+ return {
164
+ ruleId: rule.ruleId,
165
+ outcome: 'fail',
166
+ severity: rule.defaultSeverity || 'minor',
167
+ occurrences
168
+ };
138
169
  }
139
170
 
140
171
  module.exports = { id, meta, runInPage };
@@ -10,10 +10,16 @@ const meta = {
10
10
  descriptionKey: 'comboboxNamePresent_description'
11
11
  },
12
12
  helpUrl: null,
13
- tags: ['wcag2a','wcag412','forms','atomic','automatic','name','combobox'],
13
+ tags: ['wcag2a', 'wcag412', 'forms', 'atomic', 'automatic', 'name', 'combobox'],
14
14
  wcagSc: ['4.1.2'],
15
15
  normativeMappings: [
16
- { standard: 'WCAG', version: '2.2', requirement: '4.1.2', title: 'Name, Role, Value', conformanceLevel: 'A' }
16
+ {
17
+ standard: 'WCAG',
18
+ version: '2.2',
19
+ requirement: '4.1.2',
20
+ title: 'Name, Role, Value',
21
+ conformanceLevel: 'A'
22
+ }
17
23
  ],
18
24
  defaultSeverity: 'serious',
19
25
  category: 'robust',
@@ -24,38 +30,22 @@ const meta = {
24
30
 
25
31
  function runInPage(ctx) {
26
32
  const { document, helpers, rule } = ctx;
27
- const getEligibilityInfo = helpers && typeof helpers.getEligibilityInfo === 'function'
28
- ? helpers.getEligibilityInfo
29
- : null;
30
-
33
+ const getEligibilityInfo =
34
+ helpers && typeof helpers.getEligibilityInfo === 'function' ? helpers.getEligibilityInfo : null;
31
35
 
32
36
  function normalizeWs(s) {
33
- return String(s || '').replace(/\s+/g, ' ').trim();
37
+ return String(s || '')
38
+ .replace(/\s+/g, ' ')
39
+ .trim();
34
40
  }
35
41
 
36
42
  function getAttr(el, name) {
37
43
  try {
38
44
  if (!el || !el.getAttribute) return '';
39
45
  return normalizeWs(el.getAttribute(name));
40
- } catch { return ''; }
41
- }
42
-
43
- function hasAttr(el, name) {
44
- try { return !!(el && el.hasAttribute && el.hasAttribute(name)); } catch { return false; }
45
- }
46
-
47
- function isExplicitProgrammatic(el) {
48
- // Programmatic name mechanisms we treat as authoritative (presence-only):
49
- // - aria-label (non-empty)
50
- // - aria-labelledby (non-empty)
51
- // - title (non-empty) [weak but allowed for presence]
52
- const al = getAttr(el, 'aria-label');
53
- if (al) return true;
54
- const alb = getAttr(el, 'aria-labelledby');
55
- if (alb) return true;
56
- const t = getAttr(el, 'title');
57
- if (t) return true;
58
- return false;
46
+ } catch {
47
+ return '';
48
+ }
59
49
  }
60
50
 
61
51
  function buildLabelForMap(doc) {
@@ -85,7 +75,7 @@ function runInPage(ctx) {
85
75
  const info = helpers.getContentNameInfo(container, ctx);
86
76
  return info && info.present ? info.value : '';
87
77
  }
88
- const t = (container && container.textContent) ? String(container.textContent) : '';
78
+ const t = container && container.textContent ? String(container.textContent) : '';
89
79
  return t.replace(/\s+/g, ' ').trim();
90
80
  }
91
81
 
@@ -132,17 +122,9 @@ function runInPage(ctx) {
132
122
  return '';
133
123
  }
134
124
 
135
- function getInputValueName(el) {
136
- try {
137
- const type = getAttr(el, 'type').toLowerCase();
138
- if (type !== 'button' && type !== 'submit' && type !== 'reset') return '';
139
- const v = getAttr(el, 'value');
140
- return v;
141
- } catch { return ''; }
142
- }
143
-
144
125
  function isEligibleAcc(helpers, el, ctx) {
145
- const fn = helpers && typeof helpers.isAccTreeEligible === 'function' ? helpers.isAccTreeEligible : null;
126
+ const fn =
127
+ helpers && typeof helpers.isAccTreeEligible === 'function' ? helpers.isAccTreeEligible : null;
146
128
  if (!fn) return true;
147
129
  try {
148
130
  const r = fn(el, ctx);
@@ -153,12 +135,13 @@ function runInPage(ctx) {
153
135
  }
154
136
  }
155
137
 
156
-
157
138
  const occurrences = [];
158
139
  let applicableCount = 0;
159
140
 
160
141
  const selector = '[role="combobox"]';
161
- const nodes = helpers.queryAllSmart ? helpers.queryAllSmart(selector) : helpers.queryAll(selector);
142
+ const nodes = helpers.queryAllSmart
143
+ ? helpers.queryAllSmart(selector)
144
+ : helpers.queryAll(selector);
162
145
 
163
146
  // Precompute label[for] map for combobox elements that are labelable
164
147
  // native form controls (e.g. <input role="combobox">).
@@ -231,16 +214,22 @@ function runInPage(ctx) {
231
214
  if (res.ok) continue;
232
215
 
233
216
  const eligInfo = getEligibilityInfo
234
- ? (() => { try { return getEligibilityInfo(el, ctx, { targetSet: 'acc' }); } catch { return null; } })()
235
- : null;
217
+ ? (() => {
218
+ try {
219
+ return getEligibilityInfo(el, ctx, { targetSet: 'acc' });
220
+ } catch {
221
+ return null;
222
+ }
223
+ })()
224
+ : null;
236
225
  const stableSelector = helpers.buildSelector ? helpers.buildSelector(el) : 'html';
237
- const html = helpers.getOuterHtmlSnippet ? helpers.getOuterHtmlSnippet(el) : (el.outerHTML || '');
226
+ const html = helpers.getOuterHtmlSnippet ? helpers.getOuterHtmlSnippet(el) : el.outerHTML || '';
238
227
 
239
228
  occurrences.push({
240
229
  selector: stableSelector,
241
230
  html,
242
231
  summary: 'This combobox has no accessible name.',
243
- hint: 'Provide aria-label or aria-labelledby (preferred), or ensure the element has visible text that is not hidden from assistive technologies.',
232
+ hint: "Provide aria-label, aria-labelledby, or a title attribute — visible text content is not exposed as this combobox's accessible name.",
244
233
  i18n: {
245
234
  summaryKey: 'comboboxNamePresent_summary_fail',
246
235
  hintKey: 'comboboxNamePresent_hint_fail',
@@ -253,12 +242,16 @@ function runInPage(ctx) {
253
242
  });
254
243
  }
255
244
 
256
-
257
245
  if (applicableCount === 0) {
258
246
  return { ruleId: rule.ruleId, outcome: 'notApplicable', severity: 'minor', occurrences: [] };
259
247
  }
260
248
  if (occurrences.length) {
261
- return { ruleId: rule.ruleId, outcome: 'fail', severity: rule.defaultSeverity || 'minor', occurrences };
249
+ return {
250
+ ruleId: rule.ruleId,
251
+ outcome: 'fail',
252
+ severity: rule.defaultSeverity || 'minor',
253
+ occurrences
254
+ };
262
255
  }
263
256
  return { ruleId: rule.ruleId, outcome: 'pass', severity: 'minor', occurrences: [] };
264
257
  }