@surea11y/core 1.1.2 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (160) hide show
  1. package/CHANGELOG.md +48 -4
  2. package/LICENSE +373 -21
  3. package/README.md +70 -1
  4. package/bin/core.js +240 -11
  5. package/docs/API_STABILITY.md +61 -0
  6. package/docs/BASELINE.md +66 -0
  7. package/docs/BINDING_AUTHORS_GUIDE.md +9 -9
  8. package/docs/CI_INTEGRATIONS.md +103 -0
  9. package/docs/CLI.md +80 -1
  10. package/docs/ENGINE_OPTIONS.md +4 -0
  11. package/docs/INTEGRATION.md +19 -1
  12. package/docs/OUTPUT_SCHEMA.md +2 -2
  13. package/docs/REPORT.md +33 -0
  14. package/docs/RULE_AUTHORING.md +31 -0
  15. package/docs/RULE_CATALOG.md +1 -1
  16. package/docs/SARIF.md +59 -0
  17. package/package.json +15 -4
  18. package/src/baseline.js +0 -0
  19. package/src/catalogs/composites.wcag.js +414 -450
  20. package/src/checks/automatic/area-alt-present.js +59 -25
  21. package/src/checks/automatic/aria-allowed-attr.js +193 -33
  22. package/src/checks/automatic/aria-allowed-role.js +21 -7
  23. package/src/checks/automatic/aria-braille-equivalent.js +32 -10
  24. package/src/checks/automatic/aria-conditional-attr.js +24 -7
  25. package/src/checks/automatic/aria-deprecated-role.js +22 -8
  26. package/src/checks/automatic/aria-hidden-body.js +50 -19
  27. package/src/checks/automatic/aria-hidden-focus.js +408 -53
  28. package/src/checks/automatic/aria-prohibited-attr.js +296 -22
  29. package/src/checks/automatic/aria-prohibited-children.js +125 -29
  30. package/src/checks/automatic/aria-required-attr.js +23 -8
  31. package/src/checks/automatic/aria-required-children.js +37 -14
  32. package/src/checks/automatic/aria-required-parent.js +48 -14
  33. package/src/checks/automatic/aria-role-name-present.js +47 -21
  34. package/src/checks/automatic/aria-roles-valid.js +22 -12
  35. package/src/checks/automatic/aria-valid-attr-value.js +28 -7
  36. package/src/checks/automatic/aria-valid-attr.js +17 -5
  37. package/src/checks/automatic/autocomplete-valid.js +74 -16
  38. package/src/checks/automatic/avoid-inline-spacing.js +20 -6
  39. package/src/checks/automatic/binary-control-name-present.js +60 -50
  40. package/src/checks/automatic/button-name-present.js +48 -18
  41. package/src/checks/automatic/bypass-blocks-present.js +50 -25
  42. package/src/checks/automatic/canvas-text-alternative-present.js +57 -26
  43. package/src/checks/automatic/combobox-name-present.js +38 -45
  44. package/src/checks/automatic/contrast-computable.js +361 -341
  45. package/src/checks/automatic/contrast-enhanced.js +487 -466
  46. package/src/checks/automatic/contrast-minimum.js +486 -465
  47. package/src/checks/automatic/css-orientation-lock.js +39 -9
  48. package/src/checks/automatic/definition-list-children-valid.js +40 -19
  49. package/src/checks/automatic/deprecated-elements-not-used.js +21 -7
  50. package/src/checks/automatic/dialog-name-present.js +37 -75
  51. package/src/checks/automatic/dlitem-parent-valid.js +23 -8
  52. package/src/checks/automatic/duplicate-id-aria.js +24 -6
  53. package/src/checks/automatic/embed-text-alternative-present.js +86 -35
  54. package/src/checks/automatic/form-control-programmatic-label-present.js +79 -196
  55. package/src/checks/automatic/form-control-single-label.js +47 -10
  56. package/src/checks/automatic/html-xml-lang-mismatch.js +43 -19
  57. package/src/checks/automatic/iframe-focusable-content.js +26 -11
  58. package/src/checks/automatic/iframe-name-present.js +31 -9
  59. package/src/checks/automatic/iframe-title-unique.js +29 -8
  60. package/src/checks/automatic/img-alt-present.js +47 -43
  61. package/src/checks/automatic/input-image-alt-present.js +143 -112
  62. package/src/checks/automatic/label-in-name.js +50 -22
  63. package/src/checks/automatic/language-page-present.js +117 -109
  64. package/src/checks/automatic/link-in-text-block.js +59 -19
  65. package/src/checks/automatic/link-name-present.js +45 -14
  66. package/src/checks/automatic/list-children-valid.js +26 -9
  67. package/src/checks/automatic/listbox-name-present.js +39 -19
  68. package/src/checks/automatic/listitem-parent-valid.js +18 -6
  69. package/src/checks/automatic/menuitem-name-present.js +39 -61
  70. package/src/checks/automatic/meta-refresh-no-exceptions.js +37 -8
  71. package/src/checks/automatic/meta-refresh-timing-absent.js +28 -6
  72. package/src/checks/automatic/meta-viewport-zoom-enabled.js +32 -7
  73. package/src/checks/automatic/meter-name-present.js +36 -33
  74. package/src/checks/automatic/nested-interactive-controls-absent.js +31 -10
  75. package/src/checks/automatic/object-text-alternative-present.js +91 -39
  76. package/src/checks/automatic/option-name-present.js +38 -21
  77. package/src/checks/automatic/page-title-present.js +26 -7
  78. package/src/checks/automatic/progressbar-name-present.js +41 -34
  79. package/src/checks/automatic/role-img-alt-present.js +209 -157
  80. package/src/checks/automatic/searchbox-name-present.js +39 -19
  81. package/src/checks/automatic/server-side-image-map-absent.js +23 -8
  82. package/src/checks/automatic/slider-name-present.js +40 -47
  83. package/src/checks/automatic/spinbutton-name-present.js +39 -19
  84. package/src/checks/automatic/summary-name-present.js +37 -17
  85. package/src/checks/automatic/svg-image-text-alternative-present.js +114 -47
  86. package/src/checks/automatic/svg-text-alternative-present.js +246 -226
  87. package/src/checks/automatic/tab-name-present.js +37 -60
  88. package/src/checks/automatic/table-headers-attr-valid.js +24 -8
  89. package/src/checks/automatic/table-th-has-data-cells.js +22 -8
  90. package/src/checks/automatic/target-size-minimum.js +118 -48
  91. package/src/checks/automatic/td-has-header.js +29 -11
  92. package/src/checks/automatic/textbox-name-present.js +39 -19
  93. package/src/checks/automatic/tooltip-name-present.js +37 -18
  94. package/src/checks/automatic/treeitem-name-present.js +38 -21
  95. package/src/checks/automatic/valid-lang.js +20 -6
  96. package/src/checks/automatic/video-poster-text-alternative-present.js +79 -36
  97. package/src/checks/manual/accesskeys-manual.js +14 -5
  98. package/src/checks/manual/area-alt-decorative-manual.js +192 -193
  99. package/src/checks/manual/area-alt-quality-manual.js +182 -141
  100. package/src/checks/manual/aria-checked-state-mismatch-manual.js +34 -11
  101. package/src/checks/manual/aria-text-manual.js +14 -6
  102. package/src/checks/manual/canvas-text-alternative-quality-manual.js +149 -114
  103. package/src/checks/manual/css-hidden-focus.js +196 -165
  104. package/src/checks/manual/embed-text-alternative-quality-manual.js +171 -160
  105. package/src/checks/manual/empty-heading-manual.js +24 -12
  106. package/src/checks/manual/empty-table-header-manual.js +21 -14
  107. package/src/checks/manual/focus-order-semantics-manual.js +45 -10
  108. package/src/checks/manual/form-control-programmatic-label-quality-manual.js +207 -246
  109. package/src/checks/manual/heading-order-manual.js +22 -12
  110. package/src/checks/manual/identical-links-same-purpose-manual.js +34 -12
  111. package/src/checks/manual/image-redundant-alt-manual.js +17 -7
  112. package/src/checks/manual/img-alt-decorative-manual.js +131 -96
  113. package/src/checks/manual/img-alt-quality-manual.js +176 -127
  114. package/src/checks/manual/input-image-alt-decorative-manual.js +125 -92
  115. package/src/checks/manual/input-image-alt-quality-manual.js +125 -92
  116. package/src/checks/manual/label-title-only-manual.js +14 -5
  117. package/src/checks/manual/landmark-banner-is-top-level-manual.js +95 -29
  118. package/src/checks/manual/landmark-contentinfo-is-top-level-manual.js +82 -29
  119. package/src/checks/manual/landmark-main-is-top-level-manual.js +64 -23
  120. package/src/checks/manual/landmark-no-duplicate-banner-manual.js +54 -23
  121. package/src/checks/manual/landmark-no-duplicate-contentinfo-manual.js +54 -23
  122. package/src/checks/manual/landmark-no-duplicate-main-manual.js +17 -8
  123. package/src/checks/manual/landmark-one-main-manual.js +35 -21
  124. package/src/checks/manual/landmark-unique-manual.js +73 -45
  125. package/src/checks/manual/link-name-quality-manual.js +43 -12
  126. package/src/checks/manual/media-transcript-present-manual.js +35 -22
  127. package/src/checks/manual/meta-viewport-large-manual.js +25 -6
  128. package/src/checks/manual/mouse-only-event-handlers-manual.js +38 -11
  129. package/src/checks/manual/no-autoplay-audio-manual.js +20 -6
  130. package/src/checks/manual/object-text-alternative-quality-manual.js +175 -154
  131. package/src/checks/manual/p-as-heading-manual.js +22 -7
  132. package/src/checks/manual/page-has-heading-one-manual.js +40 -23
  133. package/src/checks/manual/page-title-patterns-manual.js +87 -51
  134. package/src/checks/manual/presentation-role-conflict-manual.js +59 -19
  135. package/src/checks/manual/region-manual.js +275 -62
  136. package/src/checks/manual/scope-attr-valid-manual.js +11 -9
  137. package/src/checks/manual/scrollable-region-focusable-manual.js +37 -11
  138. package/src/checks/manual/skip-link-manual.js +35 -12
  139. package/src/checks/manual/svg-text-alternative-quality-manual.js +206 -165
  140. package/src/checks/manual/tabindex-manual.js +11 -9
  141. package/src/checks/manual/table-duplicate-name-manual.js +17 -7
  142. package/src/checks/manual/table-fake-caption-manual.js +24 -7
  143. package/src/checks/manual/video-caption-manual.js +15 -4
  144. package/src/checks/manual-review.js +56 -12
  145. package/src/core/aria-helpers.js +1128 -823
  146. package/src/core/contrast-helpers.js +1217 -1062
  147. package/src/core/dom-helpers.js +4176 -3886
  148. package/src/core/dom-runner.js +720 -596
  149. package/src/core/frame-messaging.js +189 -138
  150. package/src/core/frame-scan.js +94 -82
  151. package/src/core/rollup-composites.js +94 -102
  152. package/src/core/rule-meta.js +71 -35
  153. package/src/core.js +37939 -29121
  154. package/src/i18n/en.js +1194 -887
  155. package/src/i18n/fr.js +1136 -793
  156. package/src/policy/contracts.js +13 -13
  157. package/src/policy/resolvePolicy.js +48 -44
  158. package/src/report.js +502 -0
  159. package/src/sarif.js +175 -0
  160. package/surea11y.browser.js +36042 -0
