@surea11y/core 1.3.0 → 1.4.1

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 (166) hide show
  1. package/CHANGELOG.md +87 -2
  2. package/README.md +109 -35
  3. package/bin/surea11y-core.js +20 -0
  4. package/docs/API_STABILITY.md +26 -0
  5. package/docs/ARIA_DEPRECATION.md +95 -0
  6. package/docs/CI_INTEGRATIONS.md +7 -7
  7. package/docs/ENGINE_OPTIONS.md +1 -1
  8. package/docs/I18N.md +12 -9
  9. package/docs/INTEGRATION.md +1 -1
  10. package/docs/LIMITATIONS.md +1 -1
  11. package/docs/REPORT.md +1 -1
  12. package/docs/RULE_CATALOG.md +6 -6
  13. package/package.json +52 -16
  14. package/src/baseline.js +0 -0
  15. package/src/checks/automatic/area-alt-present.js +4 -6
  16. package/src/checks/automatic/aria-allowed-attr.js +663 -134
  17. package/src/checks/automatic/aria-allowed-role.js +2 -0
  18. package/src/checks/automatic/aria-braille-equivalent.js +2 -0
  19. package/src/checks/automatic/aria-conditional-attr.js +8 -7
  20. package/src/checks/automatic/aria-deprecated-role.js +107 -38
  21. package/src/checks/automatic/aria-hidden-body.js +6 -4
  22. package/src/checks/automatic/aria-hidden-focus.js +12 -13
  23. package/src/checks/automatic/aria-prohibited-attr.js +98 -105
  24. package/src/checks/automatic/aria-prohibited-children.js +56 -87
  25. package/src/checks/automatic/aria-required-attr.js +6 -7
  26. package/src/checks/automatic/aria-required-children.js +7 -10
  27. package/src/checks/automatic/aria-required-parent.js +20 -25
  28. package/src/checks/automatic/aria-role-name-present.js +2 -0
  29. package/src/checks/automatic/aria-roles-valid.js +33 -6
  30. package/src/checks/automatic/aria-valid-attr-value.js +18 -15
  31. package/src/checks/automatic/aria-valid-attr.js +2 -0
  32. package/src/checks/automatic/autocomplete-valid.js +39 -1
  33. package/src/checks/automatic/avoid-inline-spacing.js +181 -20
  34. package/src/checks/automatic/binary-control-name-present.js +13 -3
  35. package/src/checks/automatic/button-name-present.js +54 -21
  36. package/src/checks/automatic/canvas-text-alternative-present.js +17 -8
  37. package/src/checks/automatic/combobox-name-present.js +10 -1
  38. package/src/checks/automatic/contrast-computable.js +2 -0
  39. package/src/checks/automatic/contrast-enhanced.js +2 -0
  40. package/src/checks/automatic/contrast-minimum.js +2 -0
  41. package/src/checks/automatic/css-orientation-lock.js +21 -27
  42. package/src/checks/automatic/definition-list-children-valid.js +6 -6
  43. package/src/checks/automatic/deprecated-elements-not-used.js +4 -2
  44. package/src/checks/automatic/dialog-name-present.js +18 -11
  45. package/src/checks/automatic/dlitem-parent-valid.js +2 -0
  46. package/src/checks/automatic/duplicate-id-aria.js +4 -3
  47. package/src/checks/automatic/embed-text-alternative-present.js +2 -0
  48. package/src/checks/automatic/form-control-programmatic-label-present.js +50 -4
  49. package/src/checks/automatic/form-control-single-label.js +110 -43
  50. package/src/checks/automatic/html-xml-lang-mismatch.js +2 -0
  51. package/src/checks/automatic/iframe-focusable-content.js +246 -18
  52. package/src/checks/automatic/iframe-name-present.js +2 -0
  53. package/src/checks/automatic/iframe-title-unique.js +3 -1
  54. package/src/checks/automatic/img-alt-present.js +23 -18
  55. package/src/checks/automatic/input-image-alt-present.js +101 -52
  56. package/src/checks/automatic/label-in-name.js +97 -27
  57. package/src/checks/automatic/language-page-present.js +7 -1
  58. package/src/checks/automatic/link-in-text-block.js +2 -0
  59. package/src/checks/automatic/link-name-present.js +52 -17
  60. package/src/checks/automatic/list-children-valid.js +14 -24
  61. package/src/checks/automatic/listbox-name-present.js +10 -1
  62. package/src/checks/automatic/listitem-parent-valid.js +30 -7
  63. package/src/checks/automatic/menuitem-name-present.js +10 -1
  64. package/src/checks/automatic/meta-refresh-no-exceptions.js +33 -4
  65. package/src/checks/automatic/meta-refresh-timing-absent.js +32 -4
  66. package/src/checks/automatic/meta-viewport-zoom-enabled.js +39 -15
  67. package/src/checks/automatic/meter-name-present.js +12 -4
  68. package/src/checks/automatic/nested-interactive-controls-absent.js +177 -25
  69. package/src/checks/automatic/object-text-alternative-present.js +16 -7
  70. package/src/checks/automatic/option-name-present.js +10 -1
  71. package/src/checks/automatic/page-title-present.js +2 -0
  72. package/src/checks/automatic/progressbar-name-present.js +16 -11
  73. package/src/checks/automatic/role-img-alt-present.js +4 -4
  74. package/src/checks/automatic/searchbox-name-present.js +10 -1
  75. package/src/checks/automatic/server-side-image-map-absent.js +4 -3
  76. package/src/checks/automatic/slider-name-present.js +13 -2
  77. package/src/checks/automatic/spinbutton-name-present.js +10 -1
  78. package/src/checks/automatic/summary-name-present.js +10 -1
  79. package/src/checks/automatic/svg-image-text-alternative-present.js +2 -0
  80. package/src/checks/automatic/svg-text-alternative-present.js +17 -5
  81. package/src/checks/automatic/tab-name-present.js +10 -1
  82. package/src/checks/automatic/table-headers-attr-valid.js +3 -2
  83. package/src/checks/automatic/table-th-has-data-cells.js +69 -6
  84. package/src/checks/automatic/target-size-minimum.js +5 -0
  85. package/src/checks/automatic/td-has-header.js +24 -1
  86. package/src/checks/automatic/textbox-name-present.js +10 -1
  87. package/src/checks/automatic/tooltip-name-present.js +10 -1
  88. package/src/checks/automatic/treeitem-name-present.js +10 -1
  89. package/src/checks/automatic/valid-lang.js +18 -3
  90. package/src/checks/automatic/video-poster-text-alternative-present.js +2 -0
  91. package/src/checks/manual/accesskeys-manual.js +3 -1
  92. package/src/checks/manual/area-alt-decorative-manual.js +2 -0
  93. package/src/checks/manual/area-alt-quality-manual.js +2 -0
  94. package/src/checks/manual/aria-checked-state-mismatch-manual.js +14 -23
  95. package/src/checks/manual/aria-text-manual.js +6 -5
  96. package/src/checks/manual/bypass-blocks-present-manual.js +279 -0
  97. package/src/checks/manual/canvas-text-alternative-quality-manual.js +2 -0
  98. package/src/checks/manual/css-hidden-focus.js +184 -9
  99. package/src/checks/manual/embed-text-alternative-quality-manual.js +18 -13
  100. package/src/checks/manual/empty-heading-manual.js +17 -17
  101. package/src/checks/manual/empty-table-header-manual.js +52 -25
  102. package/src/checks/manual/focus-order-semantics-manual.js +16 -4
  103. package/src/checks/manual/form-control-programmatic-label-quality-manual.js +2 -0
  104. package/src/checks/manual/heading-order-manual.js +28 -1
  105. package/src/checks/manual/identical-links-same-purpose-manual.js +2 -0
  106. package/src/checks/manual/image-redundant-alt-manual.js +21 -1
  107. package/src/checks/manual/img-alt-decorative-manual.js +2 -0
  108. package/src/checks/manual/img-alt-quality-manual.js +2 -0
  109. package/src/checks/manual/input-image-alt-decorative-manual.js +26 -0
  110. package/src/checks/manual/input-image-alt-quality-manual.js +2 -0
  111. package/src/checks/manual/label-title-only-manual.js +29 -22
  112. package/src/checks/manual/landmark-banner-is-top-level-manual.js +51 -55
  113. package/src/checks/manual/landmark-contentinfo-is-top-level-manual.js +44 -31
  114. package/src/checks/manual/landmark-main-is-top-level-manual.js +41 -24
  115. package/src/checks/manual/landmark-no-duplicate-banner-manual.js +16 -23
  116. package/src/checks/manual/landmark-no-duplicate-contentinfo-manual.js +14 -21
  117. package/src/checks/manual/landmark-no-duplicate-main-manual.js +10 -13
  118. package/src/checks/manual/landmark-one-main-manual.js +12 -23
  119. package/src/checks/manual/landmark-unique-manual.js +37 -52
  120. package/src/checks/manual/link-name-quality-manual.js +2 -0
  121. package/src/checks/manual/media-transcript-present-manual.js +2 -0
  122. package/src/checks/manual/meta-viewport-large-manual.js +3 -1
  123. package/src/checks/manual/mouse-only-event-handlers-manual.js +2 -0
  124. package/src/checks/manual/no-autoplay-audio-manual.js +2 -0
  125. package/src/checks/manual/object-text-alternative-quality-manual.js +2 -0
  126. package/src/checks/manual/p-as-heading-manual.js +2 -0
  127. package/src/checks/manual/page-has-heading-one-manual.js +12 -11
  128. package/src/checks/manual/page-title-patterns-manual.js +2 -0
  129. package/src/checks/manual/presentation-role-conflict-manual.js +51 -37
  130. package/src/checks/manual/region-manual.js +27 -36
  131. package/src/checks/manual/scope-attr-valid-manual.js +3 -1
  132. package/src/checks/manual/scrollable-region-focusable-manual.js +2 -0
  133. package/src/checks/manual/skip-link-manual.js +7 -6
  134. package/src/checks/manual/svg-text-alternative-quality-manual.js +2 -0
  135. package/src/checks/manual/tabindex-manual.js +3 -1
  136. package/src/checks/manual/table-duplicate-name-manual.js +5 -4
  137. package/src/checks/manual/table-fake-caption-manual.js +24 -3
  138. package/src/checks/manual/video-caption-manual.js +2 -0
  139. package/src/checks/manual-review.js +2 -0
  140. package/src/core.js +11820 -3317
  141. package/src/index.js +2 -0
  142. package/src/report.js +51 -9
  143. package/src/sarif.js +20 -5
  144. package/surea11y.browser.js +4943 -1388
  145. package/bin/core.js +0 -473
  146. package/docs/CLI.md +0 -128
  147. package/src/catalogs/composites.wcag.js +0 -454
  148. package/src/checks/automatic/bypass-blocks-present.js +0 -215
  149. package/src/checks/rules-and-tags.full.csv +0 -19
  150. package/src/checks/rules-and-tags.full.json +0 -259
  151. package/src/core/aria-helpers.js +0 -1211
  152. package/src/core/contrast-helpers.js +0 -1302
  153. package/src/core/dom-helpers.js +0 -4493
  154. package/src/core/dom-runner.js +0 -787
  155. package/src/core/frame-messaging.js +0 -261
  156. package/src/core/frame-scan.js +0 -190
  157. package/src/core/rollup-composites.js +0 -127
  158. package/src/core/rule-meta.js +0 -176
  159. package/src/coverage/wcag-facets.js +0 -1079
  160. package/src/coverage/wcag-version-map.js +0 -84
  161. package/src/i18n/en.js +0 -1228
  162. package/src/i18n/fr.js +0 -1185
  163. package/src/policy/contracts.js +0 -18
  164. package/src/policy/resolvePolicy.js +0 -59
  165. package/src/policy/schemas/engine-options.schema.json +0 -103
  166. package/src/policy/schemas/policy-contract.schema.json +0 -40
