@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
@@ -64,7 +64,13 @@ const meta = {
64
64
  tags: ['wcag21aa', 'wcag134', 'structure', 'atomic', 'automatic'],
65
65
  wcagSc: ['1.3.4'],
66
66
  normativeMappings: [
67
- { standard: 'WCAG', version: '2.2', requirement: '1.3.4', title: 'Orientation', conformanceLevel: 'AA' }
67
+ {
68
+ standard: 'WCAG',
69
+ version: '2.2',
70
+ requirement: '1.3.4',
71
+ title: 'Orientation',
72
+ conformanceLevel: 'AA'
73
+ }
68
74
  ],
69
75
  defaultSeverity: 'serious',
70
76
  category: 'perceivable',
@@ -87,7 +93,9 @@ function runInPage(ctx) {
87
93
  const CSS_MEDIA_RULE = 4;
88
94
  const CSS_STYLE_RULE = 1;
89
95
 
90
- function trim(v) { return (v == null ? '' : String(v)).trim(); }
96
+ function trim(v) {
97
+ return (v == null ? '' : String(v)).trim();
98
+ }
91
99
 
92
100
  // Converts a rotate()/rotateZ() angle argument (with unit) to degrees.
93
101
  // Matches a widely-used reference engine's own getAngleInDegrees: only deg/grad/rad/turn are
@@ -126,9 +134,13 @@ function runInPage(ctx) {
126
134
  // near 90 or 270 degrees (mod 90, once the 0/180 case is excluded) is.
127
135
  function isLockingRotation(styleDecl) {
128
136
  if (!styleDecl) return false;
129
- const transformVal = trim(styleDecl.transform) || trim(styleDecl.getPropertyValue ? styleDecl.getPropertyValue('-webkit-transform') : '');
137
+ const transformVal =
138
+ trim(styleDecl.transform) ||
139
+ trim(styleDecl.getPropertyValue ? styleDecl.getPropertyValue('-webkit-transform') : '');
130
140
  // The standalone CSS `rotate` property (distinct from `transform: rotate()`) -- that reference engine checks this too.
131
- const rotatePropVal = trim(styleDecl.getPropertyValue ? styleDecl.getPropertyValue('rotate') : '');
141
+ const rotatePropVal = trim(
142
+ styleDecl.getPropertyValue ? styleDecl.getPropertyValue('rotate') : ''
143
+ );
132
144
 
133
145
  let degrees = rotateDegreesFromTransform(transformVal);
134
146
  if (!degrees && rotatePropVal) degrees = angleToDegrees(rotatePropVal);
@@ -189,9 +201,10 @@ function runInPage(ctx) {
189
201
  return { ruleId: rule.ruleId, outcome: 'pass', severity: 'minor', occurrences: [] };
190
202
  }
191
203
 
192
- const target = document.documentElement || (document.body || null);
204
+ const target = document.documentElement || document.body || null;
193
205
  const stableSelector = helpers.buildSelector && target ? helpers.buildSelector(target) : 'html';
194
- const html = helpers.getOuterHtmlSnippet && target ? helpers.getOuterHtmlSnippet(target) : '<html>';
206
+ const html =
207
+ helpers.getOuterHtmlSnippet && target ? helpers.getOuterHtmlSnippet(target) : '<html>';
195
208
 
196
209
  const occurrences = findings.map((f) => ({
197
210
  selector: stableSelector,
@@ -204,11 +217,20 @@ function runInPage(ctx) {
204
217
  params: { mediaText: f.mediaText, selectorText: f.selectorText }
205
218
  },
206
219
  data: {
207
- details: { reasonCode: 'ORIENTATION_MEDIA_ROTATE_TRANSFORM', mediaText: f.mediaText, selectorText: f.selectorText }
220
+ details: {
221
+ reasonCode: 'ORIENTATION_MEDIA_ROTATE_TRANSFORM',
222
+ mediaText: f.mediaText,
223
+ selectorText: f.selectorText
224
+ }
208
225
  }
209
226
  }));
210
227
 
211
- return { ruleId: rule.ruleId, outcome: 'fail', severity: rule.defaultSeverity || 'serious', occurrences };
228
+ return {
229
+ ruleId: rule.ruleId,
230
+ outcome: 'fail',
231
+ severity: rule.defaultSeverity || 'serious',
232
+ occurrences
233
+ };
212
234
  }
213
235
 
214
236
  module.exports = { id, meta, runInPage, applicability };
@@ -35,7 +35,8 @@ const id = 'definition-list-children-valid';
35
35
 
36
36
  const meta = {
37
37
  title: 'Description lists must be structured correctly',
38
- description: 'Checks that <dl> elements only directly contain <dt>/<dd> groups (optionally wrapped in one <div>), <script>, <template>, or <style>.',
38
+ description:
39
+ 'Checks that <dl> elements only directly contain <dt>/<dd> groups (optionally wrapped in one <div>), <script>, <template>, or <style>.',
39
40
  i18n: {
40
41
  titleKey: 'definitionListChildrenValid_title',
41
42
  descriptionKey: 'definitionListChildrenValid_description'
@@ -44,7 +45,13 @@ const meta = {
44
45
  tags: ['wcag2a', 'wcag131', 'structure', 'atomic', 'automatic', 'list'],
45
46
  wcagSc: ['1.3.1'],
46
47
  normativeMappings: [
47
- { standard: 'WCAG', version: '2.2', requirement: '1.3.1', title: 'Info and Relationships', conformanceLevel: 'A' }
48
+ {
49
+ standard: 'WCAG',
50
+ version: '2.2',
51
+ requirement: '1.3.1',
52
+ title: 'Info and Relationships',
53
+ conformanceLevel: 'A'
54
+ }
48
55
  ],
49
56
  defaultSeverity: 'serious',
50
57
  category: 'perceivable',
@@ -54,7 +61,7 @@ const meta = {
54
61
  };
55
62
 
56
63
  function runInPage(ctx) {
57
- const { document, helpers, rule } = ctx;
64
+ const { helpers, rule } = ctx;
58
65
 
59
66
  // Declared inside runInPage — see scripts/build-core.js header
60
67
  // ("runInPage MUST be self-contained").
@@ -89,10 +96,17 @@ function runInPage(ctx) {
89
96
  const invalidTags = [];
90
97
  for (const node of flattened) {
91
98
  const tag = node.tagName.toLowerCase();
92
- if (tag === 'dt') { hasDt = true; continue; }
93
- if (tag === 'dd') { hasDd = true; continue; }
99
+ if (tag === 'dt') {
100
+ hasDt = true;
101
+ continue;
102
+ }
103
+ if (tag === 'dd') {
104
+ hasDd = true;
105
+ continue;
106
+ }
94
107
  if (!PASSTHROUGH_TAGS.has(tag)) invalidTags.push(tag);
95
108
  }
109
+ const dedupedInvalidTags = [...new Set(invalidTags)];
96
110
 
97
111
  // Verified 2026-07-20 against a widely-used reference engine's definition-list check:
98
112
  // the dt/dd pairing is only required "when not empty" — a <dl> with
@@ -101,19 +115,21 @@ function runInPage(ctx) {
101
115
  // is vacuously fine, not a violation. Only an UNBALANCED pairing (dt
102
116
  // present without any dd, or vice versa) is a real structural problem.
103
117
  const reasonCode = invalidTags.length
104
- ? 'DL_INVALID_CHILD'
105
- : ((hasDt || hasDd) && !(hasDt && hasDd) ? 'DL_NO_DT_DD' : null);
118
+ ? 'DL_INVALID_CHILD'
119
+ : (hasDt || hasDd) && !(hasDt && hasDd)
120
+ ? 'DL_NO_DT_DD'
121
+ : null;
106
122
  if (!reasonCode) continue;
107
123
 
108
124
  const stableSelector = helpers.buildSelector ? helpers.buildSelector(el) : 'html';
109
- const html = helpers.getOuterHtmlSnippet ? helpers.getOuterHtmlSnippet(el) : (el.outerHTML || '');
125
+ const html = helpers.getOuterHtmlSnippet ? helpers.getOuterHtmlSnippet(el) : el.outerHTML || '';
110
126
 
111
127
  const summary = invalidTags.length
112
- ? 'This description list contains a direct or wrapped child that is not part of a dt/dd group.'
113
- : 'This description list has no <dt>/<dd> term-definition group.';
128
+ ? 'This description list contains a direct or wrapped child that is not part of a dt/dd group.'
129
+ : 'This description list has no <dt>/<dd> term-definition group.';
114
130
  const hint = invalidTags.length
115
- ? 'Only use <dt>/<dd> (optionally wrapped in one <div>), <script>, <template>, or <style> inside <dl>.'
116
- : 'Add at least one <dt>/<dd> pair inside this <dl>.';
131
+ ? 'Only use <dt>/<dd> (optionally wrapped in one <div>), <script>, <template>, or <style> inside <dl>.'
132
+ : 'Add at least one <dt>/<dd> pair inside this <dl>.';
117
133
 
118
134
  occurrences.push({
119
135
  selector: stableSelector,
@@ -122,12 +138,12 @@ function runInPage(ctx) {
122
138
  hint,
123
139
  i18n: {
124
140
  summaryKey: invalidTags.length
125
- ? 'definitionListChildrenValid_summary_fail_invalidChild'
126
- : 'definitionListChildrenValid_summary_fail_noDtDd',
141
+ ? 'definitionListChildrenValid_summary_fail_invalidChild'
142
+ : 'definitionListChildrenValid_summary_fail_noDtDd',
127
143
  hintKey: invalidTags.length
128
- ? 'definitionListChildrenValid_hint_fail_invalidChild'
129
- : 'definitionListChildrenValid_hint_fail_noDtDd',
130
- params: { invalidChildren: invalidTags.join(', ') }
144
+ ? 'definitionListChildrenValid_hint_fail_invalidChild'
145
+ : 'definitionListChildrenValid_hint_fail_noDtDd',
146
+ params: { invalidChildren: dedupedInvalidTags.join(', ') }
131
147
  },
132
148
  data: {
133
149
  details: { reasonCode, invalidChildren: invalidTags }
@@ -139,9 +155,14 @@ function runInPage(ctx) {
139
155
  return { ruleId: rule.ruleId, outcome: 'notApplicable', severity: 'minor', occurrences: [] };
140
156
  }
141
157
  if (occurrences.length) {
142
- return { ruleId: rule.ruleId, outcome: 'fail', severity: rule.defaultSeverity || 'serious', occurrences };
158
+ return {
159
+ ruleId: rule.ruleId,
160
+ outcome: 'fail',
161
+ severity: rule.defaultSeverity || 'serious',
162
+ occurrences
163
+ };
143
164
  }
144
165
  return { ruleId: rule.ruleId, outcome: 'pass', severity: 'minor', occurrences: [] };
145
166
  }
146
167
 
147
- module.exports = { id, meta, runInPage };
168
+ module.exports = { id, meta, runInPage };
@@ -29,7 +29,8 @@ const id = 'deprecated-elements-not-used';
29
29
 
30
30
  const meta = {
31
31
  title: 'Obsolete non-stoppable elements (<blink>, <marquee>) must not be used',
32
- description: 'Checks that deprecated, non-standard HTML elements whose blinking/scrolling content cannot be paused, stopped, or hidden by the user (<blink>, <marquee>) are not present.',
32
+ description:
33
+ 'Checks that deprecated, non-standard HTML elements whose blinking/scrolling content cannot be paused, stopped, or hidden by the user (<blink>, <marquee>) are not present.',
33
34
  i18n: {
34
35
  titleKey: 'deprecatedElements_title',
35
36
  descriptionKey: 'deprecatedElements_description'
@@ -38,7 +39,13 @@ const meta = {
38
39
  tags: ['wcag2a', 'wcag222', 'structure', 'atomic', 'automatic'],
39
40
  wcagSc: ['2.2.2'],
40
41
  normativeMappings: [
41
- { standard: 'WCAG', version: '2.2', requirement: '2.2.2', title: 'Pause, Stop, Hide', conformanceLevel: 'A' }
42
+ {
43
+ standard: 'WCAG',
44
+ version: '2.2',
45
+ requirement: '2.2.2',
46
+ title: 'Pause, Stop, Hide',
47
+ conformanceLevel: 'A'
48
+ }
42
49
  ],
43
50
  defaultSeverity: 'serious',
44
51
  category: 'operable',
@@ -48,9 +55,11 @@ const meta = {
48
55
  };
49
56
 
50
57
  function runInPage(ctx) {
51
- const { document, helpers, rule } = ctx;
58
+ const { helpers, rule } = ctx;
52
59
 
53
- const nodes = helpers.queryAllSmart ? helpers.queryAllSmart('blink, marquee') : helpers.queryAll('blink, marquee');
60
+ const nodes = helpers.queryAllSmart
61
+ ? helpers.queryAllSmart('blink, marquee')
62
+ : helpers.queryAll('blink, marquee');
54
63
 
55
64
  const occurrences = [];
56
65
  let applicableCount = 0;
@@ -62,7 +71,7 @@ function runInPage(ctx) {
62
71
 
63
72
  const tag = el.tagName.toLowerCase();
64
73
  const stableSelector = helpers.buildSelector ? helpers.buildSelector(el) : 'html';
65
- const html = helpers.getOuterHtmlSnippet ? helpers.getOuterHtmlSnippet(el) : (el.outerHTML || '');
74
+ const html = helpers.getOuterHtmlSnippet ? helpers.getOuterHtmlSnippet(el) : el.outerHTML || '';
66
75
 
67
76
  occurrences.push({
68
77
  selector: stableSelector,
@@ -84,9 +93,14 @@ function runInPage(ctx) {
84
93
  return { ruleId: rule.ruleId, outcome: 'notApplicable', severity: 'minor', occurrences: [] };
85
94
  }
86
95
  if (occurrences.length) {
87
- return { ruleId: rule.ruleId, outcome: 'fail', severity: rule.defaultSeverity || 'serious', occurrences };
96
+ return {
97
+ ruleId: rule.ruleId,
98
+ outcome: 'fail',
99
+ severity: rule.defaultSeverity || 'serious',
100
+ occurrences
101
+ };
88
102
  }
89
103
  return { ruleId: rule.ruleId, outcome: 'pass', severity: 'minor', occurrences: [] };
90
104
  }
91
105
 
92
- module.exports = { id, meta, runInPage };
106
+ module.exports = { id, meta, runInPage };
@@ -4,16 +4,23 @@ const id = 'dialog-name-present';
4
4
 
5
5
  const meta = {
6
6
  title: 'Dialogs have an accessible name',
7
- description: 'Checks that elements with role="dialog" or role="alertdialog" expose a non-empty accessible name.',
7
+ description:
8
+ 'Checks that elements with role="dialog" or role="alertdialog" expose a non-empty accessible name.',
8
9
  i18n: {
9
10
  titleKey: 'dialogNamePresent_title',
10
11
  descriptionKey: 'dialogNamePresent_description'
11
12
  },
12
13
  helpUrl: null,
13
- tags: ['wcag2a','wcag412','structure','atomic','automatic','name','dialog'],
14
+ tags: ['wcag2a', 'wcag412', 'structure', 'atomic', 'automatic', 'name', 'dialog'],
14
15
  wcagSc: ['4.1.2'],
15
16
  normativeMappings: [
16
- { standard: 'WCAG', version: '2.2', requirement: '4.1.2', title: 'Name, Role, Value', conformanceLevel: 'A' }
17
+ {
18
+ standard: 'WCAG',
19
+ version: '2.2',
20
+ requirement: '4.1.2',
21
+ title: 'Name, Role, Value',
22
+ conformanceLevel: 'A'
23
+ }
17
24
  ],
18
25
  defaultSeverity: 'serious',
19
26
  category: 'robust',
@@ -24,69 +31,22 @@ const meta = {
24
31
 
25
32
  function runInPage(ctx) {
26
33
  const { document, helpers, rule } = ctx;
27
- const getEligibilityInfo = helpers && typeof helpers.getEligibilityInfo === 'function'
28
- ? helpers.getEligibilityInfo
29
- : null;
30
-
34
+ const getEligibilityInfo =
35
+ helpers && typeof helpers.getEligibilityInfo === 'function' ? helpers.getEligibilityInfo : null;
31
36
 
32
37
  function normalizeWs(s) {
33
- return String(s || '').replace(/\s+/g, ' ').trim();
38
+ return String(s || '')
39
+ .replace(/\s+/g, ' ')
40
+ .trim();
34
41
  }
35
42
 
36
43
  function getAttr(el, name) {
37
44
  try {
38
45
  if (!el || !el.getAttribute) return '';
39
46
  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;
59
- }
60
-
61
- function buildLabelForMap(doc) {
62
- const map = new Map(); // id -> label element (first)
63
- try {
64
- const labels = doc && doc.getElementsByTagName ? doc.getElementsByTagName('label') : [];
65
- for (let i = 0; i < labels.length; i += 1) {
66
- const lab = labels[i];
67
- if (!lab || !lab.getAttribute) continue;
68
- const f = normalizeWs(lab.getAttribute('for'));
69
- if (!f) continue;
70
- if (!map.has(f)) map.set(f, lab);
71
- }
72
- } catch {}
73
- return map;
74
- }
75
-
76
- function getConservativeSubtreeText(document, container) {
77
- // "Name from content" — recurses into descendants and uses each one's
78
- // own accessible name (img alt, aria-label/aria-labelledby, title) when
79
- // it has one, not just literal text nodes. See getContentNameInfo's
80
- // header comment in src/core/dom-helpers.js for the full rationale
81
- // (this replaced a text-node-only TreeWalker that missed the common
82
- // "<a><img alt='...'></a>" logo-link / "<button><img alt='...'></button>"
83
- // icon-button pattern).
84
- if (helpers.getContentNameInfo) {
85
- const info = helpers.getContentNameInfo(container, ctx);
86
- return info && info.present ? info.value : '';
47
+ } catch {
48
+ return '';
87
49
  }
88
- const t = (container && container.textContent) ? String(container.textContent) : '';
89
- return t.replace(/\s+/g, ' ').trim();
90
50
  }
91
51
 
92
52
  function resolveAriaLabelledbyText(document, el, maxRefs) {
@@ -114,17 +74,9 @@ function runInPage(ctx) {
114
74
  return '';
115
75
  }
116
76
 
117
- function getInputValueName(el) {
118
- try {
119
- const type = getAttr(el, 'type').toLowerCase();
120
- if (type !== 'button' && type !== 'submit' && type !== 'reset') return '';
121
- const v = getAttr(el, 'value');
122
- return v;
123
- } catch { return ''; }
124
- }
125
-
126
77
  function isEligibleAcc(helpers, el, ctx) {
127
- const fn = helpers && typeof helpers.isAccTreeEligible === 'function' ? helpers.isAccTreeEligible : null;
78
+ const fn =
79
+ helpers && typeof helpers.isAccTreeEligible === 'function' ? helpers.isAccTreeEligible : null;
128
80
  if (!fn) return true;
129
81
  try {
130
82
  const r = fn(el, ctx);
@@ -135,13 +87,13 @@ function runInPage(ctx) {
135
87
  }
136
88
  }
137
89
 
138
-
139
90
  const occurrences = [];
140
91
  let applicableCount = 0;
141
92
 
142
93
  const selector = '[role="dialog"],[role="alertdialog"]';
143
- const nodes = helpers.queryAllSmart ? helpers.queryAllSmart(selector) : helpers.queryAll(selector);
144
-
94
+ const nodes = helpers.queryAllSmart
95
+ ? helpers.queryAllSmart(selector)
96
+ : helpers.queryAll(selector);
145
97
 
146
98
  function evaluate(el) {
147
99
  const ariaLabel = getAttr(el, 'aria-label');
@@ -173,10 +125,16 @@ function runInPage(ctx) {
173
125
  if (res.ok) continue;
174
126
 
175
127
  const eligInfo = getEligibilityInfo
176
- ? (() => { try { return getEligibilityInfo(el, ctx, { targetSet: 'acc' }); } catch { return null; } })()
177
- : null;
128
+ ? (() => {
129
+ try {
130
+ return getEligibilityInfo(el, ctx, { targetSet: 'acc' });
131
+ } catch {
132
+ return null;
133
+ }
134
+ })()
135
+ : null;
178
136
  const stableSelector = helpers.buildSelector ? helpers.buildSelector(el) : 'html';
179
- const html = helpers.getOuterHtmlSnippet ? helpers.getOuterHtmlSnippet(el) : (el.outerHTML || '');
137
+ const html = helpers.getOuterHtmlSnippet ? helpers.getOuterHtmlSnippet(el) : el.outerHTML || '';
180
138
 
181
139
  occurrences.push({
182
140
  selector: stableSelector,
@@ -195,12 +153,16 @@ function runInPage(ctx) {
195
153
  });
196
154
  }
197
155
 
198
-
199
156
  if (applicableCount === 0) {
200
157
  return { ruleId: rule.ruleId, outcome: 'notApplicable', severity: 'minor', occurrences: [] };
201
158
  }
202
159
  if (occurrences.length) {
203
- return { ruleId: rule.ruleId, outcome: 'fail', severity: rule.defaultSeverity || 'minor', occurrences };
160
+ return {
161
+ ruleId: rule.ruleId,
162
+ outcome: 'fail',
163
+ severity: rule.defaultSeverity || 'minor',
164
+ occurrences
165
+ };
204
166
  }
205
167
  return { ruleId: rule.ruleId, outcome: 'pass', severity: 'minor', occurrences: [] };
206
168
  }
@@ -23,7 +23,8 @@ const id = 'dlitem-parent-valid';
23
23
 
24
24
  const meta = {
25
25
  title: 'Description-list items must be inside a description list',
26
- description: 'Checks that <dt>/<dd> elements are contained by a <dl>, directly or via one wrapping <div>.',
26
+ description:
27
+ 'Checks that <dt>/<dd> elements are contained by a <dl>, directly or via one wrapping <div>.',
27
28
  i18n: {
28
29
  titleKey: 'dlitemParentValid_title',
29
30
  descriptionKey: 'dlitemParentValid_description'
@@ -32,7 +33,13 @@ const meta = {
32
33
  tags: ['wcag2a', 'wcag131', 'structure', 'atomic', 'automatic', 'list'],
33
34
  wcagSc: ['1.3.1'],
34
35
  normativeMappings: [
35
- { standard: 'WCAG', version: '2.2', requirement: '1.3.1', title: 'Info and Relationships', conformanceLevel: 'A' }
36
+ {
37
+ standard: 'WCAG',
38
+ version: '2.2',
39
+ requirement: '1.3.1',
40
+ title: 'Info and Relationships',
41
+ conformanceLevel: 'A'
42
+ }
36
43
  ],
37
44
  defaultSeverity: 'serious',
38
45
  category: 'perceivable',
@@ -42,9 +49,11 @@ const meta = {
42
49
  };
43
50
 
44
51
  function runInPage(ctx) {
45
- const { document, helpers, rule } = ctx;
52
+ const { helpers, rule } = ctx;
46
53
 
47
- const nodes = helpers.queryAllSmart ? helpers.queryAllSmart('dt, dd') : helpers.queryAll('dt, dd');
54
+ const nodes = helpers.queryAllSmart
55
+ ? helpers.queryAllSmart('dt, dd')
56
+ : helpers.queryAll('dt, dd');
48
57
 
49
58
  const occurrences = [];
50
59
  let applicableCount = 0;
@@ -61,7 +70,8 @@ function runInPage(ctx) {
61
70
 
62
71
  if (!valid && parentTag === 'div') {
63
72
  const grandparent = parent.parentElement;
64
- const grandparentTag = grandparent && grandparent.tagName ? grandparent.tagName.toLowerCase() : '';
73
+ const grandparentTag =
74
+ grandparent && grandparent.tagName ? grandparent.tagName.toLowerCase() : '';
65
75
  valid = grandparentTag === 'dl';
66
76
  }
67
77
 
@@ -69,7 +79,7 @@ function runInPage(ctx) {
69
79
 
70
80
  const tag = el.tagName.toLowerCase();
71
81
  const stableSelector = helpers.buildSelector ? helpers.buildSelector(el) : 'html';
72
- const html = helpers.getOuterHtmlSnippet ? helpers.getOuterHtmlSnippet(el) : (el.outerHTML || '');
82
+ const html = helpers.getOuterHtmlSnippet ? helpers.getOuterHtmlSnippet(el) : el.outerHTML || '';
73
83
 
74
84
  occurrences.push({
75
85
  selector: stableSelector,
@@ -91,9 +101,14 @@ function runInPage(ctx) {
91
101
  return { ruleId: rule.ruleId, outcome: 'notApplicable', severity: 'minor', occurrences: [] };
92
102
  }
93
103
  if (occurrences.length) {
94
- return { ruleId: rule.ruleId, outcome: 'fail', severity: rule.defaultSeverity || 'serious', occurrences };
104
+ return {
105
+ ruleId: rule.ruleId,
106
+ outcome: 'fail',
107
+ severity: rule.defaultSeverity || 'serious',
108
+ occurrences
109
+ };
95
110
  }
96
111
  return { ruleId: rule.ruleId, outcome: 'pass', severity: 'minor', occurrences: [] };
97
112
  }
98
113
 
99
- module.exports = { id, meta, runInPage };
114
+ module.exports = { id, meta, runInPage };
@@ -29,7 +29,8 @@ const id = 'duplicate-id-aria';
29
29
 
30
30
  const meta = {
31
31
  title: 'IDs referenced by ARIA must be unique',
32
- description: 'Checks that any id value referenced by an ARIA ID-reference attribute (aria-labelledby, aria-describedby, aria-owns, aria-controls, aria-activedescendant, aria-flowto, aria-errormessage, aria-details) is unique in the document.',
32
+ description:
33
+ 'Checks that any id value referenced by an ARIA ID-reference attribute (aria-labelledby, aria-describedby, aria-owns, aria-controls, aria-activedescendant, aria-flowto, aria-errormessage, aria-details) is unique in the document.',
33
34
  i18n: {
34
35
  titleKey: 'duplicateIdAria_title',
35
36
  descriptionKey: 'duplicateIdAria_description'
@@ -38,7 +39,13 @@ const meta = {
38
39
  tags: ['wcag2a', 'wcag412', 'aria', 'structure', 'atomic', 'automatic'],
39
40
  wcagSc: ['4.1.2'],
40
41
  normativeMappings: [
41
- { standard: 'WCAG', version: '2.2', requirement: '4.1.2', title: 'Name, Role, Value', conformanceLevel: 'A' }
42
+ {
43
+ standard: 'WCAG',
44
+ version: '2.2',
45
+ requirement: '4.1.2',
46
+ title: 'Name, Role, Value',
47
+ conformanceLevel: 'A'
48
+ }
42
49
  ],
43
50
  defaultSeverity: 'serious',
44
51
  category: 'robust',
@@ -95,7 +102,9 @@ function runInPage(ctx) {
95
102
 
96
103
  for (const el of els) {
97
104
  const stableSelector = helpers.buildSelector ? helpers.buildSelector(el) : 'html';
98
- const html = helpers.getOuterHtmlSnippet ? helpers.getOuterHtmlSnippet(el) : (el.outerHTML || '');
105
+ const html = helpers.getOuterHtmlSnippet
106
+ ? helpers.getOuterHtmlSnippet(el)
107
+ : el.outerHTML || '';
99
108
 
100
109
  occurrences.push({
101
110
  selector: stableSelector,
@@ -108,7 +117,11 @@ function runInPage(ctx) {
108
117
  params: { id: refId, duplicateCount: String(els.length) }
109
118
  },
110
119
  data: {
111
- details: { reasonCode: 'DUPLICATE_ID_ARIA_REFERENCED', id: refId, duplicateCount: els.length }
120
+ details: {
121
+ reasonCode: 'DUPLICATE_ID_ARIA_REFERENCED',
122
+ id: refId,
123
+ duplicateCount: els.length
124
+ }
112
125
  }
113
126
  });
114
127
  }
@@ -118,9 +131,14 @@ function runInPage(ctx) {
118
131
  return { ruleId: rule.ruleId, outcome: 'notApplicable', severity: 'minor', occurrences: [] };
119
132
  }
120
133
  if (occurrences.length) {
121
- return { ruleId: rule.ruleId, outcome: 'fail', severity: rule.defaultSeverity || 'serious', occurrences };
134
+ return {
135
+ ruleId: rule.ruleId,
136
+ outcome: 'fail',
137
+ severity: rule.defaultSeverity || 'serious',
138
+ occurrences
139
+ };
122
140
  }
123
141
  return { ruleId: rule.ruleId, outcome: 'pass', severity: 'minor', occurrences: [] };
124
142
  }
125
143
 
126
- module.exports = { id, meta, runInPage };
144
+ module.exports = { id, meta, runInPage };