@surea11y/core 1.3.0 → 1.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (166) hide show
  1. package/CHANGELOG.md +87 -2
  2. package/README.md +109 -35
  3. package/bin/surea11y-core.js +20 -0
  4. package/docs/API_STABILITY.md +26 -0
  5. package/docs/ARIA_DEPRECATION.md +95 -0
  6. package/docs/CI_INTEGRATIONS.md +7 -7
  7. package/docs/ENGINE_OPTIONS.md +1 -1
  8. package/docs/I18N.md +12 -9
  9. package/docs/INTEGRATION.md +1 -1
  10. package/docs/LIMITATIONS.md +1 -1
  11. package/docs/REPORT.md +1 -1
  12. package/docs/RULE_CATALOG.md +6 -6
  13. package/package.json +52 -16
  14. package/src/baseline.js +0 -0
  15. package/src/checks/automatic/area-alt-present.js +4 -6
  16. package/src/checks/automatic/aria-allowed-attr.js +663 -134
  17. package/src/checks/automatic/aria-allowed-role.js +2 -0
  18. package/src/checks/automatic/aria-braille-equivalent.js +2 -0
  19. package/src/checks/automatic/aria-conditional-attr.js +8 -7
  20. package/src/checks/automatic/aria-deprecated-role.js +107 -38
  21. package/src/checks/automatic/aria-hidden-body.js +6 -4
  22. package/src/checks/automatic/aria-hidden-focus.js +12 -13
  23. package/src/checks/automatic/aria-prohibited-attr.js +98 -105
  24. package/src/checks/automatic/aria-prohibited-children.js +56 -87
  25. package/src/checks/automatic/aria-required-attr.js +6 -7
  26. package/src/checks/automatic/aria-required-children.js +7 -10
  27. package/src/checks/automatic/aria-required-parent.js +20 -25
  28. package/src/checks/automatic/aria-role-name-present.js +2 -0
  29. package/src/checks/automatic/aria-roles-valid.js +33 -6
  30. package/src/checks/automatic/aria-valid-attr-value.js +18 -15
  31. package/src/checks/automatic/aria-valid-attr.js +2 -0
  32. package/src/checks/automatic/autocomplete-valid.js +39 -1
  33. package/src/checks/automatic/avoid-inline-spacing.js +181 -20
  34. package/src/checks/automatic/binary-control-name-present.js +13 -3
  35. package/src/checks/automatic/button-name-present.js +54 -21
  36. package/src/checks/automatic/canvas-text-alternative-present.js +17 -8
  37. package/src/checks/automatic/combobox-name-present.js +10 -1
  38. package/src/checks/automatic/contrast-computable.js +2 -0
  39. package/src/checks/automatic/contrast-enhanced.js +2 -0
  40. package/src/checks/automatic/contrast-minimum.js +2 -0
  41. package/src/checks/automatic/css-orientation-lock.js +21 -27
  42. package/src/checks/automatic/definition-list-children-valid.js +6 -6
  43. package/src/checks/automatic/deprecated-elements-not-used.js +4 -2
  44. package/src/checks/automatic/dialog-name-present.js +18 -11
  45. package/src/checks/automatic/dlitem-parent-valid.js +2 -0
  46. package/src/checks/automatic/duplicate-id-aria.js +4 -3
  47. package/src/checks/automatic/embed-text-alternative-present.js +2 -0
  48. package/src/checks/automatic/form-control-programmatic-label-present.js +50 -4
  49. package/src/checks/automatic/form-control-single-label.js +110 -43
  50. package/src/checks/automatic/html-xml-lang-mismatch.js +2 -0
  51. package/src/checks/automatic/iframe-focusable-content.js +246 -18
  52. package/src/checks/automatic/iframe-name-present.js +2 -0
  53. package/src/checks/automatic/iframe-title-unique.js +3 -1
  54. package/src/checks/automatic/img-alt-present.js +23 -18
  55. package/src/checks/automatic/input-image-alt-present.js +101 -52
  56. package/src/checks/automatic/label-in-name.js +97 -27
  57. package/src/checks/automatic/language-page-present.js +7 -1
  58. package/src/checks/automatic/link-in-text-block.js +2 -0
  59. package/src/checks/automatic/link-name-present.js +52 -17
  60. package/src/checks/automatic/list-children-valid.js +14 -24
  61. package/src/checks/automatic/listbox-name-present.js +10 -1
  62. package/src/checks/automatic/listitem-parent-valid.js +30 -7
  63. package/src/checks/automatic/menuitem-name-present.js +10 -1
  64. package/src/checks/automatic/meta-refresh-no-exceptions.js +33 -4
  65. package/src/checks/automatic/meta-refresh-timing-absent.js +32 -4
  66. package/src/checks/automatic/meta-viewport-zoom-enabled.js +39 -15
  67. package/src/checks/automatic/meter-name-present.js +12 -4
  68. package/src/checks/automatic/nested-interactive-controls-absent.js +177 -25
  69. package/src/checks/automatic/object-text-alternative-present.js +16 -7
  70. package/src/checks/automatic/option-name-present.js +10 -1
  71. package/src/checks/automatic/page-title-present.js +2 -0
  72. package/src/checks/automatic/progressbar-name-present.js +16 -11
  73. package/src/checks/automatic/role-img-alt-present.js +4 -4
  74. package/src/checks/automatic/searchbox-name-present.js +10 -1
  75. package/src/checks/automatic/server-side-image-map-absent.js +4 -3
  76. package/src/checks/automatic/slider-name-present.js +13 -2
  77. package/src/checks/automatic/spinbutton-name-present.js +10 -1
  78. package/src/checks/automatic/summary-name-present.js +10 -1
  79. package/src/checks/automatic/svg-image-text-alternative-present.js +2 -0
  80. package/src/checks/automatic/svg-text-alternative-present.js +17 -5
  81. package/src/checks/automatic/tab-name-present.js +10 -1
  82. package/src/checks/automatic/table-headers-attr-valid.js +3 -2
  83. package/src/checks/automatic/table-th-has-data-cells.js +69 -6
  84. package/src/checks/automatic/target-size-minimum.js +5 -0
  85. package/src/checks/automatic/td-has-header.js +24 -1
  86. package/src/checks/automatic/textbox-name-present.js +10 -1
  87. package/src/checks/automatic/tooltip-name-present.js +10 -1
  88. package/src/checks/automatic/treeitem-name-present.js +10 -1
  89. package/src/checks/automatic/valid-lang.js +18 -3
  90. package/src/checks/automatic/video-poster-text-alternative-present.js +2 -0
  91. package/src/checks/manual/accesskeys-manual.js +3 -1
  92. package/src/checks/manual/area-alt-decorative-manual.js +2 -0
  93. package/src/checks/manual/area-alt-quality-manual.js +2 -0
  94. package/src/checks/manual/aria-checked-state-mismatch-manual.js +14 -23
  95. package/src/checks/manual/aria-text-manual.js +6 -5
  96. package/src/checks/manual/bypass-blocks-present-manual.js +279 -0
  97. package/src/checks/manual/canvas-text-alternative-quality-manual.js +2 -0
  98. package/src/checks/manual/css-hidden-focus.js +184 -9
  99. package/src/checks/manual/embed-text-alternative-quality-manual.js +18 -13
  100. package/src/checks/manual/empty-heading-manual.js +17 -17
  101. package/src/checks/manual/empty-table-header-manual.js +52 -25
  102. package/src/checks/manual/focus-order-semantics-manual.js +16 -4
  103. package/src/checks/manual/form-control-programmatic-label-quality-manual.js +2 -0
  104. package/src/checks/manual/heading-order-manual.js +28 -1
  105. package/src/checks/manual/identical-links-same-purpose-manual.js +2 -0
  106. package/src/checks/manual/image-redundant-alt-manual.js +21 -1
  107. package/src/checks/manual/img-alt-decorative-manual.js +2 -0
  108. package/src/checks/manual/img-alt-quality-manual.js +2 -0
  109. package/src/checks/manual/input-image-alt-decorative-manual.js +26 -0
  110. package/src/checks/manual/input-image-alt-quality-manual.js +2 -0
  111. package/src/checks/manual/label-title-only-manual.js +29 -22
  112. package/src/checks/manual/landmark-banner-is-top-level-manual.js +51 -55
  113. package/src/checks/manual/landmark-contentinfo-is-top-level-manual.js +44 -31
  114. package/src/checks/manual/landmark-main-is-top-level-manual.js +41 -24
  115. package/src/checks/manual/landmark-no-duplicate-banner-manual.js +16 -23
  116. package/src/checks/manual/landmark-no-duplicate-contentinfo-manual.js +14 -21
  117. package/src/checks/manual/landmark-no-duplicate-main-manual.js +10 -13
  118. package/src/checks/manual/landmark-one-main-manual.js +12 -23
  119. package/src/checks/manual/landmark-unique-manual.js +37 -52
  120. package/src/checks/manual/link-name-quality-manual.js +2 -0
  121. package/src/checks/manual/media-transcript-present-manual.js +2 -0
  122. package/src/checks/manual/meta-viewport-large-manual.js +3 -1
  123. package/src/checks/manual/mouse-only-event-handlers-manual.js +2 -0
  124. package/src/checks/manual/no-autoplay-audio-manual.js +2 -0
  125. package/src/checks/manual/object-text-alternative-quality-manual.js +2 -0
  126. package/src/checks/manual/p-as-heading-manual.js +2 -0
  127. package/src/checks/manual/page-has-heading-one-manual.js +12 -11
  128. package/src/checks/manual/page-title-patterns-manual.js +2 -0
  129. package/src/checks/manual/presentation-role-conflict-manual.js +51 -37
  130. package/src/checks/manual/region-manual.js +27 -36
  131. package/src/checks/manual/scope-attr-valid-manual.js +3 -1
  132. package/src/checks/manual/scrollable-region-focusable-manual.js +2 -0
  133. package/src/checks/manual/skip-link-manual.js +7 -6
  134. package/src/checks/manual/svg-text-alternative-quality-manual.js +2 -0
  135. package/src/checks/manual/tabindex-manual.js +3 -1
  136. package/src/checks/manual/table-duplicate-name-manual.js +5 -4
  137. package/src/checks/manual/table-fake-caption-manual.js +24 -3
  138. package/src/checks/manual/video-caption-manual.js +2 -0
  139. package/src/checks/manual-review.js +2 -0
  140. package/src/core.js +11820 -3317
  141. package/src/index.js +2 -0
  142. package/src/report.js +51 -9
  143. package/src/sarif.js +20 -5
  144. package/surea11y.browser.js +4943 -1388
  145. package/bin/core.js +0 -473
  146. package/docs/CLI.md +0 -128
  147. package/src/catalogs/composites.wcag.js +0 -454
  148. package/src/checks/automatic/bypass-blocks-present.js +0 -215
  149. package/src/checks/rules-and-tags.full.csv +0 -19
  150. package/src/checks/rules-and-tags.full.json +0 -259
  151. package/src/core/aria-helpers.js +0 -1211
  152. package/src/core/contrast-helpers.js +0 -1302
  153. package/src/core/dom-helpers.js +0 -4493
  154. package/src/core/dom-runner.js +0 -787
  155. package/src/core/frame-messaging.js +0 -261
  156. package/src/core/frame-scan.js +0 -190
  157. package/src/core/rollup-composites.js +0 -127
  158. package/src/core/rule-meta.js +0 -176
  159. package/src/coverage/wcag-facets.js +0 -1079
  160. package/src/coverage/wcag-version-map.js +0 -84
  161. package/src/i18n/en.js +0 -1228
  162. package/src/i18n/fr.js +0 -1185
  163. package/src/policy/contracts.js +0 -18
  164. package/src/policy/resolvePolicy.js +0 -59
  165. package/src/policy/schemas/engine-options.schema.json +0 -103
  166. package/src/policy/schemas/policy-contract.schema.json +0 -40