package/src/index.js CHANGED
@@ -1,3 +1,5 @@
1
+ /* SPDX-License-Identifier: MPL-2.0 */
2
+
1
3
  'use strict';
2
4
 
3
5
  // surea11y public API: export the generated core surface verbatim
package/src/report.js CHANGED
@@ -1,3 +1,5 @@
1
+ /* SPDX-License-Identifier: MPL-2.0 */
2
+
1
3
  'use strict';
2
4
 
3
5
  /**
@@ -56,6 +58,32 @@ function countByOutcome(checksResults) {
56
58
  return counts;
57
59
  }
58
60
 
61
+ function getOccurrenceOutcome(ruleResult, occurrence) {
62
+ const occurrenceOutcome =
63
+ occurrence &&
64
+ (occurrence.occurrenceOutcome === 'fail' || occurrence.occurrenceOutcome === 'cantTell'
65
+ ? occurrence.occurrenceOutcome
66
+ : occurrence.outcome === 'fail' || occurrence.outcome === 'cantTell'
67
+ ? occurrence.outcome
68
+ : null);
69
+ if (occurrenceOutcome) return occurrenceOutcome;
70
+ return (
71
+ ruleResult &&
72
+ (ruleResult.outcome === 'fail' || ruleResult.outcome === 'cantTell' ? ruleResult.outcome : null)
73
+ );
74
+ }
75
+
76
+ function getCardOutcome(ruleResult) {
77
+ if (!ruleResult || !Array.isArray(ruleResult.occurrences)) return ruleResult.outcome;
78
+ let hasCantTell = false;
79
+ for (const occ of ruleResult.occurrences) {
80
+ const outcome = getOccurrenceOutcome(ruleResult, occ);
81
+ if (outcome === 'fail') return 'fail';
82
+ if (outcome === 'cantTell') hasCantTell = true;
83
+ }
84
+ return hasCantTell ? 'cantTell' : ruleResult.outcome;
85
+ }
86
+
59
87
  // One plain-language headline + one horizontal stacked bar + a legend with
60
88
  // icon+label+count (status color is never the only signal) -- the first
61
89
  // thing a reader sees; exhaustive detail lives in the collapsed
@@ -190,33 +218,46 @@ const MAX_CARDS = 24;
190
218
  function renderCards(checksResults) {
191
219
  const withIssues = checksResults.filter(
192
220
  (r) =>
193
- (r.outcome === 'fail' || r.outcome === 'cantTell') &&
194
221
  Array.isArray(r.occurrences) &&
195
- r.occurrences.length > 0
222
+ r.occurrences.some((occ) => {
223
+ const outcome = getOccurrenceOutcome(r, occ);
224
+ return outcome === 'fail' || outcome === 'cantTell';
225
+ })
196
226
  );
197
227
  if (!withIssues.length) {
198
228
  return '<p class="note">No fail/cantTell rules with occurrences on this scan.</p>';
199
229
  }
200
230
 
201
231
  const sorted = withIssues.slice().sort((a, b) => {
202
- if (a.outcome !== b.outcome) return a.outcome === 'fail' ? -1 : 1;
232
+ const aOutcome = getCardOutcome(a);
233
+ const bOutcome = getCardOutcome(b);
234
+ if (aOutcome !== bOutcome) return aOutcome === 'fail' ? -1 : 1;
203
235
  return b.occurrences.length - a.occurrences.length;
204
236
  });
205
237
  const shown = sorted.slice(0, MAX_CARDS);
206
238
 
207
239
  const cards = shown
208
240
  .map((r) => {
209
- const info = OUTCOME_INFO[r.outcome];
210
- const occ = r.occurrences[0];
241
+ const cardOutcome = getCardOutcome(r);
242
+ const info = OUTCOME_INFO[cardOutcome] || OUTCOME_INFO.notApplicable;
243
+ const occurrenceCounts = { fail: 0, cantTell: 0 };
244
+ for (const occ of r.occurrences) {
245
+ const outcome = getOccurrenceOutcome(r, occ);
246
+ if (outcome === 'fail' || outcome === 'cantTell') occurrenceCounts[outcome] += 1;
247
+ }
248
+ const representative =
249
+ r.occurrences.find((occ) => getOccurrenceOutcome(r, occ) === cardOutcome) ||
250
+ r.occurrences[0];
211
251
  const wcagChips = ((r.meta && r.meta.normativeMappings) || [])
212
252
  .map(
213
253
  (m) =>
214
254
  `<span class="chip" style="background:${STATUS.neutral.bg};color:${STATUS.neutral.color}">WCAG ${esc(m.requirement)}</span>`
215
255
  )
216
256
  .join('');
257
+ const hasMixedOutcomes = occurrenceCounts.fail > 0 && occurrenceCounts.cantTell > 0;
217
258
  const countLabel =
218
259
  r.occurrences.length > 1
219
- ? ` <span class="card-count">× ${r.occurrences.length.toLocaleString()}</span>`
260
+ ? ` <span class="card-count">× ${r.occurrences.length.toLocaleString()}${hasMixedOutcomes ? ` (${occurrenceCounts.fail.toLocaleString()} fail / ${occurrenceCounts.cantTell.toLocaleString()} needs review)` : ''}</span>`
220
261
  : '';
221
262
 
222
263
  return `<div class="card">
@@ -226,8 +267,8 @@ function renderCards(checksResults) {
226
267
  </div>
227
268
  <div class="card-body">
228
269
  <div class="card-meta">${wcagChips}</div>
229
- <div class="card-selector"><span class="card-selector-label">Selector:</span> <code>${esc(occ.selector || '(none)')}</code></div>
230
- <div class="card-snippet">${esc(occ.summary)}${occ.hint ? ` — ${esc(occ.hint)}` : ''}</div>
270
+ <div class="card-selector"><span class="card-selector-label">Selector:</span> <code>${esc(representative.selector || '(none)')}</code></div>
271
+ <div class="card-snippet">${esc(representative.summary)}${representative.hint ? ` — ${esc(representative.hint)}` : ''}</div>
231
272
  ${r.occurrences.length > 1 ? `<p class="card-note">Selector/summary above are from one representative occurrence — ${r.occurrences.length.toLocaleString()} total on this rule.</p>` : ''}
232
273
  </div>
233
274
  </div>`;
@@ -250,9 +291,10 @@ function flattenOccurrences(checksResults) {
250
291
  for (const r of checksResults) {
251
292
  if (!Array.isArray(r.occurrences)) continue;
252
293
  for (const occ of r.occurrences) {
294
+ const occurrenceOutcome = getOccurrenceOutcome(r, occ);
253
295
  rows.push({
254
296
  ruleId: r.ruleId,
255
- outcome: r.outcome,
297
+ outcome: occurrenceOutcome || r.outcome,
256
298
  severity: r.severity,
257
299
  selector: occ.selector || '',
258
300
  html: occ.html || '',
package/src/sarif.js CHANGED
@@ -1,10 +1,12 @@
1
+ /* SPDX-License-Identifier: MPL-2.0 */
2
+
1
3
  'use strict';