@@ -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',
@@ -73,13 +79,23 @@ const meta = {
73
79
  coverage: { facetsBySc: { '1.3.4': ['css-orientation-lock'] } }
74
80
  };
75
81
 
82
+ // This check is inherently whole-document (does the PAGE have this
83
+ // property?), not evaluable per-subtree -- notApplicable when contextSelector
84
+ // scoped this run narrower than the whole document, or when
85
+ // engineOptions.fragment:true was set (see helpers.isWholeDocumentScope).
86
+ function applicability(ctx) {
87
+ return ctx.helpers.isWholeDocumentScope ? ctx.helpers.isWholeDocumentScope() : true;
88
+ }
89
+
76
90
  function runInPage(ctx) {
77
91
  const { document, helpers, rule } = ctx;
78
92
 
79
93
  const CSS_MEDIA_RULE = 4;
80
94
  const CSS_STYLE_RULE = 1;
81
95
 
82
- function trim(v) { return (v == null ? '' : String(v)).trim(); }
96
+ function trim(v) {
97
+ return (v == null ? '' : String(v)).trim();
98
+ }
83
99
 
84
100
  // Converts a rotate()/rotateZ() angle argument (with unit) to degrees.
85
101
  // Matches a widely-used reference engine's own getAngleInDegrees: only deg/grad/rad/turn are
@@ -118,9 +134,13 @@ function runInPage(ctx) {
118
134
  // near 90 or 270 degrees (mod 90, once the 0/180 case is excluded) is.
119
135
  function isLockingRotation(styleDecl) {
120
136
  if (!styleDecl) return false;
121
- 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') : '');
122
140
  // The standalone CSS `rotate` property (distinct from `transform: rotate()`) -- that reference engine checks this too.
123
- const rotatePropVal = trim(styleDecl.getPropertyValue ? styleDecl.getPropertyValue('rotate') : '');
141
+ const rotatePropVal = trim(
142
+ styleDecl.getPropertyValue ? styleDecl.getPropertyValue('rotate') : ''
143
+ );
124
144
 
125
145
  let degrees = rotateDegreesFromTransform(transformVal);
126
146
  if (!degrees && rotatePropVal) degrees = angleToDegrees(rotatePropVal);
@@ -181,9 +201,10 @@ function runInPage(ctx) {
181
201
  return { ruleId: rule.ruleId, outcome: 'pass', severity: 'minor', occurrences: [] };
182
202
  }
183
203
 
184
- const target = document.documentElement || (document.body || null);
204
+ const target = document.documentElement || document.body || null;
185
205
  const stableSelector = helpers.buildSelector && target ? helpers.buildSelector(target) : 'html';
186
- const html = helpers.getOuterHtmlSnippet && target ? helpers.getOuterHtmlSnippet(target) : '<html>';
206
+ const html =
207
+ helpers.getOuterHtmlSnippet && target ? helpers.getOuterHtmlSnippet(target) : '<html>';
187
208
 
188
209
  const occurrences = findings.map((f) => ({
189
210
  selector: stableSelector,
@@ -196,11 +217,20 @@ function runInPage(ctx) {
196
217
  params: { mediaText: f.mediaText, selectorText: f.selectorText }
197
218
  },
198
219
  data: {
199
- 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
+ }
200
225
  }
201
226
  }));
202
227
 
203
- 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
+ };
204
234
  }
205
235
 
206
- module.exports = { id, meta, runInPage };
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 };