@surea11y/core 1.5.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 +240 -149
- package/README.md +51 -44
- package/docs/ACT_RULE_MAPPING.md +245 -0
- package/docs/API_STABILITY.md +53 -5
- package/docs/BINDING_AUTHORS_GUIDE.md +106 -4
- package/docs/DESIGN_CHALLENGES.md +367 -0
- package/docs/EARL.md +100 -0
- package/docs/ENGINE_OPTIONS.md +42 -4
- package/docs/I18N.md +4 -4
- package/docs/INTEGRATION.md +4 -2
- package/docs/LIMITATIONS.md +9 -5
- package/docs/OUTPUT_SCHEMA.md +44 -6
- package/docs/POLICY.md +1 -1
- package/docs/REPORT.md +1 -1
- package/docs/RULE_AUTHORING.md +63 -36
- package/docs/RULE_CATALOG.md +1928 -169
- package/docs/RULE_HELPERS.md +333 -0
- package/docs/RULE_TAXONOMY.md +27 -6
- package/docs/SARIF.md +21 -2
- package/docs/TROUBLESHOOTING.md +2 -2
- package/docs/WCAG_CONFORMANCE.md +34 -10
- package/package.json +11 -9
- package/src/baseline.js +3 -3
- package/src/checks/automatic/area-alt-present.js +2 -2
- package/src/checks/automatic/aria-allowed-attr.js +74 -10
- package/src/checks/automatic/aria-allowed-role.js +34 -25
- package/src/checks/automatic/aria-braille-equivalent.js +21 -13
- package/src/checks/automatic/aria-conditional-attr.js +22 -15
- package/src/checks/automatic/aria-deprecated-role.js +13 -1
- package/src/checks/automatic/aria-hidden-body.js +3 -3
- package/src/checks/automatic/aria-hidden-focus.js +5 -5
- package/src/checks/automatic/aria-prohibited-attr.js +23 -18
- package/src/checks/automatic/aria-prohibited-children.js +136 -43
- package/src/checks/automatic/aria-required-attr.js +119 -24
- package/src/checks/automatic/aria-required-children.js +54 -30
- package/src/checks/automatic/aria-required-parent.js +93 -15
- package/src/checks/automatic/aria-role-name-present.js +37 -23
- package/src/checks/automatic/aria-roles-valid.js +52 -21
- package/src/checks/automatic/aria-valid-attr-value.js +89 -33
- package/src/checks/automatic/aria-valid-attr.js +15 -10
- package/src/checks/automatic/autocomplete-valid.js +2 -2
- package/src/checks/automatic/avoid-inline-spacing.js +133 -6
- package/src/checks/automatic/binary-control-name-present.js +27 -5
- package/src/checks/automatic/button-name-present.js +92 -6
- package/src/checks/automatic/combobox-name-present.js +26 -6
- package/src/checks/automatic/contrast-computable.js +42 -0
- package/src/checks/automatic/contrast-enhanced.js +33 -1
- package/src/checks/automatic/contrast-minimum.js +33 -1
- package/src/checks/automatic/css-orientation-lock.js +138 -24
- package/src/checks/automatic/definition-list-children-valid.js +7 -8
- package/src/checks/automatic/deprecated-elements-not-used.js +1 -1
- package/src/checks/automatic/dialog-name-present.js +20 -2
- package/src/checks/automatic/duplicate-id-aria.js +10 -3
- package/src/checks/automatic/duplicate-id.js +203 -0
- package/src/checks/automatic/embed-text-alternative-present.js +2 -2
- package/src/checks/automatic/form-control-programmatic-label-present.js +19 -2
- package/src/checks/automatic/form-control-single-label.js +10 -1
- package/src/checks/automatic/identical-iframes-same-purpose.js +229 -0
- package/src/checks/automatic/iframe-focusable-content.js +68 -7
- package/src/checks/automatic/iframe-name-present.js +37 -3
- package/src/checks/automatic/iframe-title-unique.js +1 -1
- package/src/checks/automatic/img-alt-present.js +12 -4
- package/src/checks/automatic/label-in-name.js +204 -68
- package/src/checks/automatic/link-in-text-block.js +285 -29
- package/src/checks/automatic/link-name-present.js +22 -1
- package/src/checks/automatic/list-children-valid.js +6 -6
- package/src/checks/automatic/listbox-name-present.js +28 -8
- package/src/checks/automatic/listitem-parent-valid.js +4 -4
- package/src/checks/automatic/menuitem-name-present.js +20 -2
- package/src/checks/automatic/meta-refresh-no-exceptions.js +25 -14
- package/src/checks/automatic/meta-refresh-timing-absent.js +14 -7
- package/src/checks/automatic/meter-name-present.js +23 -4
- package/src/checks/automatic/nested-interactive-controls-absent.js +5 -5
- package/src/checks/automatic/option-name-present.js +23 -4
- package/src/checks/automatic/page-title-present.js +21 -3
- package/src/checks/automatic/presentational-children-focusable-absent.js +330 -0
- package/src/checks/automatic/progressbar-name-present.js +23 -4
- package/src/checks/automatic/{role-img-alt-present.js → role-img-text-alternative-present.js} +64 -16
- package/src/checks/automatic/searchbox-name-present.js +28 -8
- package/src/checks/automatic/server-side-image-map-absent.js +1 -1
- package/src/checks/automatic/slider-name-present.js +27 -6
- package/src/checks/automatic/spinbutton-name-present.js +28 -8
- package/src/checks/automatic/summary-name-present.js +18 -2
- package/src/checks/automatic/svg-image-text-alternative-present.js +1 -1
- package/src/checks/automatic/svg-text-alternative-present.js +13 -10
- package/src/checks/automatic/tab-name-present.js +21 -2
- package/src/checks/automatic/table-headers-attr-valid.js +43 -8
- package/src/checks/automatic/table-th-has-data-cells.js +61 -5
- package/src/checks/automatic/target-size-minimum.js +155 -58
- package/src/checks/automatic/td-has-header.js +24 -23
- package/src/checks/automatic/textbox-name-present.js +28 -8
- package/src/checks/automatic/tooltip-name-present.js +21 -2
- package/src/checks/automatic/treeitem-name-present.js +23 -4
- package/src/checks/automatic/valid-lang.js +92 -7
- package/src/checks/automatic/video-poster-text-alternative-present.js +1 -1
- package/src/checks/manual/accesskeys-manual.js +3 -3
- package/src/checks/manual/area-alt-decorative-manual.js +7 -0
- package/src/checks/manual/area-alt-quality-manual.js +6 -0
- package/src/checks/manual/aria-checked-state-mismatch-manual.js +5 -5
- package/src/checks/manual/aria-text-manual.js +4 -4
- package/src/checks/manual/bypass-blocks-present-manual.js +44 -26
- package/src/checks/manual/canvas-text-alternative-quality-manual.js +8 -0
- package/src/checks/manual/css-focus-indicator-suppressed-manual.js +444 -0
- package/src/checks/manual/embed-text-alternative-quality-manual.js +8 -0
- package/src/checks/manual/empty-heading-manual.js +58 -11
- package/src/checks/manual/empty-table-header-manual.js +8 -8
- package/src/checks/manual/focus-order-semantics-manual.js +15 -15
- package/src/checks/manual/form-control-label-quality-manual.js +563 -0
- package/src/checks/manual/form-control-programmatic-label-quality-manual.js +2 -2
- package/src/checks/manual/heading-order-manual.js +3 -3
- package/src/checks/manual/heading-quality-manual.js +338 -0
- package/src/checks/manual/identical-links-same-purpose-manual.js +73 -12
- package/src/checks/manual/image-redundant-alt-manual.js +4 -4
- package/src/checks/manual/img-alt-decorative-manual.js +211 -52
- package/src/checks/manual/img-alt-quality-manual.js +7 -0
- package/src/checks/manual/input-image-alt-decorative-manual.js +8 -0
- package/src/checks/manual/input-image-alt-quality-manual.js +5 -0
- package/src/checks/manual/label-title-only-manual.js +4 -4
- package/src/checks/manual/landmark-banner-is-top-level-manual.js +7 -7
- package/src/checks/manual/landmark-complementary-is-top-level-manual.js +231 -0
- package/src/checks/manual/landmark-contentinfo-is-top-level-manual.js +9 -9
- package/src/checks/manual/landmark-main-is-top-level-manual.js +6 -6
- package/src/checks/manual/landmark-no-duplicate-banner-manual.js +6 -6
- package/src/checks/manual/landmark-no-duplicate-contentinfo-manual.js +6 -6
- package/src/checks/manual/landmark-no-duplicate-main-manual.js +2 -2
- package/src/checks/manual/landmark-one-main-manual.js +6 -6
- package/src/checks/manual/landmark-unique-manual.js +9 -9
- package/src/checks/manual/link-name-quality-manual.js +161 -32
- package/src/checks/manual/media-transcript-present-manual.js +2 -3
- package/src/checks/manual/meta-viewport-large-manual.js +2 -2
- package/src/checks/manual/mouse-only-event-handlers-manual.js +9 -9
- package/src/checks/manual/no-autoplay-audio-manual.js +3 -3
- package/src/checks/manual/object-text-alternative-quality-manual.js +8 -0
- package/src/checks/manual/p-as-heading-manual.js +4 -4
- package/src/checks/manual/page-has-heading-one-manual.js +6 -6
- package/src/checks/manual/page-title-patterns-manual.js +26 -3
- package/src/checks/manual/password-paste-enabled-manual.js +255 -0
- package/src/checks/manual/presentation-role-conflict-manual.js +55 -25
- package/src/checks/manual/region-manual.js +19 -19
- package/src/checks/manual/scope-attr-valid-manual.js +2 -2
- package/src/checks/manual/scrollable-region-focusable-manual.js +7 -7
- package/src/checks/manual/skip-link-manual.js +5 -5
- package/src/checks/manual/svg-text-alternative-quality-manual.js +9 -0
- package/src/checks/manual/tabindex-manual.js +2 -2
- package/src/checks/manual/table-duplicate-name-manual.js +2 -2
- package/src/checks/manual/table-fake-caption-manual.js +2 -2
- package/src/checks/manual/video-caption-manual.js +3 -3
- package/src/checks/manual-review.js +17 -1
- package/src/core.js +8880 -41883
- package/src/earl.js +144 -0
- package/src/report.js +2 -2
- package/src/sarif.js +22 -2
- package/surea11y.browser.js +10 -37882
- package/surea11y.i18n.de.js +2 -21
- package/surea11y.i18n.es.js +2 -21
- package/surea11y.i18n.fr.js +2 -21
- package/bin/surea11y-core.js +0 -20
package/docs/RULE_AUTHORING.md
CHANGED
|
@@ -52,7 +52,16 @@ function runInPage(ctx) { /* see Runtime Contract */ }
|
|
|
52
52
|
module.exports = { id, meta, runInPage };
|
|
53
53
|
```
|
|
54
54
|
|
|
55
|
-
|
|
55
|
+
One optional fourth export: `applicability(ctx)`, a predicate the engine calls before
|
|
56
|
+
`runInPage` to decide whether the rule is in scope for this run at all. Fourteen rules
|
|
57
|
+
use it today (see §11.2). Export it alongside the other three when you need it:
|
|
58
|
+
|
|
59
|
+
```js
|
|
60
|
+
module.exports = { id, meta, runInPage, applicability };
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Nothing else. `npm run validate:rules` enforces exactly this set, and rejects a fifth
|
|
64
|
+
export.
|
|
56
65
|
|
|
57
66
|
---
|
|
58
67
|
|
|
@@ -78,7 +87,7 @@ Examples observed:
|
|
|
78
87
|
|
|
79
88
|
## 4) Meta Contract (all keys used by current rules)
|
|
80
89
|
|
|
81
|
-
Every rule defines a `meta` object.
|
|
90
|
+
Every rule defines a `meta` object. Across the shipped ruleset, the union of meta keys is:
|
|
82
91
|
|
|
83
92
|
### 4.1 Required top-level keys
|
|
84
93
|
|
|
@@ -177,8 +186,7 @@ occurrences.push(helpers.reportOccurrence(el, { summary: '…', hint: '…' }));
|
|
|
177
186
|
```
|
|
178
187
|
|
|
179
188
|
It attaches the element for the engine to finalize, which is how `selector`,
|
|
180
|
-
`html` and `structuralPath` get filled in centrally instead of in each
|
|
181
|
-
124 rules.
|
|
189
|
+
`html` and `structuralPath` get filled in centrally instead of in each rule.
|
|
182
190
|
|
|
183
191
|
**This is a performance contract, not just a convenience.** Every occurrence
|
|
184
192
|
gets a `structuralPath`. Given the element, the engine computes it directly.
|
|
@@ -246,17 +254,15 @@ that one is on you.
|
|
|
246
254
|
|
|
247
255
|
## 6) Helpers contract used by rules (ctx.helpers)
|
|
248
256
|
|
|
249
|
-
Rules use helpers returned by `createDomHelpers()`.
|
|
257
|
+
Rules use helpers returned by `createDomHelpers()`. The most load-bearing ones —
|
|
258
|
+
`queryAllSmart` (query with shadow/hidden/exclude handling built in),
|
|
259
|
+
`getAccessibleNameInfo`/`getAccessibleDescriptionInfo`/`getTextAlternativeInfo` (naming),
|
|
260
|
+
`isAccTreeEligible`/`getEligibilityInfo` (visibility), `getRoleInfo`/`getFocusableInfo`
|
|
261
|
+
(role/focus) — cover most rules.
|
|
250
262
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
- `buildSimpleSelector`, `buildSelector`
|
|
255
|
-
- `isAccTreeEligible`, `getEligibilityInfo`
|
|
256
|
-
- `resolveIdRefs`, `getTextFromIdRefs`
|
|
257
|
-
- `getAccessibleNameInfo`, `getAccessibleDescriptionInfo`
|
|
258
|
-
- `getTextAlternativeInfo`
|
|
259
|
-
- `getRoleInfo`, `getFocusableInfo`
|
|
263
|
+
**See [`RULE_HELPERS.md`](./RULE_HELPERS.md) for the full reference** (~35 helpers plus
|
|
264
|
+
the `contrast.*`/`aria.*` namespaces), with what each one does and when to reach for it
|
|
265
|
+
instead of reimplementing the logic in a new rule.
|
|
260
266
|
|
|
261
267
|
### 6.1 Shadow DOM scanning
|
|
262
268
|
|
|
@@ -268,15 +274,19 @@ const nodes = helpers.queryAllSmart
|
|
|
268
274
|
: helpers.queryAll('img');
|
|
269
275
|
```
|
|
270
276
|
|
|
271
|
-
Shadow traversal is
|
|
277
|
+
Shadow traversal is on by default. It is the caller who opts out:
|
|
272
278
|
```js
|
|
273
|
-
engineOptions: { includeShadowDom:
|
|
279
|
+
engineOptions: { includeShadowDom: false } // light DOM only
|
|
274
280
|
```
|
|
281
|
+
So write the rule assuming open shadow roots are in scope; `queryAllSmart` honours the
|
|
282
|
+
caller's choice for you. Closed roots are unreachable either way.
|
|
275
283
|
|
|
276
284
|
### 6.2 Reporting note for Shadow DOM
|
|
277
285
|
|
|
278
|
-
Selectors do not pierce shadow boundaries, so a `selector` may not uniquely locate
|
|
279
|
-
|
|
286
|
+
Selectors do not pierce shadow boundaries, so a `selector` may not uniquely locate a node
|
|
287
|
+
inside a shadow root — which is why `html` matters as the "which element" signal there.
|
|
288
|
+
You get both for free by reporting the element through `helpers.reportOccurrence` (§4.3);
|
|
289
|
+
there is nothing extra to do for shadow DOM specifically.
|
|
280
290
|
|
|
281
291
|
---
|
|
282
292
|
|
|
@@ -290,7 +300,8 @@ data: {
|
|
|
290
300
|
}
|
|
291
301
|
```
|
|
292
302
|
|
|
293
|
-
|
|
303
|
+
Pass the `eligInfo` you already computed for the element; the fallback object above is
|
|
304
|
+
for the case where a rule has none to give.
|
|
294
305
|
|
|
295
306
|
---
|
|
296
307
|
|
|
@@ -319,34 +330,48 @@ Manual:
|
|
|
319
330
|
|
|
320
331
|
---
|
|
321
332
|
|
|
322
|
-
## 9) Occurrence object shape
|
|
333
|
+
## 9) Occurrence object shape
|
|
323
334
|
|
|
324
|
-
|
|
335
|
+
Report the element and let the engine finish the object (§4.3):
|
|
325
336
|
|
|
326
337
|
```js
|
|
327
|
-
occurrences.push(
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
338
|
+
occurrences.push(
|
|
339
|
+
helpers.reportOccurrence(el, {
|
|
340
|
+
summary: '…',
|
|
341
|
+
hint: '…',
|
|
342
|
+
i18n: { summaryKey: '…', hintKey: '…', params: { element: 'img' } },
|
|
343
|
+
data: { visibilityFilter: eligInfo || { targetSet: 'acc', accEligible: null, reasons: [] } }
|
|
344
|
+
})
|
|
345
|
+
);
|
|
335
346
|
```
|
|
336
347
|
|
|
337
|
-
|
|
338
|
-
- `selector` (or sometimes `selectorStr`)
|
|
339
|
-
- `html`
|
|
348
|
+
What a rule supplies:
|
|
340
349
|
- `summary`
|
|
341
350
|
- `hint`
|
|
342
351
|
- `i18n` (`summaryKey`, `hintKey`, `params`)
|
|
343
352
|
- `data` (includes `visibilityFilter`)
|
|
344
353
|
|
|
354
|
+
What the engine fills in from the reported element:
|
|
355
|
+
- `selector`
|
|
356
|
+
- `html`
|
|
357
|
+
- `structuralPath`
|
|
358
|
+
|
|
359
|
+
Setting `selector`/`html` yourself still works and still wins — a handful of rules whose
|
|
360
|
+
finding is not a single element (the contrast rules report text runs) do exactly that. It
|
|
361
|
+
is the exception, not the pattern to copy.
|
|
362
|
+
|
|
345
363
|
---
|
|
346
364
|
|
|
347
365
|
## 10) Structured doc comment block
|
|
348
366
|
|
|
349
|
-
Keep the structured header comment (`@
|
|
367
|
+
Keep the structured header comment (`@check`, `@atomic`, `@summary`, `@standard`, `@sc`,
|
|
368
|
+
`@applicability`, `@expectation`). The id goes on `@check` — `@rule` is not a tag this
|
|
369
|
+
repo uses. `docs/RULE_TEMPLATE.js` has the full block to copy.
|
|
370
|
+
|
|
371
|
+
`@applicability` and `@expectation` are consumer-facing: `scripts/generate-rule-catalog.js`
|
|
372
|
+
reads them straight from the source and publishes them per rule in
|
|
373
|
+
[`RULE_CATALOG.md`](./RULE_CATALOG.md#rule-reference). Write them for someone deciding
|
|
374
|
+
whether a result applies to their page, and rerun `npm run docs:rule-catalog` after editing them.
|
|
350
375
|
|
|
351
376
|
---
|
|
352
377
|
|
|
@@ -452,8 +477,10 @@ After adding or changing any fixture, regenerate the index:
|
|
|
452
477
|
npm run fixtures:index
|
|
453
478
|
```
|
|
454
479
|
|
|
455
|
-
This writes `tests/fixtures/INDEX.md` (human-readable)
|
|
480
|
+
This writes `tests/fixtures/INDEX.md` (human-readable), `tests/fixtures/index.json`
|
|
456
481
|
(machine-readable — every rule, its fixture path, and parsed pass/fail/cantTell case
|
|
457
|
-
counts, for external tooling to enumerate and load fixtures directly)
|
|
482
|
+
counts, for external tooling to enumerate and load fixtures directly) and
|
|
483
|
+
`tests/fixtures/index.html` (the same listing as a browsable page). Commit all three
|
|
458
484
|
alongside the fixture and test changes. A rule shipped without its fixture is treated
|
|
459
|
-
the same as a rule shipped without tests — not done.
|
|
485
|
+
the same as a rule shipped without tests — not done. `npm run fixtures:check` reports
|
|
486
|
+
a stale index without rewriting it, and CI fails on one.
|