2
4
 
3
5
  /**
4
6
  * Renders one scan result as a SARIF 2.1.0 log (docs/SARIF.md) for GitHub
5
7
  * Code Scanning / other SARIF-consuming dashboards -- a different output
6
- * shape from --json's raw result, purpose-built for that ecosystem (see
7
- * docs/CLI.md's --sarif flag).
8
+ * shape from --json's raw result, purpose-built for that ecosystem (written
9
+ * by @surea11y/cli's --sarif flag, see its docs/CLI.md).
8
10
  *
9
11
  * Only fail/cantTell occurrences become SARIF results (a pass/notApplicable
10
12
  * result has no occurrences at all -- see docs/OUTPUT_SCHEMA.md), the same
@@ -110,6 +112,18 @@ function buildResult(check, occurrence, level, artifactUri) {
110
112
  };
111
113
  }
112
114
 
115
+ function getOccurrenceOutcome(check, occurrence) {
116
+ const occurrenceOutcome =
117
+ occurrence &&
118
+ (occurrence.occurrenceOutcome === 'fail' || occurrence.occurrenceOutcome === 'cantTell'
119
+ ? occurrence.occurrenceOutcome
120
+ : occurrence.outcome === 'fail' || occurrence.outcome === 'cantTell'
121
+ ? occurrence.outcome
122
+ : null);
123
+ if (occurrenceOutcome) return occurrenceOutcome;
124
+ return check && (check.outcome === 'fail' || check.outcome === 'cantTell') ? check.outcome : null;
125
+ }
126
+
113
127
  function renderSarifReport(result, options = {}) {
114
128
  const { toolVersion, informationUri, baselineEntries } = options;
115
129
  const artifactUri = artifactUriFromResult(result);
@@ -133,7 +147,8 @@ function renderSarifReport(result, options = {}) {
133
147
  for (const occurrence of check.occurrences) {
134
148
  if (!occurrence) continue;
135
149
 
136
- if (check.outcome === 'fail') {
150
+ const occurrenceOutcome = getOccurrenceOutcome(check, occurrence);
151
+ if (occurrenceOutcome === 'fail') {
137
152
  const reasonCode = getReasonCode(occurrence);
138
153
  const html = typeof occurrence.html === 'string' ? occurrence.html : '';
139
154
  const key = computeBaselineKey(check.ruleId, reasonCode, html);
@@ -143,7 +158,7 @@ function renderSarifReport(result, options = {}) {
143
158
  continue; // already known via the baseline -- omit, don't re-gate
144
159
  }
145
160
  failResults.push(buildResult(check, occurrence, 'error', artifactUri));
146
- } else {
161
+ } else if (occurrenceOutcome === 'cantTell') {
147
162
  cantTellResults.push(buildResult(check, occurrence, 'warning', artifactUri));
148
163
  }
149
164
  }
@@ -157,7 +172,7 @@ function renderSarifReport(result, options = {}) {
157
172
  tool: {
158
173
  driver: {
159
174
  name: 'surea11y',
160
- informationUri: informationUri || 'https://github.com/rumoroso/surea11y-core',
175
+ informationUri: informationUri || 'https://github.com/SureA11y/core',
161
176
  version: toolVersion || '0.0.0',
162
177
  rules
163
178
  }