@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
@@ -18,138 +18,169 @@
18
18
  const id = 'input-image-alt-present';
19
19
 
20
20
  const meta = {
21
- title: '<input type="image"> must have an alt attribute',
22
- description:
23
- 'Checks that <input type="image"> elements provide an alt attribute to support a text alternative mechanism.',
24
- i18n: {
25
- titleKey: 'inputImage_altPresent_title',
26
- descriptionKey: 'inputImage_altPresent_description'
27
- },
28
- helpUrl: null,
29
- tags: ['wcag2a', 'wcag111', 'nontext', 'images', 'atomic', 'automatic'],
30
- wcagSc: ['1.1.1'],
31
- normativeMappings: [
32
- { standard: 'WCAG', version: '2.2', requirement: '1.1.1', title: 'Non-text Content', conformanceLevel: 'A' }
33
- ],
34
- defaultSeverity: 'serious',
35
- category: 'perceivable',
36
- type: 'automatic',
37
- defaultConfidence: 'high',
38
- coverage: {
39
- facetsBySc: {
40
- '1.1.1': ['input-image-alt-attr-present']
41
- }
21
+ title: '<input type="image"> must have an alt attribute',
22
+ description:
23
+ 'Checks that <input type="image"> elements provide an alt attribute to support a text alternative mechanism.',
24
+ i18n: {
25
+ titleKey: 'inputImage_altPresent_title',
26
+ descriptionKey: 'inputImage_altPresent_description'
27
+ },
28
+ helpUrl: null,
29
+ tags: ['wcag2a', 'wcag111', 'nontext', 'images', 'atomic', 'automatic'],
30
+ wcagSc: ['1.1.1'],
31
+ normativeMappings: [
32
+ {
33
+ standard: 'WCAG',
34
+ version: '2.2',
35
+ requirement: '1.1.1',
36
+ title: 'Non-text Content',
37
+ conformanceLevel: 'A'
38
+ }
39
+ ],
40
+ defaultSeverity: 'serious',
41
+ category: 'perceivable',
42
+ type: 'automatic',
43
+ defaultConfidence: 'high',
44
+ coverage: {
45
+ facetsBySc: {
46
+ '1.1.1': ['input-image-alt-attr-present']
42
47
  }
48
+ }
43
49
  };
44
50
 
45
51
  function runInPage(ctx) {
46
- const { document, root, helpers, rule } = ctx;
47
- const safeRoot = root || document;
48
-
49
- const queryAllSmart = helpers && typeof helpers.queryAllSmart === 'function' ? helpers.queryAllSmart : null;
50
- const queryAll = helpers && typeof helpers.queryAll === 'function'
51
- ? helpers.queryAll
52
- : (sel) => {
53
- try { return safeRoot && safeRoot.querySelectorAll ? Array.from(safeRoot.querySelectorAll(sel)) : []; }
54
- catch { return []; }
52
+ const { document, root, helpers, rule } = ctx;
53
+ const safeRoot = root || document;
54
+
55
+ const queryAllSmart =
56
+ helpers && typeof helpers.queryAllSmart === 'function' ? helpers.queryAllSmart : null;
57
+ const queryAll =
58
+ helpers && typeof helpers.queryAll === 'function'
59
+ ? helpers.queryAll
60
+ : (sel) => {
61
+ try {
62
+ return safeRoot && safeRoot.querySelectorAll
63
+ ? Array.from(safeRoot.querySelectorAll(sel))
64
+ : [];
65
+ } catch {
66
+ return [];
67
+ }
55
68
  };
56
69
 
57
- const getEligibilityInfo = helpers && typeof helpers.getEligibilityInfo === 'function'
58
- ? helpers.getEligibilityInfo
59
- : null;
60
-
61
- const isAccTreeEligible = helpers && typeof helpers.isAccTreeEligible === 'function'
62
- ? helpers.isAccTreeEligible
63
- : null;
70
+ const getEligibilityInfo =
71
+ helpers && typeof helpers.getEligibilityInfo === 'function' ? helpers.getEligibilityInfo : null;
64
72
 
65
- const getAriaNameInfo = helpers && typeof helpers.getAriaNameInfo === 'function'
66
- ? helpers.getAriaNameInfo
67
- : null;
73
+ const isAccTreeEligible =
74
+ helpers && typeof helpers.isAccTreeEligible === 'function' ? helpers.isAccTreeEligible : null;
68
75
 
69
- const inputs = (() => {
70
- try { return Array.from((queryAllSmart ? queryAllSmart('input[type="image"]') : queryAll('input[type="image"]')) || []); }
71
- catch { return queryAll('input[type="image"]'); }
72
- })();
76
+ const getAriaNameInfo =
77
+ helpers && typeof helpers.getAriaNameInfo === 'function' ? helpers.getAriaNameInfo : null;
73
78
 
74
- if (!inputs.length) {
75
- return { ruleId: rule.ruleId, outcome: 'notApplicable', severity: 'minor', occurrences: [] };
79
+ const inputs = (() => {
80
+ try {
81
+ return Array.from(
82
+ (queryAllSmart ? queryAllSmart('input[type="image"]') : queryAll('input[type="image"]')) ||
83
+ []
84
+ );
85
+ } catch {
86
+ return queryAll('input[type="image"]');
76
87
  }
88
+ })();
77
89
 
78
- const occurrences = [];
79
- let applicableCount = 0;
90
+ if (!inputs.length) {
91
+ return { ruleId: rule.ruleId, outcome: 'notApplicable', severity: 'minor', occurrences: [] };
92
+ }
80
93
 
81
- for (const el of inputs) {
82
- if (!el || !el.getAttribute) continue;
94
+ const occurrences = [];
95
+ let applicableCount = 0;
83
96
 
84
- // Applicability: eligible in the acc tree (with helper exceptions).
85
- if (isAccTreeEligible) {
86
- const elig = (() => {
87
- try { return isAccTreeEligible(el, ctx); } catch { return { eligible: true, reasons: [] }; }
88
- })();
89
- if (elig && elig.eligible === false) continue;
90
- }
97
+ for (const el of inputs) {
98
+ if (!el || !el.getAttribute) continue;
91
99
 
92
- applicableCount += 1;
93
-
94
- const hasAlt = el.getAttribute('alt') !== null;
95
- if (hasAlt) continue;
96
-
97
- // aria-label / aria-labelledby is also a valid, standards-recognized
98
- // text-alternative mechanism for <input type="image"> (HTML-AAM
99
- // accessible name computation includes ARIA naming before falling
100
- // back to alt).
101
- if (getAriaNameInfo) {
102
- let ariaName = null;
103
- try {
104
- ariaName = getAriaNameInfo(el, ctx);
105
- } catch {
106
- ariaName = null;
107
- }
108
- if (ariaName && ariaName.present) continue;
109
- }
110
-
111
- // A non-empty title attribute is HTML-AAM's own next fallback naming
112
- // source once alt is entirely absent -- also accepted by a widely-used
113
- // reference engine's equivalent input-image-alt rule (non-empty-title, same "any" list
114
- // as non-empty-alt/aria-label/aria-labelledby). See img-alt-present's
115
- // sibling fix (2026-07-23, AliExpress's title-only logo <img>) for
116
- // the real page this was found via -- same gap, same fix, different
117
- // element.
118
- const titleRaw = (() => { try { return el.getAttribute('title'); } catch { return null; } })();
119
- if (titleRaw !== null && String(titleRaw).trim()) continue;
120
-
121
- const eligInfo = getEligibilityInfo ? getEligibilityInfo(el, ctx, { targetSet: 'acc' }) : null;
122
-
123
- const baseOccurrence = {
124
- summary: 'Missing alt attribute on <input type="image">.',
125
- hint: 'Add an alt attribute (use alt="" only when a separate accessible name is provided).',
126
- i18n: {
127
- summaryKey: 'inputImage_altPresent_summary_fail',
128
- hintKey: 'inputImage_altPresent_hint_fail',
129
- params: { element: 'input[type=image]' }
130
- },
131
- data: {
132
- visibilityFilter: eligInfo || { targetSet: 'acc', accEligible: null, reasons: [] }
133
- }
134
- };
135
-
136
- if (helpers && typeof helpers.reportOccurrence === 'function') {
137
- occurrences.push(helpers.reportOccurrence(el, baseOccurrence));
138
- } else {
139
- // Never compute selector/snippet in the rule.
140
- occurrences.push({ selector: '', html: '', ...baseOccurrence });
100
+ // Applicability: eligible in the acc tree (with helper exceptions).
101
+ if (isAccTreeEligible) {
102
+ const elig = (() => {
103
+ try {
104
+ return isAccTreeEligible(el, ctx);
105
+ } catch {
106
+ return { eligible: true, reasons: [] };
141
107
  }
108
+ })();
109
+ if (elig && elig.eligible === false) continue;
142
110
  }
143
111
 
144
- if (applicableCount === 0) {
145
- return { ruleId: rule.ruleId, outcome: 'notApplicable', severity: 'minor', occurrences: [] };
112
+ applicableCount += 1;
113
+
114
+ const hasAlt = el.getAttribute('alt') !== null;
115
+ if (hasAlt) continue;
116
+
117
+ // aria-label / aria-labelledby is also a valid, standards-recognized
118
+ // text-alternative mechanism for <input type="image"> (HTML-AAM
119
+ // accessible name computation includes ARIA naming before falling
120
+ // back to alt).
121
+ if (getAriaNameInfo) {
122
+ let ariaName;
123
+ try {
124
+ ariaName = getAriaNameInfo(el, ctx);
125
+ } catch {
126
+ ariaName = null;
127
+ }
128
+ if (ariaName && ariaName.present) continue;
146
129
  }
147
130
 
148
- if (!occurrences.length) {
149
- return { ruleId: rule.ruleId, outcome: 'pass', severity: 'minor', occurrences: [] };
131
+ // A non-empty title attribute is HTML-AAM's own next fallback naming
132
+ // source once alt is entirely absent -- also accepted by a widely-used
133
+ // reference engine's equivalent input-image-alt rule (non-empty-title, same "any" list
134
+ // as non-empty-alt/aria-label/aria-labelledby). See img-alt-present's
135
+ // sibling fix (2026-07-23, AliExpress's title-only logo <img>) for
136
+ // the real page this was found via -- same gap, same fix, different
137
+ // element.
138
+ const titleRaw = (() => {
139
+ try {
140
+ return el.getAttribute('title');
141
+ } catch {
142
+ return null;
143
+ }
144
+ })();
145
+ if (titleRaw !== null && String(titleRaw).trim()) continue;
146
+
147
+ const eligInfo = getEligibilityInfo ? getEligibilityInfo(el, ctx, { targetSet: 'acc' }) : null;
148
+
149
+ const baseOccurrence = {
150
+ summary: 'Missing alt attribute on <input type="image">.',
151
+ hint: 'Add an alt attribute (use alt="" only when a separate accessible name is provided).',
152
+ i18n: {
153
+ summaryKey: 'inputImage_altPresent_summary_fail',
154
+ hintKey: 'inputImage_altPresent_hint_fail',
155
+ params: { element: 'input[type=image]' }
156
+ },
157
+ data: {
158
+ visibilityFilter: eligInfo || { targetSet: 'acc', accEligible: null, reasons: [] }
159
+ }
160
+ };
161
+
162
+ if (helpers && typeof helpers.reportOccurrence === 'function') {
163
+ occurrences.push(helpers.reportOccurrence(el, baseOccurrence));
164
+ } else {
165
+ // Never compute selector/snippet in the rule.
166
+ occurrences.push({ selector: '', html: '', ...baseOccurrence });
150
167
  }
151
-
152
- return { ruleId: rule.ruleId, outcome: 'fail', severity: rule.defaultSeverity || 'minor', occurrences };
168
+ }
169
+
170
+ if (applicableCount === 0) {
171
+ return { ruleId: rule.ruleId, outcome: 'notApplicable', severity: 'minor', occurrences: [] };
172
+ }
173
+
174
+ if (!occurrences.length) {
175
+ return { ruleId: rule.ruleId, outcome: 'pass', severity: 'minor', occurrences: [] };
176
+ }
177
+
178
+ return {
179
+ ruleId: rule.ruleId,
180
+ outcome: 'fail',
181
+ severity: rule.defaultSeverity || 'minor',
182
+ occurrences
183
+ };
153
184
  }
154
185
 
155
186
  module.exports = { id, meta, runInPage };
@@ -5,7 +5,7 @@ const id = 'label-in-name';
5
5
  const meta = {
6
6
  title: 'Label in Name: accessible name contains visible text',
7
7
  description:
8
- 'Checks that when a control has a visible text label, the accessible name (from aria-label/aria-labelledby) contains that visible label text (WCAG 2.5.3).',
8
+ 'Checks that when a control has a visible text label, the accessible name (from aria-label/aria-labelledby) contains that visible label text (WCAG 2.5.3).',
9
9
  i18n: {
10
10
  titleKey: 'labelInName_title',
11
11
  descriptionKey: 'labelInName_description'
@@ -37,20 +37,27 @@ function runInPage(ctx) {
37
37
 
38
38
  // Applicability: focus/activation controls with explicit ARIA naming.
39
39
  // NOTE: aria-hidden is intentionally NOT excluded here; it does not affect visual rendering.
40
- const selector = ':is(button, a[href], summary, input:not([type="hidden"]), textarea, select, [role="button"], [role="link"], [role="checkbox"], [role="radio"], [role="switch"], [role="searchbox"], [role="tab"], [role="menuitem"], [role="menuitemcheckbox"], [role="menuitemradio"], [role="option"], [role="treeitem"], [role="gridcell"]):not([hidden]):not([disabled]):not([aria-disabled="true"]):is([aria-label], [aria-labelledby])';
40
+ const selector =
41
+ ':is(button, a[href], summary, input:not([type="hidden"]), textarea, select, [role="button"], [role="link"], [role="checkbox"], [role="radio"], [role="switch"], [role="searchbox"], [role="tab"], [role="menuitem"], [role="menuitemcheckbox"], [role="menuitemradio"], [role="option"], [role="treeitem"], [role="gridcell"]):not([hidden]):not([disabled]):not([aria-disabled="true"]):is([aria-label], [aria-labelledby])';
41
42
 
42
- const nodes = helpers.queryAllSmart ? helpers.queryAllSmart(selector) : helpers.queryAll(selector);
43
+ const nodes = helpers.queryAllSmart
44
+ ? helpers.queryAllSmart(selector)
45
+ : helpers.queryAll(selector);
43
46
 
44
47
  function norm(s) {
45
- const v = (s == null ? '' : String(s));
48
+ const v = s == null ? '' : String(s);
46
49
  // deterministic normalization: trim + collapse whitespace + case-fold
47
50
  return v.replace(/\s+/g, ' ').trim().toLowerCase();
48
51
  }
49
52
 
50
53
  function getElementDescriptor(el) {
51
- const tag = (el && el.tagName ? String(el.tagName).toLowerCase() : 'element');
52
- let role = '';
53
- try { role = el && el.getAttribute ? (el.getAttribute('role') || '') : ''; } catch { role = ''; }
54
+ const tag = el && el.tagName ? String(el.tagName).toLowerCase() : 'element';
55
+ let role;
56
+ try {
57
+ role = el && el.getAttribute ? el.getAttribute('role') || '' : '';
58
+ } catch {
59
+ role = '';
60
+ }
54
61
  const r = String(role || '').trim();
55
62
  return r ? `${tag}[role="${r}"]` : tag;
56
63
  }
@@ -62,14 +69,23 @@ function runInPage(ctx) {
62
69
  }
63
70
 
64
71
  function isNonRenderedTag(el) {
65
- const tn = (el && el.tagName ? String(el.tagName).toLowerCase() : '');
66
- return tn === 'script' || tn === 'style' || tn === 'template' || tn === 'noscript' || tn === 'meta' || tn === 'link';
72
+ const tn = el && el.tagName ? String(el.tagName).toLowerCase() : '';
73
+ return (
74
+ tn === 'script' ||
75
+ tn === 'style' ||
76
+ tn === 'template' ||
77
+ tn === 'noscript' ||
78
+ tn === 'meta' ||
79
+ tn === 'link'
80
+ );
67
81
  }
68
82
 
69
83
  function isDomVisible(el) {
70
84
  if (!el) return false;
71
- if (helpers.isDomVisibleEligible) return !!helpers.isDomVisibleEligible(el, ctx, { targetSet: 'dom' }).eligible;
72
- if (helpers.getEligibilityInfo) return !!helpers.getEligibilityInfo(el, ctx, { targetSet: 'dom' }).eligible;
85
+ if (helpers.isDomVisibleEligible)
86
+ return !!helpers.isDomVisibleEligible(el, ctx, { targetSet: 'dom' }).eligible;
87
+ if (helpers.getEligibilityInfo)
88
+ return !!helpers.getEligibilityInfo(el, ctx, { targetSet: 'dom' }).eligible;
73
89
  return true;
74
90
  }
75
91
 
@@ -90,7 +106,8 @@ function runInPage(ctx) {
90
106
  // aria-hidden icon rendering literally as "format_color_fill").
91
107
  function isAccEligible(el) {
92
108
  if (!el) return false;
93
- const fn = helpers && typeof helpers.isAccTreeEligible === 'function' ? helpers.isAccTreeEligible : null;
109
+ const fn =
110
+ helpers && typeof helpers.isAccTreeEligible === 'function' ? helpers.isAccTreeEligible : null;
94
111
  if (!fn) return isDomVisible(el);
95
112
  try {
96
113
  const r = fn(el, ctx);
@@ -118,7 +135,7 @@ function runInPage(ctx) {
118
135
  if (!isDomVisible(container)) return '';
119
136
 
120
137
  // TreeWalker is deterministic in document order.
121
- let walker = null;
138
+ let walker;
122
139
  try {
123
140
  walker = document.createTreeWalker(container, SHOW_TEXT, null);
124
141
  } catch {
@@ -134,7 +151,7 @@ function runInPage(ctx) {
134
151
  }
135
152
 
136
153
  const parts = [];
137
- let n = null;
154
+ let n;
138
155
  // eslint-disable-next-line no-cond-assign
139
156
  while ((n = walker.nextNode())) {
140
157
  try {
@@ -183,7 +200,7 @@ function runInPage(ctx) {
183
200
  // contextSelector support), not a single element with its own
184
201
  // .querySelector to call directly.
185
202
  try {
186
- const idAttribute = control && control.getAttribute ? (control.getAttribute('id') || '') : '';
203
+ const idAttribute = control && control.getAttribute ? control.getAttribute('id') || '' : '';
187
204
  const key = String(idAttribute || '').trim();
188
205
  if (key && document && document.querySelector) {
189
206
  const l = document.querySelector('label[for="' + CSS.escape(key) + '"]');
@@ -215,10 +232,10 @@ function runInPage(ctx) {
215
232
  // 1) <label> association for form controls
216
233
  // 2) visible text inside the element
217
234
  // 3) visible text from aria-labelledby referenced elements
218
- let text = '';
235
+ let text;
219
236
  let source = 'none';
220
237
 
221
- const tn = (el && el.tagName ? String(el.tagName).toLowerCase() : '');
238
+ const tn = el && el.tagName ? String(el.tagName).toLowerCase() : '';
222
239
 
223
240
  // 1) Label association (native form controls)
224
241
  const isFormControl = tn === 'input' || tn === 'select' || tn === 'textarea';
@@ -244,7 +261,7 @@ function runInPage(ctx) {
244
261
  if (idrefs && helpers.resolveIdRefs) {
245
262
  const r = helpers.resolveIdRefs(idrefs, ctx, { maxRefs: 8 });
246
263
  const parts = [];
247
- for (const ref of (r && Array.isArray(r.refs) ? r.refs : [])) {
264
+ for (const ref of r && Array.isArray(r.refs) ? r.refs : []) {
248
265
  if (!ref || !ref.tagName) continue;
249
266
  if (!isDomVisible(ref)) continue;
250
267
  const t = collectVisibleTextUnder(ref);
@@ -275,7 +292,9 @@ function runInPage(ctx) {
275
292
 
276
293
  let acc = { present: false, value: '', mechanism: 'none', flags: [] };
277
294
  try {
278
- acc = helpers.getAccessibleNameInfo ? helpers.getAccessibleNameInfo(el, ctx, { maxRefs: 8 }) : acc;
295
+ acc = helpers.getAccessibleNameInfo
296
+ ? helpers.getAccessibleNameInfo(el, ctx, { maxRefs: 8 })
297
+ : acc;
279
298
  } catch {
280
299
  acc = { present: false, value: '', mechanism: 'none', flags: ['exception'] };
281
300
  }
@@ -286,7 +305,9 @@ function runInPage(ctx) {
286
305
 
287
306
  if (!contains) {
288
307
  const selectorOut = helpers.buildSelector ? helpers.buildSelector(el) : 'html';
289
- const html = helpers.getOuterHtmlSnippet ? helpers.getOuterHtmlSnippet(el) : (el.outerHTML || '');
308
+ const html = helpers.getOuterHtmlSnippet
309
+ ? helpers.getOuterHtmlSnippet(el)
310
+ : el.outerHTML || '';
290
311
 
291
312
  occurrences.push({
292
313
  selector: selectorOut,
@@ -317,8 +338,15 @@ function runInPage(ctx) {
317
338
  }
318
339
  }
319
340
 
320
- if (applicableCount === 0) return { ruleId: rule.ruleId, outcome: 'notApplicable', severity: 'minor', occurrences: [] };
321
- if (occurrences.length) return { ruleId: rule.ruleId, outcome: 'fail', severity: rule.defaultSeverity || 'minor', occurrences };
341
+ if (applicableCount === 0)
342
+ return { ruleId: rule.ruleId, outcome: 'notApplicable', severity: 'minor', occurrences: [] };
343
+ if (occurrences.length)
344
+ return {
345
+ ruleId: rule.ruleId,
346
+ outcome: 'fail',
347
+ severity: rule.defaultSeverity || 'minor',
348
+ occurrences
349
+ };
322
350
  return { ruleId: rule.ruleId, outcome: 'pass', severity: 'minor', occurrences: [] };
323
351
  }
324
352