@surea11y/core 1.1.2 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +48 -4
- package/LICENSE +373 -21
- package/README.md +70 -1
- package/bin/core.js +240 -11
- package/docs/API_STABILITY.md +61 -0
- package/docs/BASELINE.md +66 -0
- package/docs/BINDING_AUTHORS_GUIDE.md +9 -9
- package/docs/CI_INTEGRATIONS.md +103 -0
- package/docs/CLI.md +80 -1
- package/docs/ENGINE_OPTIONS.md +4 -0
- package/docs/INTEGRATION.md +19 -1
- package/docs/OUTPUT_SCHEMA.md +2 -2
- package/docs/REPORT.md +33 -0
- package/docs/RULE_AUTHORING.md +31 -0
- package/docs/RULE_CATALOG.md +1 -1
- package/docs/SARIF.md +59 -0
- package/package.json +15 -4
- package/src/baseline.js +0 -0
- package/src/catalogs/composites.wcag.js +414 -450
- package/src/checks/automatic/area-alt-present.js +59 -25
- package/src/checks/automatic/aria-allowed-attr.js +193 -33
- package/src/checks/automatic/aria-allowed-role.js +21 -7
- package/src/checks/automatic/aria-braille-equivalent.js +32 -10
- package/src/checks/automatic/aria-conditional-attr.js +24 -7
- package/src/checks/automatic/aria-deprecated-role.js +22 -8
- package/src/checks/automatic/aria-hidden-body.js +50 -19
- package/src/checks/automatic/aria-hidden-focus.js +408 -53
- package/src/checks/automatic/aria-prohibited-attr.js +296 -22
- package/src/checks/automatic/aria-prohibited-children.js +125 -29
- package/src/checks/automatic/aria-required-attr.js +23 -8
- package/src/checks/automatic/aria-required-children.js +37 -14
- package/src/checks/automatic/aria-required-parent.js +48 -14
- package/src/checks/automatic/aria-role-name-present.js +47 -21
- package/src/checks/automatic/aria-roles-valid.js +22 -12
- package/src/checks/automatic/aria-valid-attr-value.js +28 -7
- package/src/checks/automatic/aria-valid-attr.js +17 -5
- package/src/checks/automatic/autocomplete-valid.js +74 -16
- package/src/checks/automatic/avoid-inline-spacing.js +20 -6
- package/src/checks/automatic/binary-control-name-present.js +60 -50
- package/src/checks/automatic/button-name-present.js +48 -18
- package/src/checks/automatic/bypass-blocks-present.js +50 -25
- package/src/checks/automatic/canvas-text-alternative-present.js +57 -26
- package/src/checks/automatic/combobox-name-present.js +38 -45
- package/src/checks/automatic/contrast-computable.js +361 -341
- package/src/checks/automatic/contrast-enhanced.js +487 -466
- package/src/checks/automatic/contrast-minimum.js +486 -465
- package/src/checks/automatic/css-orientation-lock.js +39 -9
- package/src/checks/automatic/definition-list-children-valid.js +40 -19
- package/src/checks/automatic/deprecated-elements-not-used.js +21 -7
- package/src/checks/automatic/dialog-name-present.js +37 -75
- package/src/checks/automatic/dlitem-parent-valid.js +23 -8
- package/src/checks/automatic/duplicate-id-aria.js +24 -6
- package/src/checks/automatic/embed-text-alternative-present.js +86 -35
- package/src/checks/automatic/form-control-programmatic-label-present.js +79 -196
- package/src/checks/automatic/form-control-single-label.js +47 -10
- package/src/checks/automatic/html-xml-lang-mismatch.js +43 -19
- package/src/checks/automatic/iframe-focusable-content.js +26 -11
- package/src/checks/automatic/iframe-name-present.js +31 -9
- package/src/checks/automatic/iframe-title-unique.js +29 -8
- package/src/checks/automatic/img-alt-present.js +47 -43
- package/src/checks/automatic/input-image-alt-present.js +143 -112
- package/src/checks/automatic/label-in-name.js +50 -22
- package/src/checks/automatic/language-page-present.js +117 -109
- package/src/checks/automatic/link-in-text-block.js +59 -19
- package/src/checks/automatic/link-name-present.js +45 -14
- package/src/checks/automatic/list-children-valid.js +26 -9
- package/src/checks/automatic/listbox-name-present.js +39 -19
- package/src/checks/automatic/listitem-parent-valid.js +18 -6
- package/src/checks/automatic/menuitem-name-present.js +39 -61
- package/src/checks/automatic/meta-refresh-no-exceptions.js +37 -8
- package/src/checks/automatic/meta-refresh-timing-absent.js +28 -6
- package/src/checks/automatic/meta-viewport-zoom-enabled.js +32 -7
- package/src/checks/automatic/meter-name-present.js +36 -33
- package/src/checks/automatic/nested-interactive-controls-absent.js +31 -10
- package/src/checks/automatic/object-text-alternative-present.js +91 -39
- package/src/checks/automatic/option-name-present.js +38 -21
- package/src/checks/automatic/page-title-present.js +26 -7
- package/src/checks/automatic/progressbar-name-present.js +41 -34
- package/src/checks/automatic/role-img-alt-present.js +209 -157
- package/src/checks/automatic/searchbox-name-present.js +39 -19
- package/src/checks/automatic/server-side-image-map-absent.js +23 -8
- package/src/checks/automatic/slider-name-present.js +40 -47
- package/src/checks/automatic/spinbutton-name-present.js +39 -19
- package/src/checks/automatic/summary-name-present.js +37 -17
- package/src/checks/automatic/svg-image-text-alternative-present.js +114 -47
- package/src/checks/automatic/svg-text-alternative-present.js +246 -226
- package/src/checks/automatic/tab-name-present.js +37 -60
- package/src/checks/automatic/table-headers-attr-valid.js +24 -8
- package/src/checks/automatic/table-th-has-data-cells.js +22 -8
- package/src/checks/automatic/target-size-minimum.js +118 -48
- package/src/checks/automatic/td-has-header.js +29 -11
- package/src/checks/automatic/textbox-name-present.js +39 -19
- package/src/checks/automatic/tooltip-name-present.js +37 -18
- package/src/checks/automatic/treeitem-name-present.js +38 -21
- package/src/checks/automatic/valid-lang.js +20 -6
- package/src/checks/automatic/video-poster-text-alternative-present.js +79 -36
- package/src/checks/manual/accesskeys-manual.js +14 -5
- package/src/checks/manual/area-alt-decorative-manual.js +192 -193
- package/src/checks/manual/area-alt-quality-manual.js +182 -141
- package/src/checks/manual/aria-checked-state-mismatch-manual.js +34 -11
- package/src/checks/manual/aria-text-manual.js +14 -6
- package/src/checks/manual/canvas-text-alternative-quality-manual.js +149 -114
- package/src/checks/manual/css-hidden-focus.js +196 -165
- package/src/checks/manual/embed-text-alternative-quality-manual.js +171 -160
- package/src/checks/manual/empty-heading-manual.js +24 -12
- package/src/checks/manual/empty-table-header-manual.js +21 -14
- package/src/checks/manual/focus-order-semantics-manual.js +45 -10
- package/src/checks/manual/form-control-programmatic-label-quality-manual.js +207 -246
- package/src/checks/manual/heading-order-manual.js +22 -12
- package/src/checks/manual/identical-links-same-purpose-manual.js +34 -12
- package/src/checks/manual/image-redundant-alt-manual.js +17 -7
- package/src/checks/manual/img-alt-decorative-manual.js +131 -96
- package/src/checks/manual/img-alt-quality-manual.js +176 -127
- package/src/checks/manual/input-image-alt-decorative-manual.js +125 -92
- package/src/checks/manual/input-image-alt-quality-manual.js +125 -92
- package/src/checks/manual/label-title-only-manual.js +14 -5
- package/src/checks/manual/landmark-banner-is-top-level-manual.js +95 -29
- package/src/checks/manual/landmark-contentinfo-is-top-level-manual.js +82 -29
- package/src/checks/manual/landmark-main-is-top-level-manual.js +64 -23
- package/src/checks/manual/landmark-no-duplicate-banner-manual.js +54 -23
- package/src/checks/manual/landmark-no-duplicate-contentinfo-manual.js +54 -23
- package/src/checks/manual/landmark-no-duplicate-main-manual.js +17 -8
- package/src/checks/manual/landmark-one-main-manual.js +35 -21
- package/src/checks/manual/landmark-unique-manual.js +73 -45
- package/src/checks/manual/link-name-quality-manual.js +43 -12
- package/src/checks/manual/media-transcript-present-manual.js +35 -22
- package/src/checks/manual/meta-viewport-large-manual.js +25 -6
- package/src/checks/manual/mouse-only-event-handlers-manual.js +38 -11
- package/src/checks/manual/no-autoplay-audio-manual.js +20 -6
- package/src/checks/manual/object-text-alternative-quality-manual.js +175 -154
- package/src/checks/manual/p-as-heading-manual.js +22 -7
- package/src/checks/manual/page-has-heading-one-manual.js +40 -23
- package/src/checks/manual/page-title-patterns-manual.js +87 -51
- package/src/checks/manual/presentation-role-conflict-manual.js +59 -19
- package/src/checks/manual/region-manual.js +275 -62
- package/src/checks/manual/scope-attr-valid-manual.js +11 -9
- package/src/checks/manual/scrollable-region-focusable-manual.js +37 -11
- package/src/checks/manual/skip-link-manual.js +35 -12
- package/src/checks/manual/svg-text-alternative-quality-manual.js +206 -165
- package/src/checks/manual/tabindex-manual.js +11 -9
- package/src/checks/manual/table-duplicate-name-manual.js +17 -7
- package/src/checks/manual/table-fake-caption-manual.js +24 -7
- package/src/checks/manual/video-caption-manual.js +15 -4
- package/src/checks/manual-review.js +56 -12
- package/src/core/aria-helpers.js +1128 -823
- package/src/core/contrast-helpers.js +1217 -1062
- package/src/core/dom-helpers.js +4176 -3886
- package/src/core/dom-runner.js +720 -596
- package/src/core/frame-messaging.js +189 -138
- package/src/core/frame-scan.js +94 -82
- package/src/core/rollup-composites.js +94 -102
- package/src/core/rule-meta.js +71 -35
- package/src/core.js +37939 -29121
- package/src/i18n/en.js +1194 -887
- package/src/i18n/fr.js +1136 -793
- package/src/policy/contracts.js +13 -13
- package/src/policy/resolvePolicy.js +48 -44
- package/src/report.js +502 -0
- package/src/sarif.js +175 -0
- package/surea11y.browser.js +36042 -0
|
@@ -43,7 +43,13 @@ const meta = {
|
|
|
43
43
|
tags: ['wcag2a', 'wcag412', 'aria', 'structure', 'atomic', 'automatic'],
|
|
44
44
|
wcagSc: ['4.1.2'],
|
|
45
45
|
normativeMappings: [
|
|
46
|
-
{
|
|
46
|
+
{
|
|
47
|
+
standard: 'WCAG',
|
|
48
|
+
version: '2.2',
|
|
49
|
+
requirement: '4.1.2',
|
|
50
|
+
title: 'Name, Role, Value',
|
|
51
|
+
conformanceLevel: 'A'
|
|
52
|
+
}
|
|
47
53
|
],
|
|
48
54
|
defaultSeverity: 'serious',
|
|
49
55
|
category: 'robust',
|
|
@@ -53,9 +59,11 @@ const meta = {
|
|
|
53
59
|
};
|
|
54
60
|
|
|
55
61
|
function runInPage(ctx) {
|
|
56
|
-
const {
|
|
62
|
+
const { helpers, rule } = ctx;
|
|
57
63
|
|
|
58
|
-
function trim(v) {
|
|
64
|
+
function trim(v) {
|
|
65
|
+
return (v == null ? '' : String(v)).trim();
|
|
66
|
+
}
|
|
59
67
|
|
|
60
68
|
const TRUTHY_INVALID_VALUES = new Set(['true', 'grammar', 'spelling']);
|
|
61
69
|
|
|
@@ -78,13 +86,14 @@ function runInPage(ctx) {
|
|
|
78
86
|
if (TRUTHY_INVALID_VALUES.has(invalidValue)) continue;
|
|
79
87
|
|
|
80
88
|
const stableSelector = helpers.buildSelector ? helpers.buildSelector(el) : 'html';
|
|
81
|
-
const html = helpers.getOuterHtmlSnippet ? helpers.getOuterHtmlSnippet(el) :
|
|
89
|
+
const html = helpers.getOuterHtmlSnippet ? helpers.getOuterHtmlSnippet(el) : el.outerHTML || '';
|
|
82
90
|
const tag = (el.tagName || '').toLowerCase();
|
|
83
91
|
|
|
84
92
|
occurrences.push({
|
|
85
93
|
selector: stableSelector,
|
|
86
94
|
html,
|
|
87
|
-
summary:
|
|
95
|
+
summary:
|
|
96
|
+
'This element has aria-errormessage but aria-invalid is missing or "false", so the error message is not exposed.',
|
|
88
97
|
hint: 'Set aria-invalid to "true" (or "grammar"/"spelling") whenever aria-errormessage should be exposed to assistive technology.',
|
|
89
98
|
i18n: {
|
|
90
99
|
summaryKey: 'ariaConditionalAttr_summary_fail',
|
|
@@ -92,7 +101,10 @@ function runInPage(ctx) {
|
|
|
92
101
|
params: { element: tag, ariaInvalid: invalidValue || '(absent)' }
|
|
93
102
|
},
|
|
94
103
|
data: {
|
|
95
|
-
details: {
|
|
104
|
+
details: {
|
|
105
|
+
reasonCode: 'ARIA_ERRORMESSAGE_WITHOUT_TRUTHY_INVALID',
|
|
106
|
+
ariaInvalid: invalidValue
|
|
107
|
+
}
|
|
96
108
|
}
|
|
97
109
|
});
|
|
98
110
|
}
|
|
@@ -101,7 +113,12 @@ function runInPage(ctx) {
|
|
|
101
113
|
return { ruleId: rule.ruleId, outcome: 'notApplicable', severity: 'minor', occurrences: [] };
|
|
102
114
|
}
|
|
103
115
|
if (occurrences.length) {
|
|
104
|
-
return {
|
|
116
|
+
return {
|
|
117
|
+
ruleId: rule.ruleId,
|
|
118
|
+
outcome: 'fail',
|
|
119
|
+
severity: rule.defaultSeverity || 'serious',
|
|
120
|
+
occurrences
|
|
121
|
+
};
|
|
105
122
|
}
|
|
106
123
|
return { ruleId: rule.ruleId, outcome: 'pass', severity: 'minor', occurrences: [] };
|
|
107
124
|
}
|
|
@@ -26,7 +26,8 @@ const id = 'aria-deprecated-role';
|
|
|
26
26
|
|
|
27
27
|
const meta = {
|
|
28
28
|
title: 'role attribute must not use a deprecated or author-prohibited ARIA role',
|
|
29
|
-
description:
|
|
29
|
+
description:
|
|
30
|
+
'Checks that an explicit role="" attribute does not use a role deprecated by the WAI-ARIA specification, or one reserved for user-agent-internal use only (e.g. role="generic").',
|
|
30
31
|
i18n: {
|
|
31
32
|
titleKey: 'ariaDeprecatedRole_title',
|
|
32
33
|
descriptionKey: 'ariaDeprecatedRole_description'
|
|
@@ -35,7 +36,13 @@ const meta = {
|
|
|
35
36
|
tags: ['wcag2a', 'wcag412', 'aria', 'structure', 'atomic', 'automatic'],
|
|
36
37
|
wcagSc: ['4.1.2'],
|
|
37
38
|
normativeMappings: [
|
|
38
|
-
{
|
|
39
|
+
{
|
|
40
|
+
standard: 'WCAG',
|
|
41
|
+
version: '2.2',
|
|
42
|
+
requirement: '4.1.2',
|
|
43
|
+
title: 'Name, Role, Value',
|
|
44
|
+
conformanceLevel: 'A'
|
|
45
|
+
}
|
|
39
46
|
],
|
|
40
47
|
defaultSeverity: 'moderate',
|
|
41
48
|
category: 'robust',
|
|
@@ -45,14 +52,16 @@ const meta = {
|
|
|
45
52
|
};
|
|
46
53
|
|
|
47
54
|
function runInPage(ctx) {
|
|
48
|
-
const {
|
|
55
|
+
const { helpers, rule } = ctx;
|
|
49
56
|
|
|
50
57
|
const ariaHelpers = helpers && helpers.aria ? helpers.aria : null;
|
|
51
58
|
if (!ariaHelpers) {
|
|
52
59
|
return { ruleId: rule.ruleId, outcome: 'notApplicable', severity: 'minor', occurrences: [] };
|
|
53
60
|
}
|
|
54
61
|
|
|
55
|
-
const nodes = helpers.queryAllSmart
|
|
62
|
+
const nodes = helpers.queryAllSmart
|
|
63
|
+
? helpers.queryAllSmart('[role]')
|
|
64
|
+
: helpers.queryAll('[role]');
|
|
56
65
|
|
|
57
66
|
const occurrences = [];
|
|
58
67
|
let applicableCount = 0;
|
|
@@ -72,10 +81,10 @@ function runInPage(ctx) {
|
|
|
72
81
|
if (!ariaHelpers.isDeprecatedRole(role)) continue;
|
|
73
82
|
|
|
74
83
|
const stableSelector = helpers.buildSelector ? helpers.buildSelector(el) : 'html';
|
|
75
|
-
const html = helpers.getOuterHtmlSnippet ? helpers.getOuterHtmlSnippet(el) :
|
|
84
|
+
const html = helpers.getOuterHtmlSnippet ? helpers.getOuterHtmlSnippet(el) : el.outerHTML || '';
|
|
76
85
|
const guidance = ariaHelpers.getDeprecatedRoleGuidance
|
|
77
|
-
|
|
78
|
-
|
|
86
|
+
? ariaHelpers.getDeprecatedRoleGuidance(role)
|
|
87
|
+
: 'Replace the deprecated role with its recommended replacement.';
|
|
79
88
|
|
|
80
89
|
occurrences.push({
|
|
81
90
|
selector: stableSelector,
|
|
@@ -97,7 +106,12 @@ function runInPage(ctx) {
|
|
|
97
106
|
return { ruleId: rule.ruleId, outcome: 'notApplicable', severity: 'minor', occurrences: [] };
|
|
98
107
|
}
|
|
99
108
|
if (occurrences.length) {
|
|
100
|
-
return {
|
|
109
|
+
return {
|
|
110
|
+
ruleId: rule.ruleId,
|
|
111
|
+
outcome: 'fail',
|
|
112
|
+
severity: rule.defaultSeverity || 'moderate',
|
|
113
|
+
occurrences
|
|
114
|
+
};
|
|
101
115
|
}
|
|
102
116
|
return { ruleId: rule.ruleId, outcome: 'pass', severity: 'minor', occurrences: [] };
|
|
103
117
|
}
|
|
@@ -24,7 +24,8 @@ const id = 'aria-hidden-body';
|
|
|
24
24
|
|
|
25
25
|
const meta = {
|
|
26
26
|
title: 'The document <body> must not be aria-hidden',
|
|
27
|
-
description:
|
|
27
|
+
description:
|
|
28
|
+
'Checks that <body> does not have aria-hidden="true", which would remove the entire page from the accessibility tree.',
|
|
28
29
|
i18n: {
|
|
29
30
|
titleKey: 'ariaHiddenBody_title',
|
|
30
31
|
descriptionKey: 'ariaHiddenBody_description'
|
|
@@ -33,8 +34,20 @@ const meta = {
|
|
|
33
34
|
tags: ['wcag2a', 'wcag131', 'wcag412', 'structure', 'atomic', 'automatic'],
|
|
34
35
|
wcagSc: ['1.3.1', '4.1.2'],
|
|
35
36
|
normativeMappings: [
|
|
36
|
-
{
|
|
37
|
-
|
|
37
|
+
{
|
|
38
|
+
standard: 'WCAG',
|
|
39
|
+
version: '2.2',
|
|
40
|
+
requirement: '1.3.1',
|
|
41
|
+
title: 'Info and Relationships',
|
|
42
|
+
conformanceLevel: 'A'
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
standard: 'WCAG',
|
|
46
|
+
version: '2.2',
|
|
47
|
+
requirement: '4.1.2',
|
|
48
|
+
title: 'Name, Role, Value',
|
|
49
|
+
conformanceLevel: 'A'
|
|
50
|
+
}
|
|
38
51
|
],
|
|
39
52
|
defaultSeverity: 'critical',
|
|
40
53
|
category: 'perceivable',
|
|
@@ -48,6 +61,14 @@ const meta = {
|
|
|
48
61
|
}
|
|
49
62
|
};
|
|
50
63
|
|
|
64
|
+
// This check is inherently whole-document (does the PAGE have this
|
|
65
|
+
// property?), not evaluable per-subtree -- notApplicable when contextSelector
|
|
66
|
+
// scoped this run narrower than the whole document, or when
|
|
67
|
+
// engineOptions.fragment:true was set (see helpers.isWholeDocumentScope).
|
|
68
|
+
function applicability(ctx) {
|
|
69
|
+
return ctx.helpers.isWholeDocumentScope ? ctx.helpers.isWholeDocumentScope() : true;
|
|
70
|
+
}
|
|
71
|
+
|
|
51
72
|
function runInPage(ctx) {
|
|
52
73
|
const { document, helpers, rule } = ctx;
|
|
53
74
|
|
|
@@ -64,24 +85,34 @@ function runInPage(ctx) {
|
|
|
64
85
|
}
|
|
65
86
|
|
|
66
87
|
const stableSelector = helpers.buildSelector ? helpers.buildSelector(body) : 'body';
|
|
67
|
-
const html = helpers.getOuterHtmlSnippet
|
|
88
|
+
const html = helpers.getOuterHtmlSnippet
|
|
89
|
+
? helpers.getOuterHtmlSnippet(body)
|
|
90
|
+
: (body.outerHTML || '').slice(0, 200);
|
|
68
91
|
|
|
69
|
-
const occurrences = [
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
92
|
+
const occurrences = [
|
|
93
|
+
{
|
|
94
|
+
selector: stableSelector,
|
|
95
|
+
html,
|
|
96
|
+
summary:
|
|
97
|
+
'The document body has aria-hidden="true", which hides the entire page from assistive technologies.',
|
|
98
|
+
hint: 'Remove aria-hidden from <body>. Hide specific elements instead, if that was the intent.',
|
|
99
|
+
i18n: {
|
|
100
|
+
summaryKey: 'ariaHiddenBody_summary_fail',
|
|
101
|
+
hintKey: 'ariaHiddenBody_hint_fail',
|
|
102
|
+
params: {}
|
|
103
|
+
},
|
|
104
|
+
data: {
|
|
105
|
+
details: { reasonCode: 'ARIA_HIDDEN_BODY' }
|
|
106
|
+
}
|
|
81
107
|
}
|
|
82
|
-
|
|
108
|
+
];
|
|
83
109
|
|
|
84
|
-
return {
|
|
110
|
+
return {
|
|
111
|
+
ruleId: rule.ruleId,
|
|
112
|
+
outcome: 'fail',
|
|
113
|
+
severity: rule.defaultSeverity || 'critical',
|
|
114
|
+
occurrences
|
|
115
|
+
};
|
|
85
116
|
}
|
|
86
117
|
|
|
87
|
-
module.exports = { id, meta, runInPage };
|
|
118
|
+
module.exports = { id, meta, runInPage, applicability };
|