@@ -1,3 +1,5 @@
1
+ /* SPDX-License-Identifier: MPL-2.0 */
2
+
1
3
  'use strict';
2
4
 
3
5
  /**
@@ -14,62 +16,27 @@
14
16
  * supported on any element (the "global" ARIA states/properties, e.g.
15
17
  * aria-label/aria-hidden/aria-describedby), or (b) explicitly listed as
16
18
  * a required or supported state/property for the element's role.
19
+ * An attribute ARIA deprecated (rather than prohibited) on the role is
20
+ * still allowed: it is reported as CANTTELL (see helpers.aria.isDeprecatedAttr)
21
+ * so the author decides, not as a not-allowed FAIL.
17
22
  * @implementation-notes
18
23
  * - Only evaluated for elements with an explicit role, since the global
19
24
  * set already covers implicit-role elements without asserting anything
20
25
  * role-specific; scope kept deliberately narrow to avoid false positives
21
26
  * on implicit-role ARIA-in-HTML edge cases not modeled here.
22
- * - SUPPORTED_ATTRS_BY_ROLE widened 2026-07-21 (7 new roles), cross-checked
23
- * against a widely-used reference engine's own per-role `allowedAttrs`
24
- * table AND verified each addition is an unambiguous, well-established ARIA
25
- * fact rather than a blind import:
26
- * - `searchbox`: identical to the already-covered `textbox` (ARIA
27
- * explicitly defines searchbox as textbox's subclass, same supported
28
- * set).
29
- * - `meter`: valuenow/valuemax/valuemin/valuetext, matching the
30
- * already-covered slider/progressbar/scrollbar/spinbutton pattern
31
- * (valuenow is also separately *required* on meter — see
32
- * `aria-required-attr`'s own 2026-07-21 widening — but the two tables
33
- * aren't unioned automatically, so it needs listing in both places).
34
- * - `columnheader`/`rowheader`: same table-role family already covered
35
- * by `gridcell`/`row` — sort/col-row-index/span are standard WAI-ARIA
36
- * grid-model properties.
37
- * - `dialog`/`alertdialog`: `aria-modal` — the spec-defining property
38
- * that distinguishes a modal from a non-modal dialog; unambiguous.
39
- * - `listitem`: level/posinset/setsize — standard flat-representation-
40
- * of-hierarchy properties, same family as the already-covered
41
- * `treeitem`.
42
- * - `menu`/`menubar`/`toolbar`: activedescendant/orientation — standard
43
- * composite-widget properties, same family as the already-covered
44
- * `tablist`.
45
- * - SUPPORTED_ATTRS_BY_ROLE full-reconciliation pass 2026-07-28: the
46
- * 2026-07-21 pass deliberately deferred ~61 roles where a widely-used
47
- * reference engine allows `aria-expanded`, treating it as too thin/
48
- * near-universal to import blindly. Checked against `aria-query`
49
- * (tracks the published WAI-ARIA 1.2 Recommendation, 6 June 2023 — the
50
- * latest actually-published version, as opposed to the in-progress 1.3
51
- * Editor's Draft) instead of that reference engine directly, because its own
52
- * source comments (`// Spec difference: Aria-expanded removed in 1.2`)
53
- * show that most of its `aria-expanded` allowances are deliberate
54
- * ARIA-1.1 legacy/AT-compat carryovers it keeps on purpose, not
55
- * current-spec facts — importing them wholesale would have re-added
56
- * exactly the kind of unverified allowance the 07-21 pass was avoiding.
57
- * `aria-query`'s per-role tables (with superclass inheritance resolved,
58
- * e.g. `aria-activedescendant` via the abstract `composite` role) gave a
59
- * spec-grounded diff instead. Net changes from that diff:
60
- * - `aria-expanded` added to: checkbox, columnheader, gridcell, listbox,
61
- * menuitemcheckbox, menuitemradio, row, rowheader, switch, tab —
62
- * confirmed present in current spec for these roles specifically
63
- * (unlike e.g. `listitem`, `dialog`, `alertdialog`, `heading`, which
64
- * the spec does NOT list it for — those stay excluded).
65
- * - `aria-activedescendant` added to: combobox, grid, listbox,
66
- * radiogroup, row, spinbutton, tablist, treegrid (inherited from the
67
- * abstract `composite` role for any composite/managed-focus widget).
68
- * - `aria-readonly`/`aria-required` added to: switch, menuitemcheckbox,
69
- * menuitemradio. `aria-posinset`/`aria-setsize` added to: tab, radio,
70
- * menuitemcheckbox, menuitemradio. `aria-level` added to: tablist.
71
- * - `tree`'s `aria-readonly` removed: not in aria-query's resolved
72
- * props for `tree` (was an unverified carryover, not spec-backed).
27
+ * - SUPPORTED_ATTRS_BY_ROLE holds unambiguous, well-established ARIA facts:
28
+ * subclass relationships like `searchbox`==`textbox`; same-family widget
29
+ * properties like `columnheader`/`rowheader` sort/col-row-index/span
30
+ * matching `gridcell`/`row`; spec-defining properties like `dialog`/
31
+ * `alertdialog` `aria-modal`. `aria-expanded` entries are checked against
32
+ * `aria-query` (which tracks the published WAI-ARIA 1.2 Recommendation,
33
+ * with superclass inheritance resolved, e.g. `aria-activedescendant` via
34
+ * the abstract `composite` role) rather than imported wholesale, since
35
+ * much of the wider ARIA-1.1 `aria-expanded` allowance list is legacy/
36
+ * AT-compat carryover, not current-spec fact.
37
+ * `meter`'s valuenow/valuemax/valuemin/valuetext is also separately
38
+ * *required* (see `aria-required-attr`), but the two tables aren't
39
+ * unioned automatically, so it's listed in both places.
73
40
  * - Not rule-gated on isAccTreeEligible: this remains a static-markup
74
41
  * property, while engine-level hidden-subtree filtering still applies
75
42
  * unless engineOptions.includeHiddenElements is true.
@@ -117,6 +84,7 @@ function runInPage(ctx) {
117
84
  // Declared inside runInPage (rather than at module scope) because the
118
85
  // build inlines only this function's own source text — see
119
86
  // scripts/build-core.js header ("runInPage MUST be self-contained").
87
+ // <generated:aria-global-attrs>
120
88
  const GLOBAL_ATTRS = [
121
89
  'aria-atomic',
122
90
  'aria-braillelabel',
@@ -127,14 +95,10 @@ function runInPage(ctx) {
127
95
  'aria-describedby',
128
96
  'aria-description',
129
97
  'aria-details',
130
- 'aria-disabled',
131
98
  'aria-dropeffect',
132
- 'aria-errormessage',
133
99
  'aria-flowto',
134
100
  'aria-grabbed',
135
- 'aria-haspopup',
136
101
  'aria-hidden',
137
- 'aria-invalid',
138
102
  'aria-keyshortcuts',
139
103
  'aria-label',
140
104
  'aria-labelledby',
@@ -143,194 +107,736 @@ function runInPage(ctx) {
143
107
  'aria-relevant',
144
108
  'aria-roledescription'
145
109
  ];
110
+ // </generated:aria-global-attrs>
146
111
 
147
112
  // Per-role supported (non-global) states/properties. Deliberately
148
113
  // conservative — see src/core/aria-helpers.js file header for the same
149
114
  // confidence-scoping rationale; only well-established, unambiguous
150
115
  // role/attribute pairings from the WAI-ARIA role definitions are listed.
116
+ // <generated:aria-implicit-roles>
117
+ const IMPLICIT_ROLE_BY_ELEMENT = {
118
+ article: 'article',
119
+ blockquote: 'blockquote',
120
+ button: 'button',
121
+ caption: 'caption',
122
+ code: 'code',
123
+ dd: 'definition',
124
+ del: 'deletion',
125
+ details: 'group',
126
+ dfn: 'term',
127
+ dialog: 'dialog',
128
+ dt: 'term',
129
+ em: 'emphasis',
130
+ fieldset: 'group',
131
+ figure: 'figure',
132
+ h1: 'heading',
133
+ h2: 'heading',
134
+ h3: 'heading',
135
+ h4: 'heading',
136
+ h5: 'heading',
137
+ h6: 'heading',
138
+ hr: 'separator',
139
+ ins: 'insertion',
140
+ main: 'main',
141
+ mark: 'mark',
142
+ menu: 'list',
143
+ meter: 'meter',
144
+ nav: 'navigation',
145
+ ol: 'list',
146
+ optgroup: 'group',
147
+ option: 'option',
148
+ output: 'status',
149
+ p: 'paragraph',
150
+ progress: 'progressbar',
151
+ strong: 'strong',
152
+ sub: 'subscript',
153
+ sup: 'superscript',
154
+ textarea: 'textbox',
155
+ time: 'time',
156
+ ul: 'list',
157
+ 'input[type=text]': 'textbox',
158
+ 'input[type=tel]': 'textbox',
159
+ 'input[type=url]': 'textbox',
160
+ 'input[type=email]': 'textbox',
161
+ 'input[type=password]': 'textbox',
162
+ 'input[type=search]': 'searchbox',
163
+ 'input[type=number]': 'spinbutton',
164
+ 'input[type=range]': 'slider',
165
+ 'input[type=checkbox]': 'checkbox',
166
+ 'input[type=radio]': 'radio',
167
+ 'input[type=button]': 'button',
168
+ 'input[type=submit]': 'button',
169
+ 'input[type=reset]': 'button',
170
+ 'input[type=image]': 'button'
171
+ };
172
+ const NON_GLOBAL_ARIA_ATTR_SELECTOR =
173
+ '[aria-activedescendant], [aria-autocomplete], [aria-checked], [aria-colcount], [aria-colindex], [aria-colspan], [aria-disabled], [aria-errormessage], [aria-expanded], [aria-haspopup], [aria-invalid], [aria-level], [aria-modal], [aria-multiline], [aria-multiselectable], [aria-orientation], [aria-placeholder], [aria-posinset], [aria-pressed], [aria-readonly], [aria-required], [aria-rowcount], [aria-rowindex], [aria-rowspan], [aria-selected], [aria-setsize], [aria-sort], [aria-valuemax], [aria-valuemin], [aria-valuenow], [aria-valuetext]';
174
+ // </generated:aria-implicit-roles>
175
+
176
+ // <generated:aria-role-attrs>
151
177
  const SUPPORTED_ATTRS_BY_ROLE = {
178
+ alert: [],
152
179
  alertdialog: ['aria-modal'],
153
- checkbox: ['aria-checked', 'aria-readonly', 'aria-required', 'aria-expanded'],
180
+ application: [
181
+ 'aria-activedescendant',
182
+ 'aria-disabled',
183
+ 'aria-errormessage',
184
+ 'aria-expanded',
185
+ 'aria-haspopup',
186
+ 'aria-invalid'
187
+ ],
188
+ article: ['aria-posinset', 'aria-setsize'],
189
+ banner: [],
190
+ blockquote: [],
191
+ button: ['aria-disabled', 'aria-expanded', 'aria-haspopup', 'aria-pressed'],
192
+ caption: [],
193
+ cell: ['aria-colindex', 'aria-colspan', 'aria-rowindex', 'aria-rowspan'],
194
+ checkbox: [
195
+ 'aria-checked',
196
+ 'aria-disabled',
197
+ 'aria-errormessage',
198
+ 'aria-expanded',
199
+ 'aria-invalid',
200
+ 'aria-readonly',
201
+ 'aria-required'
202
+ ],
203
+ code: [],
154
204
  columnheader: [
155
- 'aria-sort',
156
205
  'aria-colindex',
157
206
  'aria-colspan',
207
+ 'aria-disabled',
208
+ 'aria-errormessage',
209
+ 'aria-expanded',
210
+ 'aria-haspopup',
211
+ 'aria-invalid',
158
212
  'aria-readonly',
159
213
  'aria-required',
160
214
  'aria-rowindex',
161
215
  'aria-rowspan',
162
216
  'aria-selected',
163
- 'aria-expanded'
217
+ 'aria-sort'
164
218
  ],
165
219
  combobox: [
166
- 'aria-expanded',
220
+ 'aria-activedescendant',
167
221
  'aria-autocomplete',
222
+ 'aria-disabled',
223
+ 'aria-errormessage',
224
+ 'aria-expanded',
225
+ 'aria-haspopup',
226
+ 'aria-invalid',
168
227
  'aria-readonly',
169
- 'aria-required',
170
- 'aria-activedescendant'
228
+ 'aria-required'
171
229
  ],
230
+ complementary: [],
231
+ contentinfo: [],
232
+ definition: [],
233
+ deletion: [],
172
234
  dialog: ['aria-modal'],
235
+ directory: [],
236
+ 'doc-abstract': [
237
+ 'aria-disabled',
238
+ 'aria-errormessage',
239
+ 'aria-expanded',
240
+ 'aria-haspopup',
241
+ 'aria-invalid'
242
+ ],
243
+ 'doc-acknowledgments': [
244
+ 'aria-disabled',
245
+ 'aria-errormessage',
246
+ 'aria-expanded',
247
+ 'aria-haspopup',
248
+ 'aria-invalid'
249
+ ],
250
+ 'doc-afterword': [
251
+ 'aria-disabled',
252
+ 'aria-errormessage',
253
+ 'aria-expanded',
254
+ 'aria-haspopup',
255
+ 'aria-invalid'
256
+ ],
257
+ 'doc-appendix': [
258
+ 'aria-disabled',
259
+ 'aria-errormessage',
260
+ 'aria-expanded',
261
+ 'aria-haspopup',
262
+ 'aria-invalid'
263
+ ],
264
+ 'doc-backlink': [
265
+ 'aria-disabled',
266
+ 'aria-errormessage',
267
+ 'aria-expanded',
268
+ 'aria-haspopup',
269
+ 'aria-invalid'
270
+ ],
271
+ 'doc-biblioentry': [
272
+ 'aria-disabled',
273
+ 'aria-errormessage',
274
+ 'aria-expanded',
275
+ 'aria-haspopup',
276
+ 'aria-invalid',
277
+ 'aria-level',
278
+ 'aria-posinset',
279
+ 'aria-setsize'
280
+ ],
281
+ 'doc-bibliography': [
282
+ 'aria-disabled',
283
+ 'aria-errormessage',
284
+ 'aria-expanded',
285
+ 'aria-haspopup',
286
+ 'aria-invalid'
287
+ ],
288
+ 'doc-biblioref': [
289
+ 'aria-disabled',
290
+ 'aria-errormessage',
291
+ 'aria-expanded',
292
+ 'aria-haspopup',
293
+ 'aria-invalid'
294
+ ],
295
+ 'doc-chapter': [
296
+ 'aria-disabled',
297
+ 'aria-errormessage',
298
+ 'aria-expanded',
299
+ 'aria-haspopup',
300
+ 'aria-invalid'
301
+ ],
302
+ 'doc-colophon': [
303
+ 'aria-disabled',
304
+ 'aria-errormessage',
305
+ 'aria-expanded',
306
+ 'aria-haspopup',
307
+ 'aria-invalid'
308
+ ],
309
+ 'doc-conclusion': [
310
+ 'aria-disabled',
311
+ 'aria-errormessage',
312
+ 'aria-expanded',
313
+ 'aria-haspopup',
314
+ 'aria-invalid'
315
+ ],
316
+ 'doc-cover': [
317
+ 'aria-disabled',
318
+ 'aria-errormessage',
319
+ 'aria-expanded',
320
+ 'aria-haspopup',
321
+ 'aria-invalid'
322
+ ],
323
+ 'doc-credit': [
324
+ 'aria-disabled',
325
+ 'aria-errormessage',
326
+ 'aria-expanded',
327
+ 'aria-haspopup',
328
+ 'aria-invalid'
329
+ ],
330
+ 'doc-credits': [
331
+ 'aria-disabled',
332
+ 'aria-errormessage',
333
+ 'aria-expanded',
334
+ 'aria-haspopup',
335
+ 'aria-invalid'
336
+ ],
337
+ 'doc-dedication': [
338
+ 'aria-disabled',
339
+ 'aria-errormessage',
340
+ 'aria-expanded',
341
+ 'aria-haspopup',
342
+ 'aria-invalid'
343
+ ],
344
+ 'doc-endnote': [
345
+ 'aria-disabled',
346
+ 'aria-errormessage',
347
+ 'aria-expanded',
348
+ 'aria-haspopup',
349
+ 'aria-invalid',
350
+ 'aria-level',
351
+ 'aria-posinset',
352
+ 'aria-setsize'
353
+ ],
354
+ 'doc-endnotes': [
355
+ 'aria-disabled',
356
+ 'aria-errormessage',
357
+ 'aria-expanded',
358
+ 'aria-haspopup',
359
+ 'aria-invalid'
360
+ ],
361
+ 'doc-epigraph': [
362
+ 'aria-disabled',
363
+ 'aria-errormessage',
364
+ 'aria-expanded',
365
+ 'aria-haspopup',
366
+ 'aria-invalid'
367
+ ],
368
+ 'doc-epilogue': [
369
+ 'aria-disabled',
370
+ 'aria-errormessage',
371
+ 'aria-expanded',
372
+ 'aria-haspopup',
373
+ 'aria-invalid'
374
+ ],
375
+ 'doc-errata': [
376
+ 'aria-disabled',
377
+ 'aria-errormessage',
378
+ 'aria-expanded',
379
+ 'aria-haspopup',
380
+ 'aria-invalid'
381
+ ],
382
+ 'doc-example': [
383
+ 'aria-disabled',
384
+ 'aria-errormessage',
385
+ 'aria-expanded',
386
+ 'aria-haspopup',
387
+ 'aria-invalid'
388
+ ],
389
+ 'doc-footnote': [
390
+ 'aria-disabled',
391
+ 'aria-errormessage',
392
+ 'aria-expanded',
393
+ 'aria-haspopup',
394
+ 'aria-invalid'
395
+ ],
396
+ 'doc-foreword': [
397
+ 'aria-disabled',
398
+ 'aria-errormessage',
399
+ 'aria-expanded',
400
+ 'aria-haspopup',
401
+ 'aria-invalid'
402
+ ],
403
+ 'doc-glossary': [
404
+ 'aria-disabled',
405
+ 'aria-errormessage',
406
+ 'aria-expanded',
407
+ 'aria-haspopup',
408
+ 'aria-invalid'
409
+ ],
410
+ 'doc-glossref': [
411
+ 'aria-disabled',
412
+ 'aria-errormessage',
413
+ 'aria-expanded',
414
+ 'aria-haspopup',
415
+ 'aria-invalid'
416
+ ],
417
+ 'doc-index': [
418
+ 'aria-disabled',
419
+ 'aria-errormessage',
420
+ 'aria-expanded',
421
+ 'aria-haspopup',
422
+ 'aria-invalid'
423
+ ],
424
+ 'doc-introduction': [
425
+ 'aria-disabled',
426
+ 'aria-errormessage',
427
+ 'aria-expanded',
428
+ 'aria-haspopup',
429
+ 'aria-invalid'
430
+ ],
431
+ 'doc-noteref': [
432
+ 'aria-disabled',
433
+ 'aria-errormessage',
434
+ 'aria-expanded',
435
+ 'aria-haspopup',
436
+ 'aria-invalid'
437
+ ],
438
+ 'doc-notice': [
439
+ 'aria-disabled',
440
+ 'aria-errormessage',
441
+ 'aria-expanded',
442
+ 'aria-haspopup',
443
+ 'aria-invalid'
444
+ ],
445
+ 'doc-pagebreak': [
446
+ 'aria-disabled',
447
+ 'aria-errormessage',
448
+ 'aria-expanded',
449
+ 'aria-haspopup',
450
+ 'aria-invalid',
451
+ 'aria-orientation',
452
+ 'aria-valuemax',
453
+ 'aria-valuemin',
454
+ 'aria-valuenow',
455
+ 'aria-valuetext'
456
+ ],
457
+ 'doc-pagefooter': ['aria-disabled', 'aria-errormessage', 'aria-haspopup', 'aria-invalid'],
458
+ 'doc-pageheader': ['aria-disabled', 'aria-errormessage', 'aria-haspopup', 'aria-invalid'],
459
+ 'doc-pagelist': [
460
+ 'aria-disabled',
461
+ 'aria-errormessage',
462
+ 'aria-expanded',
463
+ 'aria-haspopup',
464
+ 'aria-invalid'
465
+ ],
466
+ 'doc-part': [
467
+ 'aria-disabled',
468
+ 'aria-errormessage',
469
+ 'aria-expanded',
470
+ 'aria-haspopup',
471
+ 'aria-invalid'
472
+ ],
473
+ 'doc-preface': [
474
+ 'aria-disabled',
475
+ 'aria-errormessage',
476
+ 'aria-expanded',
477
+ 'aria-haspopup',
478
+ 'aria-invalid'
479
+ ],
480
+ 'doc-prologue': [
481
+ 'aria-disabled',
482
+ 'aria-errormessage',
483
+ 'aria-expanded',
484
+ 'aria-haspopup',
485
+ 'aria-invalid'
486
+ ],
487
+ 'doc-pullquote': [],
488
+ 'doc-qna': [
489
+ 'aria-disabled',
490
+ 'aria-errormessage',
491
+ 'aria-expanded',
492
+ 'aria-haspopup',
493
+ 'aria-invalid'
494
+ ],
495
+ 'doc-subtitle': [
496
+ 'aria-disabled',
497
+ 'aria-errormessage',
498
+ 'aria-expanded',
499
+ 'aria-haspopup',
500
+ 'aria-invalid'
501
+ ],
502
+ 'doc-tip': [
503
+ 'aria-disabled',
504
+ 'aria-errormessage',
505
+ 'aria-expanded',
506
+ 'aria-haspopup',
507
+ 'aria-invalid'
508
+ ],
509
+ 'doc-toc': [
510
+ 'aria-disabled',
511
+ 'aria-errormessage',
512
+ 'aria-expanded',
513
+ 'aria-haspopup',
514
+ 'aria-invalid'
515
+ ],
516
+ document: [],
517
+ emphasis: [],
518
+ feed: [],
519
+ figure: [],
520
+ form: [],
521
+ generic: [],
522
+ 'graphics-document': [
523
+ 'aria-disabled',
524
+ 'aria-errormessage',
525
+ 'aria-expanded',
526
+ 'aria-haspopup',
527
+ 'aria-invalid'
528
+ ],
529
+ 'graphics-object': [
530
+ 'aria-activedescendant',
531
+ 'aria-disabled',
532
+ 'aria-errormessage',
533
+ 'aria-expanded',
534
+ 'aria-haspopup',
535
+ 'aria-invalid'
536
+ ],
537
+ 'graphics-symbol': [
538
+ 'aria-disabled',
539
+ 'aria-errormessage',
540
+ 'aria-expanded',
541
+ 'aria-haspopup',
542
+ 'aria-invalid'
543
+ ],
173
544
  grid: [
545
+ 'aria-activedescendant',
546
+ 'aria-colcount',
547
+ 'aria-disabled',
174
548
  'aria-multiselectable',
175
549
  'aria-readonly',
176
- 'aria-colcount',
177
- 'aria-rowcount',
178
- 'aria-activedescendant'
550
+ 'aria-rowcount'
179
551
  ],
180
552
  gridcell: [
181
- 'aria-selected',
182
- 'aria-readonly',
183
- 'aria-required',
184
553
  'aria-colindex',
185
554
  'aria-colspan',
555
+ 'aria-disabled',
556
+ 'aria-errormessage',
557
+ 'aria-expanded',
558
+ 'aria-haspopup',
559
+ 'aria-invalid',
560
+ 'aria-readonly',
561
+ 'aria-required',
186
562
  'aria-rowindex',
187
563
  'aria-rowspan',
188
- 'aria-expanded'
564
+ 'aria-selected'
189
565
  ],
566
+ group: ['aria-activedescendant', 'aria-disabled'],
190
567
  heading: ['aria-level'],
568
+ img: [],
569
+ insertion: [],
570
+ link: ['aria-disabled', 'aria-expanded', 'aria-haspopup'],
571
+ list: [],
191
572
  listbox: [
573
+ 'aria-activedescendant',
574
+ 'aria-disabled',
575
+ 'aria-errormessage',
576
+ 'aria-expanded',
577
+ 'aria-invalid',
192
578
  'aria-multiselectable',
193
- 'aria-readonly',
194
- 'aria-required',
195
579
  'aria-orientation',
196
- 'aria-expanded',
197
- 'aria-activedescendant'
580
+ 'aria-readonly',
581
+ 'aria-required'
198
582
  ],
199
583
  listitem: ['aria-level', 'aria-posinset', 'aria-setsize'],
200
- menu: ['aria-activedescendant', 'aria-orientation'],
201
- menubar: ['aria-activedescendant', 'aria-orientation'],
584
+ log: [],
585
+ main: [],
586
+ mark: [],
587
+ marquee: [],
588
+ math: [],
589
+ menu: ['aria-activedescendant', 'aria-disabled', 'aria-orientation'],
590
+ menubar: ['aria-activedescendant', 'aria-disabled', 'aria-orientation'],
591
+ menuitem: ['aria-disabled', 'aria-expanded', 'aria-haspopup', 'aria-posinset', 'aria-setsize'],
202
592
  menuitemcheckbox: [
203
593
  'aria-checked',
594
+ 'aria-disabled',
595
+ 'aria-errormessage',
204
596
  'aria-expanded',
597
+ 'aria-haspopup',
598
+ 'aria-invalid',
599
+ 'aria-posinset',
205
600
  'aria-readonly',
206
601
  'aria-required',
207
- 'aria-posinset',
208
602
  'aria-setsize'
209
603
  ],
210
604
  menuitemradio: [
211
605
  'aria-checked',
606
+ 'aria-disabled',
607
+ 'aria-errormessage',
212
608
  'aria-expanded',
609
+ 'aria-haspopup',
610
+ 'aria-invalid',
611
+ 'aria-posinset',
213
612
  'aria-readonly',
214
613
  'aria-required',
215
- 'aria-posinset',
216
614
  'aria-setsize'
217
615
  ],
218
- meter: ['aria-valuenow', 'aria-valuemin', 'aria-valuemax', 'aria-valuetext'],
219
- option: ['aria-selected', 'aria-checked', 'aria-posinset', 'aria-setsize'],
220
- progressbar: ['aria-valuenow', 'aria-valuemin', 'aria-valuemax', 'aria-valuetext'],
221
- radio: ['aria-checked', 'aria-posinset', 'aria-setsize'],
222
- radiogroup: ['aria-readonly', 'aria-required', 'aria-orientation', 'aria-activedescendant'],
616
+ meter: ['aria-valuemax', 'aria-valuemin', 'aria-valuenow', 'aria-valuetext'],
617
+ navigation: [],
618
+ none: [],
619
+ note: [],
620
+ option: ['aria-checked', 'aria-disabled', 'aria-posinset', 'aria-selected', 'aria-setsize'],
621
+ paragraph: [],
622
+ presentation: [],
623
+ progressbar: ['aria-valuemax', 'aria-valuemin', 'aria-valuenow', 'aria-valuetext'],
624
+ radio: ['aria-checked', 'aria-disabled', 'aria-posinset', 'aria-setsize'],
625
+ radiogroup: [
626
+ 'aria-activedescendant',
627
+ 'aria-disabled',
628
+ 'aria-errormessage',
629
+ 'aria-invalid',
630
+ 'aria-orientation',
631
+ 'aria-readonly',
632
+ 'aria-required'
633
+ ],
634
+ region: [],
223
635
  row: [
224
- 'aria-selected',
636
+ 'aria-activedescendant',
637
+ 'aria-colindex',
638
+ 'aria-disabled',
639
+ 'aria-expanded',
225
640
  'aria-level',
226
641
  'aria-posinset',
227
- 'aria-setsize',
228
- 'aria-colindex',
229
642
  'aria-rowindex',
230
- 'aria-expanded',
231
- 'aria-activedescendant'
643
+ 'aria-selected',
644
+ 'aria-setsize'
232
645
  ],
646
+ rowgroup: [],
233
647
  rowheader: [
234
- 'aria-sort',
235
648
  'aria-colindex',
236
649
  'aria-colspan',
650
+ 'aria-disabled',
651
+ 'aria-errormessage',
652
+ 'aria-expanded',
653
+ 'aria-haspopup',
654
+ 'aria-invalid',
237
655
  'aria-readonly',
238
656
  'aria-required',
239
657
  'aria-rowindex',
240
658
  'aria-rowspan',
241
659
  'aria-selected',
242
- 'aria-expanded'
660
+ 'aria-sort'
243
661
  ],
244
662
  scrollbar: [
245
- 'aria-valuenow',
246
- 'aria-valuemin',
247
- 'aria-valuemax',
248
- 'aria-valuetext',
663
+ 'aria-disabled',
249
664
  'aria-orientation',
250
- 'aria-controls'
665
+ 'aria-valuemax',
666
+ 'aria-valuemin',
667
+ 'aria-valuenow',
668
+ 'aria-valuetext'
251
669
  ],
670
+ search: [],
252
671
  searchbox: [
253
672
  'aria-activedescendant',
254
673
  'aria-autocomplete',
674
+ 'aria-disabled',
675
+ 'aria-errormessage',
676
+ 'aria-haspopup',
677
+ 'aria-invalid',
255
678
  'aria-multiline',
256
679
  'aria-placeholder',
257
680
  'aria-readonly',
258
681
  'aria-required'
259
682
  ],
260
683
  separator: [
261
- 'aria-valuenow',
262
- 'aria-valuemin',
684
+ 'aria-disabled',
685
+ 'aria-orientation',
263
686
  'aria-valuemax',
264
- 'aria-valuetext',
265
- 'aria-orientation'
687
+ 'aria-valuemin',
688
+ 'aria-valuenow',
689
+ 'aria-valuetext'
266
690
  ],
267
691
  slider: [
268
- 'aria-valuenow',
269
- 'aria-valuemin',
270
- 'aria-valuemax',
271
- 'aria-valuetext',
692
+ 'aria-disabled',
693
+ 'aria-errormessage',
694
+ 'aria-haspopup',
695
+ 'aria-invalid',
272
696
  'aria-orientation',
273
- 'aria-readonly'
697
+ 'aria-readonly',
698
+ 'aria-valuemax',
699
+ 'aria-valuemin',
700
+ 'aria-valuenow',
701
+ 'aria-valuetext'
274
702
  ],
275
703
  spinbutton: [
276
- 'aria-valuenow',
277
- 'aria-valuemin',
278
- 'aria-valuemax',
279
- 'aria-valuetext',
704
+ 'aria-activedescendant',
705
+ 'aria-disabled',
706
+ 'aria-errormessage',
707
+ 'aria-invalid',
280
708
  'aria-readonly',
281
709
  'aria-required',
282
- 'aria-activedescendant'
710
+ 'aria-valuemax',
711
+ 'aria-valuemin',
712
+ 'aria-valuenow',
713
+ 'aria-valuetext'
714
+ ],
715
+ status: [],
716
+ strong: [],
717
+ subscript: [],
718
+ superscript: [],
719
+ switch: [
720
+ 'aria-checked',
721
+ 'aria-disabled',
722
+ 'aria-errormessage',
723
+ 'aria-expanded',
724
+ 'aria-invalid',
725
+ 'aria-readonly',
726
+ 'aria-required'
727
+ ],
728
+ tab: [
729
+ 'aria-disabled',
730
+ 'aria-expanded',
731
+ 'aria-haspopup',
732
+ 'aria-posinset',
733
+ 'aria-selected',
734
+ 'aria-setsize'
283
735
  ],
284
- switch: ['aria-checked', 'aria-expanded', 'aria-readonly', 'aria-required'],
285
- tab: ['aria-selected', 'aria-expanded', 'aria-posinset', 'aria-setsize'],
286
736
  table: ['aria-colcount', 'aria-rowcount'],
287
- tablist: ['aria-multiselectable', 'aria-orientation', 'aria-level', 'aria-activedescendant'],
737
+ tablist: [
738
+ 'aria-activedescendant',
739
+ 'aria-disabled',
740
+ 'aria-level',
741
+ 'aria-multiselectable',
742
+ 'aria-orientation'
743
+ ],
744
+ tabpanel: [],
745
+ term: [],
288
746
  textbox: [
289
747
  'aria-activedescendant',
290
748
  'aria-autocomplete',
749
+ 'aria-disabled',
750
+ 'aria-errormessage',
751
+ 'aria-haspopup',
752
+ 'aria-invalid',
291
753
  'aria-multiline',
292
754
  'aria-placeholder',
293
755
  'aria-readonly',
294
756
  'aria-required'
295
757
  ],
296
- toolbar: ['aria-activedescendant', 'aria-orientation'],
297
- tree: ['aria-multiselectable', 'aria-required', 'aria-orientation', 'aria-activedescendant'],
758
+ time: [],
759
+ timer: [],
760
+ toolbar: ['aria-activedescendant', 'aria-disabled', 'aria-orientation'],
761
+ tooltip: [],
762
+ tree: [
763
+ 'aria-activedescendant',
764
+ 'aria-disabled',
765
+ 'aria-errormessage',
766
+ 'aria-invalid',
767
+ 'aria-multiselectable',
768
+ 'aria-orientation',
769
+ 'aria-required'
770
+ ],
298
771
  treegrid: [
772
+ 'aria-activedescendant',
773
+ 'aria-colcount',
774
+ 'aria-disabled',
775
+ 'aria-errormessage',
776
+ 'aria-invalid',
299
777
  'aria-multiselectable',
778
+ 'aria-orientation',
300
779
  'aria-readonly',
301
780
  'aria-required',
302
- 'aria-orientation',
303
- 'aria-colcount',
304
- 'aria-rowcount',
305
- 'aria-activedescendant'
781
+ 'aria-rowcount'
306
782
  ],
307
783
  treeitem: [
308
784
  'aria-checked',
309
- 'aria-selected',
785
+ 'aria-disabled',
310
786
  'aria-expanded',
787
+ 'aria-haspopup',
311
788
  'aria-level',
312
789
  'aria-posinset',
790
+ 'aria-selected',
313
791
  'aria-setsize'
314
792
  ]
315
793
  };
794
+ // </generated:aria-role-attrs>
316
795
 
317
796
  const globalSet = new Set(GLOBAL_ATTRS);
797
+ // [role] keeps the explicit-role path; the attribute selector brings in
798
+ // elements judged by their implicit role. Only non-global attributes can be
799
+ // disallowed, so nothing else needs visiting.
800
+ const selector = '[role], ' + NON_GLOBAL_ARIA_ATTR_SELECTOR;
318
801
  const nodes = helpers.queryAllSmart
319
- ? helpers.queryAllSmart('[role]')
320
- : helpers.queryAll('[role]');
802
+ ? helpers.queryAllSmart(selector)
803
+ : helpers.queryAll(selector);
321
804
 
322
- const occurrences = [];
805
+ const failOccurrences = [];
806
+ const cantTellOccurrences = [];
323
807
  let applicableCount = 0;
324
808
 
325
809
  for (const el of nodes) {
326
810
  if (!el || !el.attributes) continue;
327
811
 
328
- const role = ariaHelpers.getExplicitRole(el);
812
+ // ACT 5c01ea scopes the rule to any element carrying an ARIA attribute, so
813
+ // an element with no role attribute is judged against its implicit role.
814
+ // Only elements whose implicit role is context-free are covered; the
815
+ // generator lists what is excluded and why.
816
+ const explicitRole = ariaHelpers.getExplicitRole(el);
817
+ let role = explicitRole;
818
+ if (!role) {
819
+ const tag = String(el.tagName || '').toLowerCase();
820
+ const key =
821
+ tag === 'input'
822
+ ? 'input[type=' + String(el.getAttribute('type') || 'text').toLowerCase() + ']'
823
+ : tag;
824
+ role = Object.prototype.hasOwnProperty.call(IMPLICIT_ROLE_BY_ELEMENT, key)
825
+ ? IMPLICIT_ROLE_BY_ELEMENT[key]
826
+ : '';
827
+ }
329
828
  if (!role || !ariaHelpers.isValidConcreteRole(role)) continue; // aria-roles-valid's concern
330
829
 
331
- // Deliberately scoped: only roles with an explicit supported-attrs
332
- // entry are evaluated (see file header — kept narrow to avoid
333
- // over-claiming constraints for roles not yet modeled here).
830
+ // Presentational role conflict resolution drops role="none"/"presentation"
831
+ // when the element is focusable or carries global ARIA, so the implicit
832
+ // role decides which attributes are supported. Judging against the
833
+ // presentational role would flag valid markup such as
834
+ // <button role="none" aria-pressed="false">; presentation-role-conflict
835
+ // owns this case.
836
+ if (role === 'none' || role === 'presentation') continue;
837
+
838
+ // Roles absent from the generated table are unknown to ARIA, so there is
839
+ // nothing to judge them against.
334
840
  const roleSupported = SUPPORTED_ATTRS_BY_ROLE[role];
335
841
  if (!roleSupported) continue;
336
842
  const roleSupportedSet = new Set(roleSupported);
@@ -356,11 +862,36 @@ function runInPage(ctx) {
356
862
  const html = helpers.getOuterHtmlSnippet ? helpers.getOuterHtmlSnippet(el) : el.outerHTML || '';
357
863
 
358
864
  for (const name of disallowed) {
359
- occurrences.push({
865
+ // A property ARIA deprecated (rather than prohibited) on this role is
866
+ // still allowed — surfaced as cantTell for the author to decide, not a
867
+ // not-allowed fail.
868
+ const deprecated =
869
+ typeof ariaHelpers.isDeprecatedAttr === 'function' &&
870
+ ariaHelpers.isDeprecatedAttr(name, role);
871
+ if (deprecated) {
872
+ cantTellOccurrences.push({
873
+ selector: stableSelector,
874
+ html,
875
+ summary: 'This ARIA attribute is deprecated for this element’s role.',
876
+ hint: 'It is still allowed but discouraged; remove it or use a role that supports it, as a future ARIA version may disallow it.',
877
+ occurrenceOutcome: 'cantTell',
878
+ i18n: {
879
+ summaryKey: 'ariaAllowedAttr_summary_cantTell',
880
+ hintKey: 'ariaAllowedAttr_hint_cantTell',
881
+ params: { attr: name, role }
882
+ },
883
+ data: {
884
+ details: { reasonCode: 'ARIA_ATTR_DEPRECATED', attr: name, role }
885
+ }
886
+ });
887
+ continue;
888
+ }
889
+ failOccurrences.push({
360
890
  selector: stableSelector,
361
891
  html,
362
892
  summary: 'This ARIA attribute is not permitted for this element’s role.',
363
893
  hint: 'Remove this attribute, or use a role that supports it.',
894
+ occurrenceOutcome: 'fail',
364
895
  i18n: {
365
896
  summaryKey: 'ariaAllowedAttr_summary_fail',
366
897
  hintKey: 'ariaAllowedAttr_hint_fail',
@@ -376,15 +907,13 @@ function runInPage(ctx) {
376
907
  if (applicableCount === 0) {
377
908
  return { ruleId: rule.ruleId, outcome: 'notApplicable', severity: 'minor', occurrences: [] };
378
909
  }
379
- if (occurrences.length) {
380
- return {
381
- ruleId: rule.ruleId,
382
- outcome: 'fail',
383
- severity: rule.defaultSeverity || 'moderate',
384
- occurrences
385
- };
386
- }
387
- return { ruleId: rule.ruleId, outcome: 'pass', severity: 'minor', occurrences: [] };
910
+
911
+ const resolved = helpers.resolveTieredOutcome(
912
+ failOccurrences,
913
+ cantTellOccurrences,
914
+ rule.defaultSeverity || 'moderate'
915
+ );
916
+ return { ruleId: rule.ruleId, ...resolved };
388
917
  }
389
918
 
390
919
  module.exports = { id, meta, runInPage };