@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
  /**
@@ -12,9 +14,8 @@
12
14
  * declare — either because WAI-ARIA has deprecated it (e.g. "directory",
13
15
  * superseded by role="list") or because it's reserved for user-agent-
14
16
  * internal use only, not a spec deprecation but the same "valid token,
15
- * prohibited for authors" shape (role="generic" — verified 2026-07-20
16
- * against both the WAI-ARIA 1.2 spec text and MDN's explicit "It should
17
- * not be used by web authors" guidance).
17
+ * prohibited for authors" shape (role="generic" — per WAI-ARIA 1.2 and
18
+ * MDN's "It should not be used by web authors" guidance).
18
19
  * @expectation
19
20
  * The role in use is neither deprecated nor author-prohibited. This is a
20
21
  * distinct, atomic decision from aria-roles-valid (existence/
@@ -26,7 +27,8 @@ const id = 'aria-deprecated-role';
26
27
 
27
28
  const meta = {
28
29
  title: 'role attribute must not use a deprecated or author-prohibited ARIA role',
29
- description: 'Checks that an explicit role="" attribute does not use a role deprecated by the WAI-ARIA specification, or one reserved for user-agent-internal use only (e.g. role="generic").',
30
+ description:
31
+ 'Checks that an explicit role="" attribute does not use a role deprecated by the WAI-ARIA specification, or one reserved for user-agent-internal use only (e.g. role="generic").',
30
32
  i18n: {
31
33
  titleKey: 'ariaDeprecatedRole_title',
32
34
  descriptionKey: 'ariaDeprecatedRole_description'
@@ -35,7 +37,13 @@ const meta = {
35
37
  tags: ['wcag2a', 'wcag412', 'aria', 'structure', 'atomic', 'automatic'],
36
38
  wcagSc: ['4.1.2'],
37
39
  normativeMappings: [
38
- { standard: 'WCAG', version: '2.2', requirement: '4.1.2', title: 'Name, Role, Value', conformanceLevel: 'A' }
40
+ {
41
+ standard: 'WCAG',
42
+ version: '2.2',
43
+ requirement: '4.1.2',
44
+ title: 'Name, Role, Value',
45
+ conformanceLevel: 'A'
46
+ }
39
47
  ],
40
48
  defaultSeverity: 'moderate',
41
49
  category: 'robust',
@@ -45,14 +53,16 @@ const meta = {
45
53
  };
46
54
 
47
55
  function runInPage(ctx) {
48
- const { document, helpers, rule } = ctx;
56
+ const { helpers, rule } = ctx;
49
57
 
50
58
  const ariaHelpers = helpers && helpers.aria ? helpers.aria : null;
51
59
  if (!ariaHelpers) {
52
60
  return { ruleId: rule.ruleId, outcome: 'notApplicable', severity: 'minor', occurrences: [] };
53
61
  }
54
62
 
55
- const nodes = helpers.queryAllSmart ? helpers.queryAllSmart('[role]') : helpers.queryAll('[role]');
63
+ const nodes = helpers.queryAllSmart
64
+ ? helpers.queryAllSmart('[role]')
65
+ : helpers.queryAll('[role]');
56
66
 
57
67
  const occurrences = [];
58
68
  let applicableCount = 0;
@@ -72,10 +82,10 @@ function runInPage(ctx) {
72
82
  if (!ariaHelpers.isDeprecatedRole(role)) continue;
73
83
 
74
84
  const stableSelector = helpers.buildSelector ? helpers.buildSelector(el) : 'html';
75
- const html = helpers.getOuterHtmlSnippet ? helpers.getOuterHtmlSnippet(el) : (el.outerHTML || '');
85
+ const html = helpers.getOuterHtmlSnippet ? helpers.getOuterHtmlSnippet(el) : el.outerHTML || '';
76
86
  const guidance = ariaHelpers.getDeprecatedRoleGuidance
77
- ? ariaHelpers.getDeprecatedRoleGuidance(role)
78
- : 'Replace the deprecated role with its recommended replacement.';
87
+ ? ariaHelpers.getDeprecatedRoleGuidance(role)
88
+ : 'Replace the deprecated role with its recommended replacement.';
79
89
 
80
90
  occurrences.push({
81
91
  selector: stableSelector,
@@ -97,7 +107,12 @@ function runInPage(ctx) {
97
107
  return { ruleId: rule.ruleId, outcome: 'notApplicable', severity: 'minor', occurrences: [] };
98
108
  }
99
109
  if (occurrences.length) {
100
- return { ruleId: rule.ruleId, outcome: 'fail', severity: rule.defaultSeverity || 'moderate', occurrences };
110
+ return {
111
+ ruleId: rule.ruleId,
112
+ outcome: 'fail',
113
+ severity: rule.defaultSeverity || 'moderate',
114
+ occurrences
115
+ };
101
116
  }
102
117
  return { ruleId: rule.ruleId, outcome: 'pass', severity: 'minor', occurrences: [] };
103
118
  }
@@ -1,3 +1,5 @@
1
+ /* SPDX-License-Identifier: MPL-2.0 */
2
+
1
3
  'use strict';
