@surea11y/core 1.6.0 → 1.7.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.
- package/CHANGELOG.md +47 -0
- package/README.md +24 -38
- package/docs/ACT_RULE_MAPPING.md +8 -6
- package/docs/API_STABILITY.md +51 -3
- package/docs/BINDING_AUTHORS_GUIDE.md +104 -2
- package/docs/DESIGN_CHALLENGES.md +66 -0
- package/docs/EARL.md +100 -0
- package/docs/ENGINE_OPTIONS.md +28 -2
- package/docs/INTEGRATION.md +4 -2
- package/docs/LIMITATIONS.md +3 -1
- package/docs/OUTPUT_SCHEMA.md +44 -6
- package/docs/POLICY.md +1 -1
- package/docs/RULE_AUTHORING.md +11 -12
- package/docs/RULE_CATALOG.md +76 -26
- package/docs/RULE_HELPERS.md +333 -0
- package/docs/RULE_TAXONOMY.md +25 -4
- package/docs/SARIF.md +21 -2
- package/docs/WCAG_CONFORMANCE.md +9 -1
- package/package.json +9 -3
- package/src/checks/automatic/aria-allowed-attr.js +6 -0
- package/src/checks/automatic/aria-allowed-role.js +32 -23
- package/src/checks/automatic/aria-braille-equivalent.js +18 -10
- package/src/checks/automatic/aria-conditional-attr.js +17 -10
- package/src/checks/automatic/aria-deprecated-role.js +12 -0
- package/src/checks/automatic/aria-hidden-body.js +1 -1
- package/src/checks/automatic/aria-prohibited-attr.js +5 -0
- package/src/checks/automatic/aria-prohibited-children.js +6 -6
- package/src/checks/automatic/aria-required-attr.js +59 -12
- package/src/checks/automatic/aria-required-children.js +33 -16
- package/src/checks/automatic/aria-required-parent.js +32 -6
- package/src/checks/automatic/aria-role-name-present.js +1 -1
- package/src/checks/automatic/aria-roles-valid.js +52 -21
- package/src/checks/automatic/aria-valid-attr-value.js +74 -21
- package/src/checks/automatic/aria-valid-attr.js +14 -9
- package/src/checks/automatic/avoid-inline-spacing.js +133 -6
- package/src/checks/automatic/contrast-computable.js +10 -0
- package/src/checks/automatic/contrast-enhanced.js +12 -0
- package/src/checks/automatic/contrast-minimum.js +12 -0
- package/src/checks/automatic/css-orientation-lock.js +42 -5
- package/src/checks/automatic/duplicate-id-aria.js +5 -0
- package/src/checks/automatic/duplicate-id.js +13 -8
- package/src/checks/automatic/form-control-single-label.js +9 -0
- package/src/checks/automatic/identical-iframes-same-purpose.js +229 -0
- package/src/checks/automatic/iframe-focusable-content.js +5 -0
- package/src/checks/automatic/label-in-name.js +38 -56
- package/src/checks/automatic/link-in-text-block.js +279 -23
- package/src/checks/automatic/target-size-minimum.js +84 -5
- package/src/checks/automatic/td-has-header.js +19 -18
- package/src/checks/manual/form-control-label-quality-manual.js +134 -24
- package/src/checks/manual/landmark-complementary-is-top-level-manual.js +231 -0
- package/src/checks/manual/password-paste-enabled-manual.js +255 -0
- package/src/core.js +3863 -44184
- package/src/earl.js +144 -0
- package/src/sarif.js +22 -2
- package/surea11y.browser.js +10 -41039
- package/surea11y.i18n.de.js +2 -21
- package/surea11y.i18n.es.js +2 -21
- package/surea11y.i18n.fr.js +2 -21
- /package/src/checks/automatic/{role-img-alt-present.js → role-img-text-alternative-present.js} +0 -0
package/docs/RULE_CATALOG.md
CHANGED
|
@@ -2,29 +2,29 @@
|
|
|
2
2
|
|
|
3
3
|
Generated from the compiled engine's own catalog (`getChecksCatalog()`/`getRulesCatalog()`) and each rule's source header. Run `node scripts/generate-rule-catalog.js` after `npm run build` to regenerate this file whenever rules change. Do not hand-edit.
|
|
4
4
|
|
|
5
|
-
**
|
|
5
|
+
**133 rules total: 79 automatic (WCAG-normative, can return `fail`), 54 manual (advisory/judgment-required, capped at `cantTell`). 107 carry at least one formal WCAG Success Criterion mapping.**
|
|
6
6
|
|
|
7
7
|
The tables below are an index; [rule reference](#rule-reference) carries each rule's description, what it applies to and what it expects.
|
|
8
8
|
|
|
9
9
|
See [`OUTPUT_SCHEMA.md`](./OUTPUT_SCHEMA.md) for what `type`/`confidence`/`severity` mean on a scan result, and [`WCAG_CONFORMANCE.md`](./WCAG_CONFORMANCE.md) for how these roll up to an SC-level conformance claim. For WCAG-facet-level coverage-gap tracking (which parts of an SC are and aren't automatable yet), see `coverage/coverage-report.md` instead: that one is organized by facet, this one by rule.
|
|
10
10
|
|
|
11
|
-
## Automatic rules (
|
|
11
|
+
## Automatic rules (79), can return `fail`
|
|
12
12
|
|
|
13
13
|
| Rule ID | Title | WCAG SC | Level | Confidence | Default severity |
|
|
14
14
|
|---|---|---|---|---|---|
|
|
15
15
|
| [`area-alt-present`](#area-alt-present) | <area> must have an alt attribute | 1.1.1 | A | high | serious |
|
|
16
16
|
| [`aria-allowed-attr`](#aria-allowed-attr) | aria-* attributes must be permitted for the element’s role | 4.1.2 | A | medium | moderate |
|
|
17
|
-
| [`aria-allowed-role`](#aria-allowed-role) | Explicit role must be permitted for its host element |
|
|
18
|
-
| [`aria-braille-equivalent`](#aria-braille-equivalent) | aria-braillelabel/aria-brailleroledescription must have a non-braille equivalent | 4.1.2 | A | high |
|
|
19
|
-
| [`aria-conditional-attr`](#aria-conditional-attr) | aria-errormessage requires aria-invalid to be set to a non-false value | 4.1.2 | A | high |
|
|
17
|
+
| [`aria-allowed-role`](#aria-allowed-role) | Explicit role must be permitted for its host element | — | — | high | moderate |
|
|
18
|
+
| [`aria-braille-equivalent`](#aria-braille-equivalent) | aria-braillelabel/aria-brailleroledescription must have a non-braille equivalent | 4.1.2 | A | high | moderate |
|
|
19
|
+
| [`aria-conditional-attr`](#aria-conditional-attr) | aria-errormessage requires aria-invalid to be set to a non-false value | 4.1.2 | A | high | moderate |
|
|
20
20
|
| [`aria-deprecated-role`](#aria-deprecated-role) | role attribute should not use a deprecated or author-discouraged ARIA role | 4.1.2 | A | high | moderate |
|
|
21
21
|
| [`aria-hidden-body`](#aria-hidden-body) | The document <body> must not be aria-hidden | 1.3.1, 4.1.2 | A | high | critical |
|
|
22
22
|
| [`aria-hidden-focus`](#aria-hidden-focus) | ARIA hidden elements must not be focusable | 2.4.7, 4.1.2 | AA | high | serious |
|
|
23
23
|
| [`aria-prohibited-attr`](#aria-prohibited-attr) | ARIA naming attributes must not be used on roles that prohibit them | 4.1.2 | A | high | moderate |
|
|
24
|
-
| [`aria-prohibited-children`](#aria-prohibited-children) | Container roles must not own a child with a disallowed role |
|
|
24
|
+
| [`aria-prohibited-children`](#aria-prohibited-children) | Container roles must not own a child with a disallowed role | 1.3.1 | A | medium | moderate |
|
|
25
25
|
| [`aria-required-attr`](#aria-required-attr) | Roles with a required ARIA state/property must carry it | 4.1.2 | A | high | serious |
|
|
26
|
-
| [`aria-required-children`](#aria-required-children) | Container roles must own at least one required child role |
|
|
27
|
-
| [`aria-required-parent`](#aria-required-parent) | Roles requiring a specific context role must be in that context |
|
|
26
|
+
| [`aria-required-children`](#aria-required-children) | Container roles must own at least one required child role | 1.3.1 | A | medium | moderate |
|
|
27
|
+
| [`aria-required-parent`](#aria-required-parent) | Roles requiring a specific context role must be in that context | 1.3.1 | A | medium | moderate |
|
|
28
28
|
| [`aria-role-name-present`](#aria-role-name-present) | ARIA roles that require an accessible name have one | 4.1.2 | A | high | serious |
|
|
29
29
|
| [`aria-roles-valid`](#aria-roles-valid) | role attribute must be a valid, non-abstract ARIA role | 4.1.2 | A | high | serious |
|
|
30
30
|
| [`aria-valid-attr`](#aria-valid-attr) | aria-* attributes must be real, defined ARIA attributes | 4.1.2 | A | high | serious |
|
|
@@ -50,6 +50,7 @@ See [`OUTPUT_SCHEMA.md`](./OUTPUT_SCHEMA.md) for what `type`/`confidence`/`sever
|
|
|
50
50
|
| [`form-control-single-label`](#form-control-single-label) | Form controls must not have multiple labels | 3.3.2 | A | high | moderate |
|
|
51
51
|
| [`html-lang-attr-present`](#html-lang-attr-present) | Page language is declared | 3.1.1 | A | high | serious |
|
|
52
52
|
| [`html-xml-lang-mismatch`](#html-xml-lang-mismatch) | lang and xml:lang must not disagree | 3.1.1 | A | high | serious |
|
|
53
|
+
| [`identical-iframes-same-purpose`](#identical-iframes-same-purpose) | Frames with the same name embed the same resource | 4.1.2 | A | medium | moderate |
|
|
53
54
|
| [`iframe-focusable-content`](#iframe-focusable-content) | Frames with tabindex="-1" must not contain focusable content | 2.1.1 | A | high | moderate |
|
|
54
55
|
| [`iframe-name-present`](#iframe-name-present) | Frames have an accessible name | 4.1.2 | A | high | serious |
|
|
55
56
|
| [`iframe-title-unique`](#iframe-title-unique) | Frame titles must be unique | 4.1.2 | A | high | moderate |
|
|
@@ -91,7 +92,7 @@ See [`OUTPUT_SCHEMA.md`](./OUTPUT_SCHEMA.md) for what `type`/`confidence`/`sever
|
|
|
91
92
|
| [`valid-lang`](#valid-lang) | Element lang attribute must be syntactically valid | 3.1.2 | AA | high | moderate |
|
|
92
93
|
| [`video-poster-text-alternative-present`](#video-poster-text-alternative-present) | <video> poster must have a text alternative | 1.1.1 | A | medium | serious |
|
|
93
94
|
|
|
94
|
-
## Manual rules (
|
|
95
|
+
## Manual rules (54), advisory, capped at `cantTell`
|
|
95
96
|
|
|
96
97
|
| Rule ID | Title | WCAG SC | Level | Confidence | Default severity |
|
|
97
98
|
|---|---|---|---|---|---|
|
|
@@ -120,6 +121,7 @@ See [`OUTPUT_SCHEMA.md`](./OUTPUT_SCHEMA.md) for what `type`/`confidence`/`sever
|
|
|
120
121
|
| [`input-image-alt-quality`](#input-image-alt-quality) | <input type="image"> alt text must be appropriate (manual review) | 1.1.1 | A | medium | minor |
|
|
121
122
|
| [`label-title-only`](#label-title-only) | Form controls should not use title as their only label | — | — | medium | minor |
|
|
122
123
|
| [`landmark-banner-is-top-level`](#landmark-banner-is-top-level) | Banner landmark must be top-level | — | — | medium | minor |
|
|
124
|
+
| [`landmark-complementary-is-top-level`](#landmark-complementary-is-top-level) | Complementary landmark must be top-level | — | — | medium | minor |
|
|
123
125
|
| [`landmark-contentinfo-is-top-level`](#landmark-contentinfo-is-top-level) | Contentinfo landmark must be top-level | — | — | medium | minor |
|
|
124
126
|
| [`landmark-main-is-top-level`](#landmark-main-is-top-level) | Main landmark must be top-level | — | — | medium | minor |
|
|
125
127
|
| [`landmark-no-duplicate-banner`](#landmark-no-duplicate-banner) | Page must not have more than one banner landmark | — | — | medium | minor |
|
|
@@ -137,6 +139,7 @@ See [`OUTPUT_SCHEMA.md`](./OUTPUT_SCHEMA.md) for what `type`/`confidence`/`sever
|
|
|
137
139
|
| [`p-as-heading`](#p-as-heading) | A <p> styled to look like a heading should probably be a real heading | 1.3.1 | A | low | minor |
|
|
138
140
|
| [`page-has-heading-one`](#page-has-heading-one) | Page should have a level-one heading | — | — | medium | minor |
|
|
139
141
|
| [`page-title-patterns`](#page-title-patterns) | Page title patterns that may be insufficiently descriptive | 2.4.2 | A | medium | minor |
|
|
142
|
+
| [`password-paste-enabled`](#password-paste-enabled) | Authentication fields must not block pasting | 3.3.8 | AA | medium | serious |
|
|
140
143
|
| [`presentation-role-conflict`](#presentation-role-conflict) | Presentational role must not conflict with a global ARIA attribute or focusability | — | — | medium | minor |
|
|
141
144
|
| [`region`](#region) | Page content should be inside a landmark region | — | — | medium | minor |
|
|
142
145
|
| [`scope-attr-valid`](#scope-attr-valid) | scope attribute must have a valid value | — | — | medium | minor |
|
|
@@ -148,7 +151,7 @@ See [`OUTPUT_SCHEMA.md`](./OUTPUT_SCHEMA.md) for what `type`/`confidence`/`sever
|
|
|
148
151
|
| [`table-fake-caption`](#table-fake-caption) | A table's first row should not stand in for a real <caption> | 1.3.1 | A | low | minor |
|
|
149
152
|
| [`video-caption`](#video-caption) | Prerecorded video should provide a captions track | 1.2.2 | A | low | moderate |
|
|
150
153
|
|
|
151
|
-
## Composite (WCAG-SC rollup) rules (
|
|
154
|
+
## Composite (WCAG-SC rollup) rules (34)
|
|
152
155
|
|
|
153
156
|
Composite rules aren't individually authored. They're generated rollups over the atomic rules above, one per WCAG Success Criterion with automatable coverage. See [`WCAG_CONFORMANCE.md`](./WCAG_CONFORMANCE.md) for rollup semantics.
|
|
154
157
|
|
|
@@ -157,7 +160,7 @@ Composite rules aren't individually authored. They're generated rollups over the
|
|
|
157
160
|
| `wcag-1.1.1-non-text-content` | Non-text content: text alternatives | Rollup of checks ensuring non-text content has an appropriate text alternative. | 1.1.1 | A | 22 |
|
|
158
161
|
| `wcag-1.2.1-audio-only-video-only-prerecorded` | Audio-only and video-only (prerecorded): transcript | Rollup of checks for transcript availability for prerecorded audio-only/video-only media. | 1.2.1 | A | 1 |
|
|
159
162
|
| `wcag-1.2.2-captions-prerecorded` | Captions (Prerecorded) | Rollup of checks for captions-track evidence on prerecorded video. | 1.2.2 | A | 1 |
|
|
160
|
-
| `wcag-1.3.1-info-and-relationships` | Info and Relationships | Rollup of checks ensuring information, structure, and relationships conveyed through presentation are programmatically determinable. | 1.3.1 | A |
|
|
163
|
+
| `wcag-1.3.1-info-and-relationships` | Info and Relationships | Rollup of checks ensuring information, structure, and relationships conveyed through presentation are programmatically determinable. | 1.3.1 | A | 14 |
|
|
161
164
|
| `wcag-1.3.4-orientation` | Orientation | Rollup of checks ensuring content does not restrict its view to a single display orientation. | 1.3.4 | AA | 1 |
|
|
162
165
|
| `wcag-1.3.5-identify-input-purpose` | Identify Input Purpose | Rollup of checks ensuring the autocomplete attribute correctly identifies input purpose. | 1.3.5 | AA | 1 |
|
|
163
166
|
| `wcag-1.4.1-use-of-color` | Use of Color | Rollup of checks ensuring color is not used as the only visual means of conveying information. | 1.4.1 | A | 1 |
|
|
@@ -184,9 +187,10 @@ Composite rules aren't individually authored. They're generated rollups over the
|
|
|
184
187
|
| `wcag-3.1.2-language-of-parts` | Language of Parts | Rollup of checks ensuring elements whose language differs from the page default declare it correctly. | 3.1.2 | AA | 1 |
|
|
185
188
|
| `wcag-3.2.5-change-on-request` | Change on Request | Rollup of checks ensuring context changes only happen at the user's request (AAA). | 3.2.5 | AAA | 1 |
|
|
186
189
|
| `wcag-3.3.2-labels-or-instructions` | Labels or Instructions | Rollup of checks ensuring form controls have unambiguous labeling. | 3.3.2 | A | 2 |
|
|
190
|
+
| `wcag-3.3.8-accessible-authentication-minimum` | Accessible Authentication (Minimum) | Rollup of checks ensuring an authentication step leaves the mechanisms that help a user through it in place. | 3.3.8 | AA | 1 |
|
|
187
191
|
| `wcag-4.1.1-parsing` | Parsing | Rollup of checks ensuring id values are unique. WCAG 2.0/2.1 only: SC 4.1.1 was removed in WCAG 2.2, so this composite carries the wcag22-removed tag. | 4.1.1 | A | 1 |
|
|
188
|
-
| `wcag-4.1.2-aria-validity` | Name, role, value: ARIA validity | Rollup of checks that ARIA role and attribute usage conforms to the WAI-ARIA specification (valid roles, valid attributes, valid values, required attributes
|
|
189
|
-
| `wcag-4.1.2-name` | Name, role, value: accessible name | Rollup of checks that common interactive elements expose a non-empty accessible name. | 4.1.2 | A |
|
|
192
|
+
| `wcag-4.1.2-aria-validity` | Name, role, value: ARIA validity | Rollup of checks that ARIA role and attribute usage conforms to the WAI-ARIA specification (valid roles, valid attributes, valid values, required attributes, unique ARIA-referenced ids). | 4.1.2 | A | 13 |
|
|
193
|
+
| `wcag-4.1.2-name` | Name, role, value: accessible name | Rollup of checks that common interactive elements expose a non-empty accessible name. | 4.1.2 | A | 24 |
|
|
190
194
|
|
|
191
195
|
## Rule reference
|
|
192
196
|
|
|
@@ -256,19 +260,19 @@ Checks that every recognized aria-* attribute present on an element with an expl
|
|
|
256
260
|
|
|
257
261
|
**Explicit role must be permitted for its host element**
|
|
258
262
|
|
|
259
|
-
automatic · WCAG
|
|
263
|
+
automatic · no formal WCAG SC mapping · confidence high · default severity moderate
|
|
260
264
|
|
|
261
265
|
Checks that an explicit role="" attribute is one of the roles the ARIA-in-HTML specification permits for the host element (e.g. role="tab" is not permitted on <nav>).
|
|
262
266
|
|
|
263
267
|
**Applies to.** Applies to elements with an explicit, valid, non-abstract role, where the host element/attribute combination has an asserted permitted-roles constraint in the ARIA-in-HTML table (src/core/aria-helpers.js ALLOWED_ROLES_BY_ELEMENT).
|
|
264
268
|
|
|
265
|
-
**Expectation.** The explicit role is one of the roles the ARIA-in-HTML specification permits for that host element.
|
|
269
|
+
**Expectation.** The explicit role is one of the roles the ARIA-in-HTML specification permits for that host element. Reported at CANTTELL rather than FAIL: ARIA-in-HTML's permitted-roles table is an author conformance requirement with no ACT rule and no WCAG mapping in any source. The role the author asked for is still the role assistive technology exposes, so whether the combination harms anyone depends on the widget, not on the table.
|
|
266
270
|
|
|
267
271
|
### `aria-braille-equivalent`
|
|
268
272
|
|
|
269
273
|
**aria-braillelabel/aria-brailleroledescription must have a non-braille equivalent**
|
|
270
274
|
|
|
271
|
-
automatic · WCAG 4.1.2 (A) · confidence high · default severity
|
|
275
|
+
automatic · WCAG 4.1.2 (A) · confidence high · default severity moderate
|
|
272
276
|
|
|
273
277
|
Checks that elements using aria-braillelabel also have a regular accessible name, and elements using aria-brailleroledescription also have aria-roledescription.
|
|
274
278
|
|
|
@@ -281,7 +285,7 @@ Per the ARIA specification, `aria-braillelabel` is a Braille-specific SUPPLEMENT
|
|
|
281
285
|
- a non-empty accessible name from a non-braille mechanism, if it declares `aria-braillelabel`;
|
|
282
286
|
- a non-empty `aria-roledescription`, if it declares `aria-brailleroledescription`.
|
|
283
287
|
|
|
284
|
-
Using either braille-specific attribute as the ONLY naming mechanism leaves non-braille assistive technology (most screen readers, voice control, etc.) with no accessible name/role description at all.
|
|
288
|
+
Using either braille-specific attribute as the ONLY naming mechanism leaves non-braille assistive technology (most screen readers, voice control, etc.) with no accessible name/role description at all. Reported at CANTTELL rather than FAIL: aria-brailleroledescription without aria-roledescription reaches no user at all, and a missing accessible name is the naming rules' decision for the roles that require one. The braille attribute being unpaired is worth surfacing, but it is not itself a criterion failing.
|
|
285
289
|
|
|
286
290
|
### `aria-checked-state-mismatch`
|
|
287
291
|
|
|
@@ -299,13 +303,13 @@ Flags a native <input type="checkbox">/<input type="radio"> whose ex
|
|
|
299
303
|
|
|
300
304
|
**aria-errormessage requires aria-invalid to be set to a non-false value**
|
|
301
305
|
|
|
302
|
-
automatic · WCAG 4.1.2 (A) · confidence high · default severity
|
|
306
|
+
automatic · WCAG 4.1.2 (A) · confidence high · default severity moderate
|
|
303
307
|
|
|
304
308
|
Checks that elements with aria-errormessage also have aria-invalid set to "true", "grammar", or "spelling"; otherwise the error message is dropped from the accessibility tree.
|
|
305
309
|
|
|
306
310
|
**Applies to.** Elements with a non-empty `aria-errormessage` attribute.
|
|
307
311
|
|
|
308
|
-
**Expectation.** Per the ARIA specification, `aria-errormessage` is only exposed to assistive technology when `aria-invalid` is present with a value other than `"false"` (i.e. `"true"`, `"grammar"`, or `"spelling"`). An element with `aria-errormessage` but `aria-invalid` absent or `"false"` silently drops the error message from the accessibility tree, authors almost always intend it to be exposed.
|
|
312
|
+
**Expectation.** Per the ARIA specification, `aria-errormessage` is only exposed to assistive technology when `aria-invalid` is present with a value other than `"false"` (i.e. `"true"`, `"grammar"`, or `"spelling"`). An element with `aria-errormessage` but `aria-invalid` absent or `"false"` silently drops the error message from the accessibility tree, authors almost always intend it to be exposed. Reported at CANTTELL rather than FAIL: aria-errormessage is only exposed once aria-invalid is set, so the reference is currently inert. Whether that costs the user anything depends on whether the message is conveyed some other way (visible text next to the field, aria-describedby), which static markup does not settle.
|
|
309
313
|
|
|
310
314
|
### `aria-deprecated-role`
|
|
311
315
|
|
|
@@ -372,7 +376,7 @@ Checks that aria-label/aria-labelledby are not present on WAI-ARIA roles whose s
|
|
|
372
376
|
|
|
373
377
|
**Container roles must not own a child with a disallowed role**
|
|
374
378
|
|
|
375
|
-
automatic · WCAG
|
|
379
|
+
automatic · WCAG 1.3.1 (A) · confidence medium · default severity moderate
|
|
376
380
|
|
|
377
381
|
Checks that every accessible-tree-owned child of a container role (list, listbox, menu, menubar, radiogroup, rowgroup, table, grid, treegrid, tablist, tree, row) has one of that role's allowed owned roles.
|
|
378
382
|
|
|
@@ -390,25 +394,30 @@ Checks that elements with an explicit role carry every unambiguous, context-inde
|
|
|
390
394
|
|
|
391
395
|
**Applies to.** Applies to elements with an explicit, valid, non-abstract role that is also one of the small set of roles with a documented, context- independent required state/property (checkbox, combobox, heading, menuitemcheckbox, menuitemradio, meter, radio, scrollbar, separator, slider, switch) -- except when that explicit role is identical to the element's own native/implicit role (ACT 4e8ab6: e.g. <input type="checkbox" role="checkbox">, which is exempt because the native control's own state exposure already covers it; no aria-checked is required. helpers.aria.getNativeRoleForElement resolves this).
|
|
392
396
|
|
|
393
|
-
**Expectation.**
|
|
397
|
+
**Expectation.**
|
|
398
|
+
|
|
399
|
+
Every required state/property for that role is present and non-empty. Graded by whether ARIA supplies a stand-in for the missing attribute:
|
|
400
|
+
|
|
401
|
+
- FAIL where it does not, so the state is simply not exposed (aria-checked on checkbox/radio/switch/menuitemcheckbox/menuitemradio, aria-valuenow on slider/scrollbar/meter and on a focusable separator).
|
|
402
|
+
- CANTTELL where ARIA defines an implicit value the role falls back to (aria-expanded on combobox, aria-level on heading), so the role still exposes a value and only the author knows whether it is the right one.
|
|
394
403
|
|
|
395
404
|
### `aria-required-children`
|
|
396
405
|
|
|
397
406
|
**Container roles must own at least one required child role**
|
|
398
407
|
|
|
399
|
-
automatic · WCAG
|
|
408
|
+
automatic · WCAG 1.3.1 (A) · confidence medium · default severity moderate
|
|
400
409
|
|
|
401
410
|
Checks that container roles with a documented "required owned elements" entry (list, listbox, menu, radiogroup, table, grid, tablist, tree, row, ...) contain at least one descendant or aria-owns-referenced element with an acceptable owned role.
|
|
402
411
|
|
|
403
412
|
**Applies to.** Applies to elements with an explicit, valid, non-abstract role that is also one of the container roles with a documented "required owned elements" entry (list, listbox, menu, menubar, radiogroup, rowgroup, table, grid, treegrid, tablist, tree, row).
|
|
404
413
|
|
|
405
|
-
**Expectation.** At least one descendant, or one aria-owns-referenced element, has one of the acceptable owned roles for that container role.
|
|
414
|
+
**Expectation.** At least one descendant, or one aria-owns-referenced element, has one of the acceptable owned roles for that container role. Reported at CANTTELL, never FAIL: this rule asks only whether the required content is PRESENT, and a container that owns nothing conveys nothing false -- an empty role="list" is announced as a list with no items, which is what it is. Whether the content a container does own is VALID is aria-prohibited-children's decision, and that rule still fails, so a genuinely misdescribed structure (a role="button" among list items, a tablist of plain buttons) is caught with the same strength as before. The native-HTML equivalents already work this way: nothing in this ruleset fails an empty <ul>, and list-children-valid judges only the children that exist.
|
|
406
415
|
|
|
407
416
|
### `aria-required-parent`
|
|
408
417
|
|
|
409
418
|
**Roles requiring a specific context role must be in that context**
|
|
410
419
|
|
|
411
|
-
automatic · WCAG
|
|
420
|
+
automatic · WCAG 1.3.1 (A) · confidence medium · default severity moderate
|
|
412
421
|
|
|
413
422
|
Checks that roles with a documented "required context role" entry (listitem, option, tab, treeitem, row, cell, ...) have an ancestor or aria-owns owner with an acceptable context role.
|
|
414
423
|
|
|
@@ -438,7 +447,12 @@ Checks that an explicit role="" attribute resolves to a real, non-abstract WAI-A
|
|
|
438
447
|
|
|
439
448
|
**Applies to.** Applies to any element with a non-empty role="" attribute in the composed DOM.
|
|
440
449
|
|
|
441
|
-
**Expectation.**
|
|
450
|
+
**Expectation.**
|
|
451
|
+
|
|
452
|
+
At least one role token names a concrete, non-abstract ARIA role. Graded by what the element falls back to when none does:
|
|
453
|
+
|
|
454
|
+
- FAIL on a roleless host (div, span, custom element), which is left exposed as generic, so the role the author meant reaches no one.
|
|
455
|
+
- CANTTELL where the element has a native role (a <button>, <nav>, <a href>), which the accessibility tree keeps using. ACT 674b10 lists 4.1.2 as a secondary requirement only, "satisfied through the implicit role," so the bad token is worth reporting but is not itself the criterion failing.
|
|
442
456
|
|
|
443
457
|
### `aria-text`
|
|
444
458
|
|
|
@@ -462,7 +476,7 @@ Checks that every aria-* attribute name present in the DOM is a real attribute d
|
|
|
462
476
|
|
|
463
477
|
**Applies to.** Applies to any element in the composed DOM that carries at least one attribute whose name starts with "aria-".
|
|
464
478
|
|
|
465
|
-
**Expectation.** Each aria-* attribute name is a real attribute defined by the WAI-ARIA specification (catches typos / made-up attribute names, which are silently ignored by assistive technology and therefore a real, deterministic defect).
|
|
479
|
+
**Expectation.** Each aria-* attribute name is a real attribute defined by the WAI-ARIA specification (catches typos / made-up attribute names, which are silently ignored by assistive technology and therefore a real, deterministic defect). Reported at CANTTELL rather than FAIL: an aria-* attribute the spec does not define is inert, so nothing about the element's exposed name, role or value changes because it is there. Where the author meant a real attribute and the element ends up without a name, that absence is the naming rules' decision, not this one's. ACT 5f99a7 maps 1.3.1/4.1.2 as secondary requirements, "less strict" than the rule itself.
|
|
466
480
|
|
|
467
481
|
### `aria-valid-attr-value`
|
|
468
482
|
|
|
@@ -908,6 +922,18 @@ Checks that the <html> element's lang and xml:lang attributes declare the
|
|
|
908
922
|
|
|
909
923
|
**Expectation.** The primary language subtag (the part before the first "-") of lang and xml:lang match, case-insensitively. When both attributes are present but declare different languages, assistive technology and user agents may resolve the page's language inconsistently.
|
|
910
924
|
|
|
925
|
+
### `identical-iframes-same-purpose`
|
|
926
|
+
|
|
927
|
+
**Frames with the same name embed the same resource**
|
|
928
|
+
|
|
929
|
+
automatic · WCAG 4.1.2 (A) · confidence medium · default severity moderate
|
|
930
|
+
|
|
931
|
+
Checks that <iframe>/<frame> elements sharing an accessible name embed the same resource, since one name can only describe one resource.
|
|
932
|
+
|
|
933
|
+
**Applies to.** Applies to each set of two or more <iframe>/<frame> elements that are included in the accessibility tree and share the same non-empty accessible name, compared with whitespace collapsed. A frame named only by a mechanism that names nothing, or hidden from the accessibility tree, is not part of a set; a set needs two surviving members to exist at all.
|
|
934
|
+
|
|
935
|
+
**Expectation.** Every frame in a set resolves to the same resource. A shared name describes one resource, so two frames answering to it must embed the same one.
|
|
936
|
+
|
|
911
937
|
### `identical-links-same-purpose`
|
|
912
938
|
|
|
913
939
|
**Links with the same accessible name should lead to the same destination**
|
|
@@ -1076,6 +1102,18 @@ Checks that the banner landmark (role="banner" or a non-nested <header>) i
|
|
|
1076
1102
|
|
|
1077
1103
|
**Expectation.** No banner candidate has an ancestor that is itself any landmark region. A banner nested inside another landmark is not a top-level, whole-page banner and confuses landmark-based navigation for assistive technology users.
|
|
1078
1104
|
|
|
1105
|
+
### `landmark-complementary-is-top-level`
|
|
1106
|
+
|
|
1107
|
+
**Complementary landmark must be top-level**
|
|
1108
|
+
|
|
1109
|
+
manual · no formal WCAG SC mapping · confidence medium · default severity minor
|
|
1110
|
+
|
|
1111
|
+
Checks that the complementary landmark (role="complementary" or an <aside> that keeps its implicit role) is not nested inside another landmark region.
|
|
1112
|
+
|
|
1113
|
+
**Applies to.** Applies whenever the page contains at least one element carrying the complementary role: explicit role="complementary", or an <aside> that keeps its implicit role (see implementation notes on when it does not).
|
|
1114
|
+
|
|
1115
|
+
**Expectation.** No complementary candidate has an ancestor that is itself a landmark region. Complementary content supports the main content of the page and sits beside it; nested inside another landmark it is a section of that landmark instead, which is not what landmark navigation announces.
|
|
1116
|
+
|
|
1079
1117
|
### `landmark-contentinfo-is-top-level`
|
|
1080
1118
|
|
|
1081
1119
|
**Contentinfo landmark must be top-level**
|
|
@@ -1463,6 +1501,18 @@ Checks that the page includes a non-empty <title> element.
|
|
|
1463
1501
|
|
|
1464
1502
|
**Expectation.** The document has a <title> element, and document.title with whitespace collapsed is non-empty. The element is looked for anywhere in the document, not only inside <head>: a <title> the parser leaves outside <head> is still the document title in every browser. Whether that title describes the page is page-title-patterns' question.
|
|
1465
1503
|
|
|
1504
|
+
### `password-paste-enabled`
|
|
1505
|
+
|
|
1506
|
+
**Authentication fields must not block pasting**
|
|
1507
|
+
|
|
1508
|
+
manual · WCAG 3.3.8 (AA) · confidence medium · default severity serious
|
|
1509
|
+
|
|
1510
|
+
Checks that a password or one-time-code field carries no inline paste handler that cancels the paste, which would remove the password manager or clipboard that WCAG 3.3.8 relies on as the assisting mechanism.
|
|
1511
|
+
|
|
1512
|
+
**Applies to.** Applies to any control whose autocomplete token is current-password, new-password or one-time-code, and to <input type="password"> unless its autocomplete names another purpose. A disabled or readonly field takes no input to block, and one outside the accessibility tree is not being asked for, so neither is in scope.
|
|
1513
|
+
|
|
1514
|
+
**Expectation.** A reviewer confirms the field can still be pasted into. Remembering a password is a cognitive function test, and 3.3.8 asks for a mechanism that helps the user through one; a password manager, or the clipboard for a one-time code, is that mechanism.
|
|
1515
|
+
|
|
1466
1516
|
### `presentation-role-conflict`
|
|
1467
1517
|
|
|
1468
1518
|
**Presentational role must not conflict with a global ARIA attribute or focusability**
|
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
# RULE_HELPERS.md — `ctx.helpers` Reference (Canonical, repo-derived)
|
|
2
|
+
|
|
3
|
+
This is the full reference for `ctx.helpers`, the object every rule's `runInPage(ctx)`
|
|
4
|
+
receives (`const { helpers } = ctx`, per [`RULE_AUTHORING.md`](./RULE_AUTHORING.md) §6).
|
|
5
|
+
It exists because that doc's own helpers section only calls out the dozen or so most
|
|
6
|
+
common ones — the underlying object (`createDomHelpers()` in `src/core/dom-helpers.js`)
|
|
7
|
+
exports around 35, and several of them replace logic a new rule would otherwise
|
|
8
|
+
duplicate (often incorrectly — see the naming helpers below).
|
|
9
|
+
|
|
10
|
+
Read [`RULE_AUTHORING.md`](./RULE_AUTHORING.md) first for the rule contract itself
|
|
11
|
+
(module shape, `runInPage` constraints, occurrence reporting). This doc only covers
|
|
12
|
+
what each helper does and when to reach for it.
|
|
13
|
+
|
|
14
|
+
All helpers below are called as `helpers.<name>(...)` from inside `runInPage`. None of
|
|
15
|
+
them may be assigned to a module-scope variable and closed over — same
|
|
16
|
+
serialization constraint as everything else in `runInPage` (§1 of `RULE_AUTHORING.md`).
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## 1) Query & traversal
|
|
21
|
+
|
|
22
|
+
### `queryAll(selector)` → `Element[]`
|
|
23
|
+
Plain `querySelectorAll(selector)` across the resolved context root(s), deduped, with
|
|
24
|
+
self-match included (a root element matching `selector` itself is returned, which
|
|
25
|
+
`querySelectorAll` alone never does). Light DOM only.
|
|
26
|
+
|
|
27
|
+
### `queryAllDeep(selector)` → `Element[]`
|
|
28
|
+
Same as `queryAll`, but also descends into open shadow roots (BFS over discovered
|
|
29
|
+
`shadowRoot`s). Ignores `includeShadowDom`/hidden-content policy — it's the raw
|
|
30
|
+
traversal `queryAllSmart` builds on.
|
|
31
|
+
|
|
32
|
+
### `queryAllSmart(selector)` → `Element[]`
|
|
33
|
+
**The one almost every rule should use.** Honors `engineOptions.includeShadowDom`
|
|
34
|
+
(shadow-aware by default), applies the default hidden-content policy (filters out
|
|
35
|
+
`display:none`/`hidden`/etc. unless `includeHiddenElements:true`), and applies any
|
|
36
|
+
rule-scoped `excludeSelectors`. See `RULE_AUTHORING.md` §6.1 — write rules assuming
|
|
37
|
+
open shadow roots are in scope and let this helper honor the caller's choice.
|
|
38
|
+
|
|
39
|
+
```js
|
|
40
|
+
const nodes = helpers.queryAllSmart ? helpers.queryAllSmart('img') : helpers.queryAll('img');
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### `composedParent(node)` → `Node | null`
|
|
44
|
+
One step up the *flat tree*: `assignedSlot` first (a slotted node's rendered parent is
|
|
45
|
+
its slot, not its light-DOM `parentNode`), then `parentNode`, then `.host` once you're
|
|
46
|
+
at a `ShadowRoot`. Use this instead of `parentNode`/`closest` for any ancestor walk that
|
|
47
|
+
must work correctly across shadow boundaries and slots.
|
|
48
|
+
|
|
49
|
+
### `buildSimpleSelector(el, fallbackTag)` → `string`
|
|
50
|
+
A short, non-unique selector for an element: `#id`, else a `data-testid`/`data-test`/
|
|
51
|
+
`data-cy`/`data-qa` attribute selector, else `tag[name="..."]`, else just the tag name.
|
|
52
|
+
Cheap; not guaranteed unique. Prefer `reportOccurrence` (§6 below) over building
|
|
53
|
+
selectors by hand — see the perf note there.
|
|
54
|
+
|
|
55
|
+
### `buildSelector(el)` → `string`
|
|
56
|
+
The engine's real, best-effort-unique CSS selector builder (cached per element per
|
|
57
|
+
run). Used internally by `reportOccurrence`'s finalization; rules generally don't need
|
|
58
|
+
to call this directly.
|
|
59
|
+
|
|
60
|
+
### `getOuterHtmlSnippet(el)` → `string`
|
|
61
|
+
`el.outerHTML`, truncated to 2000 characters (with a trailing `…`) and cached per
|
|
62
|
+
element per run. `reportOccurrence` (§6 below) already fills in an occurrence's `html`
|
|
63
|
+
from the reported element, so most rules never call this directly — it's for the rare
|
|
64
|
+
case where a rule needs the HTML snippet itself, not just an occurrence carrying it.
|
|
65
|
+
|
|
66
|
+
### `isExcluded(el)` → `boolean`
|
|
67
|
+
Whether `el` matches the currently effective `excludeSelectors` (global config ∪ the
|
|
68
|
+
active rule's own `engineOptions.rules[ruleId].excludeSelectors`), via `closest()`.
|
|
69
|
+
`queryAllSmart` already applies this filtering for you; reach for `isExcluded` directly
|
|
70
|
+
only if a rule walks the DOM some other way (e.g. following `composedParent`) and still
|
|
71
|
+
needs to respect exclusions on nodes found off that path.
|
|
72
|
+
|
|
73
|
+
### `buildStructuralPath(node, selector)` → `number[] | null`
|
|
74
|
+
Sibling-index path from the document root to `node` (or, given only a `selector`,
|
|
75
|
+
re-resolves the element first — the same fallback `reportOccurrence` triggers, and the
|
|
76
|
+
same one `perfStats.counters['structuralPath.selectorFallback']` counts, per
|
|
77
|
+
`RULE_AUTHORING.md` §4.3). Rules don't normally call this either — it's what backs
|
|
78
|
+
`selector`/`structuralPath` finalization for reported occurrences.
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## 2) Eligibility & visibility
|
|
83
|
+
|
|
84
|
+
These answer "is this node in scope" from different angles. They are easy to reach for
|
|
85
|
+
the wrong one, so the distinctions matter:
|
|
86
|
+
|
|
87
|
+
### `isAccTreeEligible(node)` → `{ eligible, reasons[] }`
|
|
88
|
+
Whether a node is eligible for the accessibility tree, per an ordered set of checks
|
|
89
|
+
(display/visibility, `hidden`, `inert`, closed `<details>`, template content, etc.).
|
|
90
|
+
Deliberately keeps a focusable-but-`aria-hidden` element *eligible* — see the header
|
|
91
|
+
comment on `isIncludedInAccessibilityTree` below for why.
|
|
92
|
+
|
|
93
|
+
### `isIncludedInAccessibilityTree(el)` → `boolean`
|
|
94
|
+
The narrower question most accessible-name rules actually want: `isAccTreeEligible`,
|
|
95
|
+
minus anything eligible only because of an `ariaHiddenOverridden*` reason. ACT scopes
|
|
96
|
+
name-computation rules (button-name, link-name, etc.) to elements genuinely included in
|
|
97
|
+
the accessibility tree; a focusable element hidden by `aria-hidden` is a real
|
|
98
|
+
issue, but it's `aria-hidden-focus`'s issue (WCAG 4.1.2), not a naming rule's — so
|
|
99
|
+
naming rules should check this, not `isAccTreeEligible`, to avoid double-flagging the
|
|
100
|
+
same element under the wrong rule.
|
|
101
|
+
|
|
102
|
+
### `isDomVisibleEligible(node, ctx, opts)` → `{ eligible, reasons[], metrics }`
|
|
103
|
+
Style/geometry-only visibility (not accessibility-tree membership): `display`,
|
|
104
|
+
`visibility`, size/position, opacity, etc. `opts.visibilityMode` (`'styleOnly'` |
|
|
105
|
+
`'styleAndGeometry'`), `opts.disableGeometry`, `opts.ignoreOpacity` tune what's checked.
|
|
106
|
+
Use when a rule cares about visual rendering specifically, not AT exposure.
|
|
107
|
+
|
|
108
|
+
### `getEligibilityInfo(node, ctx, opts)` → `{ eligible, reasons[], targetSet, accEligible }`
|
|
109
|
+
A single wrapper choosing between the two above via `opts.targetSet` (`'acc'` |
|
|
110
|
+
`'dom'`, default `'dom'`). **This is also the shape `RULE_AUTHORING.md` §7 requires every
|
|
111
|
+
occurrence to carry** as `data.visibilityFilter` — call this once per element and pass
|
|
112
|
+
its result straight through.
|
|
113
|
+
|
|
114
|
+
### `getVisibilityHintsInfo(el, ctx, opts)` → `{ hints[], metrics, flags[] }`
|
|
115
|
+
Style-only visibility *hints* for triage/diagnostics (`opacityZero`, `clipped`, etc.) —
|
|
116
|
+
explicitly does **not** decide eligibility; a rule's own logic still owns the outcome.
|
|
117
|
+
|
|
118
|
+
### `isWholeDocumentScope()` → `boolean`
|
|
119
|
+
`true` unless `engineOptions.fragment: true` was set, or `contextSelector` scoped the
|
|
120
|
+
run narrower than the whole document. Required for any rule checking a page-wide,
|
|
121
|
+
one-per-document property (`<title>`, `<html lang>`, landmark structure) — gate it with
|
|
122
|
+
an `applicability(ctx)` export using this, per `RULE_AUTHORING.md` §4.2/§11.2, or the
|
|
123
|
+
rule will wrongly fault a scoped subtree for lacking something it was never meant to
|
|
124
|
+
have.
|
|
125
|
+
|
|
126
|
+
### `hasTruncatedAncestorWalk` (internal)
|
|
127
|
+
Backs confidence scoring during result normalization when a 200-step ancestor walk
|
|
128
|
+
didn't reach the root. Not something a rule calls directly.
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## 3) Accessible naming & labeling
|
|
133
|
+
|
|
134
|
+
Naming is layered — each function below builds on the ones above it — and several exist
|
|
135
|
+
specifically to replace a naive reimplementation that gets an edge case wrong. Reach for
|
|
136
|
+
the highest-level one that answers your actual question before dropping to a primitive.
|
|
137
|
+
|
|
138
|
+
### `getAriaLabelInfo(el)` → `{ present, value, mechanism, flags[] }`
|
|
139
|
+
Just `aria-label`, trimmed, presence-checked.
|
|
140
|
+
|
|
141
|
+
### `getAriaLabelledByInfo(el, ctx, opts)` → `{ present, value, mechanism, refsCount, missing[], flags[] }`
|
|
142
|
+
Resolves `aria-labelledby` through `getTextFromIdRefs` (recursive, accname-aligned — see
|
|
143
|
+
§4 below), not raw `textContent`.
|
|
144
|
+
|
|
145
|
+
### `getAriaNameInfo(el, ctx, opts)` → `{ present, value, mechanism, flags[] }`
|
|
146
|
+
ARIA-only name with correct precedence: `aria-labelledby` (if it resolves non-empty)
|
|
147
|
+
wins over `aria-label`. Use this rather than checking the two attributes yourself when
|
|
148
|
+
you specifically want "does ARIA name this," excluding native `<label>`/content/title.
|
|
149
|
+
|
|
150
|
+
### `getLandmarkNameInfo(el, ctx)` → `{ present, value, mechanism, flags[] }`
|
|
151
|
+
Landmark-role naming (`nav`/`main`/`region`/`banner`/`contentinfo`, etc.): ARIA name,
|
|
152
|
+
then `title` — landmark roles don't get a name from content, and `title` must be
|
|
153
|
+
included or two landmarks distinguished only by `title` both read as unnamed and get
|
|
154
|
+
flagged as duplicates. Shared by all 7 landmark rule files; use this rather than
|
|
155
|
+
reimplementing landmark naming in a new landmark rule.
|
|
156
|
+
|
|
157
|
+
### `getAccessibleNameInfo(el, ctx, opts)` → `{ present, value, mechanism, flags[] }`
|
|
158
|
+
The general-purpose accessible name: ARIA name → native `<label>` association → `alt`
|
|
159
|
+
(image-like elements) → `title` (flagged `title-used` — see the policy note in the
|
|
160
|
+
source; `title` is accepted per spec but is a weak mechanism in practice). This is what
|
|
161
|
+
almost every naming rule should call.
|
|
162
|
+
|
|
163
|
+
### `getAccessibleDescriptionInfo(el, ctx, opts)` → `{ present, value, mechanism, flags[] }`
|
|
164
|
+
`aria-describedby` (via `getTextFromIdRefs`), then optionally `title` if
|
|
165
|
+
`opts.allowTitle === true`.
|
|
166
|
+
|
|
167
|
+
### `getTextAlternativeInfo(el, ctx, opts)` → `{ present, value, mechanism, requiredMechanism, flags[] }`
|
|
168
|
+
Mechanism-aware text alternative for elements with a *specific required* mechanism:
|
|
169
|
+
`alt` for `img`/`area`/`input[type=image]` (missing `alt` is flagged even when an
|
|
170
|
+
accessible name exists elsewhere — that's still a real alt-text violation), fallback
|
|
171
|
+
content or ARIA/`title` for `<canvas>`. Use this for alt-text-shaped rules, not
|
|
172
|
+
`getAccessibleNameInfo`, when the rule cares which mechanism was used, not just whether
|
|
173
|
+
a name exists.
|
|
174
|
+
|
|
175
|
+
### `getContentNameInfo(el, ctx, opts)` → `{ present, value, mechanism, flags[] }`
|
|
176
|
+
Recursive "name from content" (accname step 2F): walks children using each child's
|
|
177
|
+
*own* accessible name (not just literal text), so `<a href="…"><img alt="Company
|
|
178
|
+
Name"></a>` and `<button><span aria-label="Close"></span></button>` both name correctly.
|
|
179
|
+
A plain `TreeWalker(SHOW_TEXT)` walk misses both.
|
|
180
|
+
|
|
181
|
+
### `getAssociatedLabelElements(el)` → `Element[]`
|
|
182
|
+
Real `<label>` element(s) associated with `el` — a `<label for="id">` pointing at it,
|
|
183
|
+
plus a wrapping `<label>` whose first labelable descendant it is. **Does not call the
|
|
184
|
+
native `.labels`/`.control` API** — in this project's supported jsdom runtime,
|
|
185
|
+
`.labels` is an expensive whole-document walk per element (`.control` resolution is
|
|
186
|
+
another one), which used to dominate whole-engine runtime on form-heavy pages. Use this
|
|
187
|
+
whenever a rule needs the actual label element(s), not just a yes/no.
|
|
188
|
+
|
|
189
|
+
### `labelContributesAccessibleName(labelEl)` → `boolean`
|
|
190
|
+
Whether a `<label>` element itself carries text that would name its control: own
|
|
191
|
+
ARIA name, else rendered content (`getContentNameInfo`, so `aria-hidden`/`display:none`
|
|
192
|
+
descendants are correctly excluded), else its own `title`. Shared by
|
|
193
|
+
`form-control-single-label` and `form-control-programmatic-label-present` so they agree
|
|
194
|
+
on what "a label with content" means.
|
|
195
|
+
|
|
196
|
+
### `getLabelMethod(el, ctx, opts)` → `{ method, value }`
|
|
197
|
+
Which mechanism actually labels `el` — `'label'` | `'aria-labelledby'` |
|
|
198
|
+
`'aria-label'` | `'title'` | `'placeholder'` | `'none'` — checked in that precedence
|
|
199
|
+
order.
|
|
200
|
+
|
|
201
|
+
### `getLabelStrength(method)` → `'strong' | 'medium' | 'weak' | 'none'`
|
|
202
|
+
Policy classification of a `getLabelMethod` result (`label`/`aria-labelledby` →
|
|
203
|
+
strong, `aria-label` → medium, `title`/`placeholder` → weak). Deterministic and
|
|
204
|
+
intentionally tweakable in one place rather than per rule.
|
|
205
|
+
|
|
206
|
+
### `hasAccessibleName(el)` → `boolean`
|
|
207
|
+
Back-compat convenience: `!!getAccessibleNameInfo(el).value`.
|
|
208
|
+
|
|
209
|
+
---
|
|
210
|
+
|
|
211
|
+
## 4) IDREF resolution
|
|
212
|
+
|
|
213
|
+
Backs `aria-labelledby`/`aria-describedby` and any other space-separated ID-reference
|
|
214
|
+
attribute.
|
|
215
|
+
|
|
216
|
+
### `resolveIdRefs(idrefString, ctx, opts)` → `{ refs: Element[], missing: string[], flags[] }`
|
|
217
|
+
Splits and resolves a space-separated ID list to elements (deduped, cached per scope).
|
|
218
|
+
`opts.maxRefs` truncates deterministically (adds `'truncated'` to `flags`).
|
|
219
|
+
|
|
220
|
+
### `getTextFromIdRefs(idrefString, ctx, opts)` → `{ text, refsCount, missing[], flags[] }`
|
|
221
|
+
Resolves refs, then computes **each target's own text alternative** recursively
|
|
222
|
+
(accname-aligned — a referenced element's name is recomputed, not read as raw
|
|
223
|
+
`textContent`), joins with spaces. This is what `getAriaLabelledByInfo`/
|
|
224
|
+
`getAccessibleDescriptionInfo` call internally.
|
|
225
|
+
|
|
226
|
+
### `getTextFromIdRefsIdrefEligible(idrefString, ctx, opts)` → `{ text, refsCount, missing[], excluded[], flags[] }`
|
|
227
|
+
Same, but under IDREF eligibility rules specifically: hidden/`aria-hidden`/collapsed
|
|
228
|
+
targets are still included (IDREF targets aren't scoped by visibility the way rendered
|
|
229
|
+
content is — see the `root` note in the source), only `inert` targets are excluded.
|
|
230
|
+
`excluded` lists `{ id, reasons }` for anything dropped.
|
|
231
|
+
|
|
232
|
+
---
|
|
233
|
+
|
|
234
|
+
## 5) Role & focusability
|
|
235
|
+
|
|
236
|
+
### `getRoleInfo(el, ctx, opts)` → `{ role, source, flags[] }`
|
|
237
|
+
Explicit `role` attribute if present (flags `'presentation'` for
|
|
238
|
+
`presentation`/`none`, `'multiple-roles'` if it contains whitespace), else a small,
|
|
239
|
+
deliberately minimal implicit-role mapping (`a[href]`→`link`, `button`→`button`,
|
|
240
|
+
`input[type=checkbox]`→`checkbox`, etc.) unless `opts.disallowImplicit`.
|
|
241
|
+
|
|
242
|
+
### `getFocusableInfo(el, ctx, opts)` → `{ focusable, tabbable, mechanism, flags[] }`
|
|
243
|
+
Platform focusability: whether `el` can receive focus at all, and whether it's in the
|
|
244
|
+
default tab order.
|
|
245
|
+
|
|
246
|
+
### `hasLandmarkScopingAncestor(el, ctx)` → `boolean`
|
|
247
|
+
Whether `el` sits inside a landmark-scoping ancestor — the role-aware
|
|
248
|
+
sectioning-content/`<main>` check backing `<header>`/`<footer>`/`<aside>`'s conditional
|
|
249
|
+
implicit roles (their implicit landmark role only applies when *not* nested inside
|
|
250
|
+
certain ancestors). Available both as `helpers.hasLandmarkScopingAncestor` and
|
|
251
|
+
`helpers.aria.hasLandmarkScopingAncestor` — same function, re-exported at the top level
|
|
252
|
+
so landmark-check files don't need to reach into `aria.*` for it.
|
|
253
|
+
|
|
254
|
+
---
|
|
255
|
+
|
|
256
|
+
## 6) Attributes, language, reporting, outcomes
|
|
257
|
+
|
|
258
|
+
### `getAttributeInfo(el, attrName)` → `{ present, value, mechanism, flags[] }`
|
|
259
|
+
Generic trimmed-attribute presence/value check. Use for any plain attribute a rule
|
|
260
|
+
inspects directly (not one of the naming/ARIA attributes above, which have their own
|
|
261
|
+
dedicated helpers).
|
|
262
|
+
|
|
263
|
+
### `isValidLanguageTag(value)` / `isRegisteredLanguageSubtag(subtag)` → `boolean`
|
|
264
|
+
BCP 47 well-formedness **plus** a real IANA subtag-registry check — shape alone accepts
|
|
265
|
+
`"eng"` or `"em-US"`, which look like language tags but use an unregistered primary
|
|
266
|
+
subtag (the registry only lists a three-letter subtag when no two-letter one exists,
|
|
267
|
+
so `"en"` is registered and `"eng"` is not). Use `isValidLanguageTag` for any
|
|
268
|
+
`lang`/`xml:lang`-checking rule instead of a regex-only check.
|
|
269
|
+
|
|
270
|
+
### `reportOccurrence(node, partial)` → occurrence object
|
|
271
|
+
**Use this to build every occurrence.** Attaches the element so the engine fills in
|
|
272
|
+
`selector`, `html`, and `structuralPath` centrally — see `RULE_AUTHORING.md` §4.3/§9 for
|
|
273
|
+
the full contract and why hand-building these fields yourself is a real (measured:
|
|
274
|
+
4 min → <1 s) performance regression on any rule reporting many occurrences.
|
|
275
|
+
|
|
276
|
+
```js
|
|
277
|
+
occurrences.push(helpers.reportOccurrence(el, {
|
|
278
|
+
summary: '…',
|
|
279
|
+
hint: '…',
|
|
280
|
+
i18n: { summaryKey: '…', hintKey: '…', params: { element: 'img' } },
|
|
281
|
+
data: { visibilityFilter: eligInfo }
|
|
282
|
+
}));
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
### `resolveTieredOutcome(failOccurrences, cantTellOccurrences, severity)` → `{ outcome, severity, occurrences }`
|
|
286
|
+
For a rule that collects two confidence tiers in one run — some findings confident
|
|
287
|
+
enough for `fail`, others only `cantTell` ("needs human review"). Returns `fail` (with
|
|
288
|
+
**both** tiers' occurrences, tagged via `occurrenceOutcome`) whenever any fail-tier
|
|
289
|
+
finding exists, `cantTell` when only cantTell-tier findings exist, `pass` otherwise.
|
|
290
|
+
Reach for this instead of the naive `if (fails.length) return fail(fails); else if
|
|
291
|
+
(cantTells.length) return cantTell(cantTells);`, which silently drops every cantTell
|
|
292
|
+
finding whenever at least one fail finding also exists on the page.
|
|
293
|
+
|
|
294
|
+
### `getPerfStats()` / `resetPerfStats()`
|
|
295
|
+
Only populated when the engine is run with `perfStats: true`. Not for rule logic —
|
|
296
|
+
useful when profiling a new rule's hot paths during development.
|
|
297
|
+
|
|
298
|
+
---
|
|
299
|
+
|
|
300
|
+
## 7) Namespaced helper groups
|
|
301
|
+
|
|
302
|
+
Two larger helper sets are exposed as namespaces rather than flattened, since their
|
|
303
|
+
member counts and internal cohesion (contrast math, ARIA validity data) don't fit the
|
|
304
|
+
flat `helpers.*` list above:
|
|
305
|
+
|
|
306
|
+
### `helpers.contrast.*`
|
|
307
|
+
Color/contrast math and text-run analysis: `parseCssColorToRgba`, `compositeRgba`,
|
|
308
|
+
`relativeLuminance`, `contrastRatio`, `requiredRatio`, `isLargeText`,
|
|
309
|
+
`computeEffectiveForeground`/`computeEffectiveBackground`, `getComputabilityBlocker`,
|
|
310
|
+
`getTextScan`, `isInactiveUiComponent`, plus small numeric/formatting utilities
|
|
311
|
+
(`clamp01`, `round2`, `toHex2`, `pxToPt`, `fontWeightLabel`, …). Backs the
|
|
312
|
+
`contrast-*` rule family (`contrast-minimum`, `contrast-enhanced`,
|
|
313
|
+
`contrast-computable`) — see `src/core/contrast-helpers.js` if you're extending that
|
|
314
|
+
family specifically.
|
|
315
|
+
|
|
316
|
+
### `helpers.aria.*`
|
|
317
|
+
ARIA validity/taxonomy data and checks: `isValidAriaAttrName`, `getAttrValueType`,
|
|
318
|
+
`validateAttrValue`, `getExplicitRole`, `getAllRoleTokens`, `isAbstractRole`,
|
|
319
|
+
`isDeprecatedRole`, `isAuthorDiscouragedRole`, `isAuthorProhibitedRole`,
|
|
320
|
+
`isDeprecatedAttr`, `getDeprecatedRoleGuidance`, `isKnownRole`, `isValidConcreteRole`,
|
|
321
|
+
`getRequiredAttrsForRole`, `getRequiredOwnedRoles`, `getRequiredContextRoles`,
|
|
322
|
+
`isRoleAllowedOnElement`, `getContainmentRole`, `getNativeRoleForElement`,
|
|
323
|
+
`hasLandmarkScopingAncestor` (also re-exported flat, see §5). Backs the whole
|
|
324
|
+
`aria-*` rule family — check here before hand-rolling role/attribute validity logic in
|
|
325
|
+
a new ARIA rule.
|
|
326
|
+
|
|
327
|
+
---
|
|
328
|
+
|
|
329
|
+
## 8) Not for rule use
|
|
330
|
+
|
|
331
|
+
`helpers.__setActiveRuleExcludeSelectors` exists on the object but is engine-internal —
|
|
332
|
+
`dom-runner.js` calls it before invoking each rule to scope that rule's
|
|
333
|
+
`engineOptions.rules[ruleId].excludeSelectors`. A rule itself never calls it.
|