2
4
 
3
5
  /**
@@ -14,17 +16,18 @@
14
16
  * @expectation
15
17
  * <body> does not have aria-hidden="true". Hiding the document body
16
18
  * removes the entire page's content and structure from the
17
- * accessibility tree at once — a widely-used reference engine tags this under both 1.3.1
18
- * (Info and Relationships: the page's structure becomes entirely
19
- * non-determinable) and 4.1.2 (Name, Role, Value: nothing in the
20
- * document exposes a role/name/value any longer).
19
+ * accessibility tree at once — both 1.3.1 (Info and Relationships: the
20
+ * page's structure becomes entirely non-determinable) and 4.1.2 (Name,
21
+ * Role, Value: nothing in the document exposes a role/name/value any
22
+ * longer) apply.
21
23
  */
22
24
 
23
25
  const id = 'aria-hidden-body';
24
26
 
25
27
  const meta = {
26
28
  title: 'The document <body> must not be aria-hidden',
27
- description: 'Checks that <body> does not have aria-hidden="true", which would remove the entire page from the accessibility tree.',
29
+ description:
30
+ 'Checks that <body> does not have aria-hidden="true", which would remove the entire page from the accessibility tree.',
28
31
  i18n: {
29
32
  titleKey: 'ariaHiddenBody_title',
30
33
  descriptionKey: 'ariaHiddenBody_description'
@@ -33,8 +36,20 @@ const meta = {
33
36
  tags: ['wcag2a', 'wcag131', 'wcag412', 'structure', 'atomic', 'automatic'],
34
37
  wcagSc: ['1.3.1', '4.1.2'],
35
38
  normativeMappings: [
36
- { standard: 'WCAG', version: '2.2', requirement: '1.3.1', title: 'Info and Relationships', conformanceLevel: 'A' },
37
- { standard: 'WCAG', version: '2.2', requirement: '4.1.2', title: 'Name, Role, Value', conformanceLevel: 'A' }
39
+ {
40
+ standard: 'WCAG',
41
+ version: '2.2',
42
+ requirement: '1.3.1',
43
+ title: 'Info and Relationships',
44
+ conformanceLevel: 'A'
45
+ },
46
+ {
47
+ standard: 'WCAG',
48
+ version: '2.2',
49
+ requirement: '4.1.2',
50
+ title: 'Name, Role, Value',
51
+ conformanceLevel: 'A'
52
+ }
38
53
  ],
39
54
  defaultSeverity: 'critical',
40
55
  category: 'perceivable',
@@ -72,24 +87,34 @@ function runInPage(ctx) {
72
87
  }
73
88
 
74
89
  const stableSelector = helpers.buildSelector ? helpers.buildSelector(body) : 'body';
75
- const html = helpers.getOuterHtmlSnippet ? helpers.getOuterHtmlSnippet(body) : (body.outerHTML || '').slice(0, 200);
90
+ const html = helpers.getOuterHtmlSnippet
91
+ ? helpers.getOuterHtmlSnippet(body)
92
+ : (body.outerHTML || '').slice(0, 200);
76
93
 
77
- const occurrences = [{
78
- selector: stableSelector,
79
- html,
80
- summary: 'The document body has aria-hidden="true", which hides the entire page from assistive technologies.',
81
- hint: 'Remove aria-hidden from <body>. Hide specific elements instead, if that was the intent.',
82
- i18n: {
83
- summaryKey: 'ariaHiddenBody_summary_fail',
84
- hintKey: 'ariaHiddenBody_hint_fail',
85
- params: {}
86
- },
87
- data: {
88
- details: { reasonCode: 'ARIA_HIDDEN_BODY' }
94
+ const occurrences = [
95
+ {
96
+ selector: stableSelector,
97
+ html,
98
+ summary:
99
+ 'The document body has aria-hidden="true", which hides the entire page from assistive technologies.',
100
+ hint: 'Remove aria-hidden from <body>. Hide specific elements instead, if that was the intent.',
101
+ i18n: {
102
+ summaryKey: 'ariaHiddenBody_summary_fail',
103
+ hintKey: 'ariaHiddenBody_hint_fail',
104
+ params: {}
105
+ },
106
+ data: {
107
+ details: { reasonCode: 'ARIA_HIDDEN_BODY' }
108
+ }
89
109
  }
90
- }];
110
+ ];
91
111
 
92
- return { ruleId: rule.ruleId, outcome: 'fail', severity: rule.defaultSeverity || 'critical', occurrences };
112
+ return {
113
+ ruleId: rule.ruleId,
114
+ outcome: 'fail',
115
+ severity: rule.defaultSeverity || 'critical',
116
+ occurrences
117
+ };
93
118
  }
94
119
 
95
- module.exports = { id, meta, runInPage, applicability };
120
+ module.exports = { id, meta, runInPage, applicability };