@surea11y/core 1.6.0 → 1.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (120) hide show
  1. package/CHANGELOG.md +140 -0
  2. package/README.md +179 -90
  3. package/docs/ACT_RULE_MAPPING.md +10 -8
  4. package/docs/API_STABILITY.md +67 -6
  5. package/docs/BINDING_AUTHORS_GUIDE.md +104 -2
  6. package/docs/CI_INTEGRATIONS.md +43 -0
  7. package/docs/DESIGN_CHALLENGES.md +162 -2
  8. package/docs/EARL.md +100 -0
  9. package/docs/ENGINE_OPTIONS.md +109 -5
  10. package/docs/I18N.md +62 -20
  11. package/docs/INTEGRATION.md +4 -2
  12. package/docs/JUNIT.md +73 -0
  13. package/docs/LIMITATIONS.md +4 -1
  14. package/docs/OUTPUT_SCHEMA.md +62 -11
  15. package/docs/POLICY.md +1 -1
  16. package/docs/REPORT.md +7 -2
  17. package/docs/RULE_AUTHORING.md +83 -17
  18. package/docs/RULE_CATALOG.md +212 -139
  19. package/docs/RULE_EXAMPLES.md +2189 -0
  20. package/docs/RULE_HELPERS.md +390 -0
  21. package/docs/RULE_TAXONOMY.md +27 -6
  22. package/docs/SARIF.md +23 -3
  23. package/docs/WCAG_CONFORMANCE.md +64 -3
  24. package/package.json +41 -12
  25. package/profiles/index.js +14 -0
  26. package/src/checks/automatic/area-alt-present.js +87 -31
  27. package/src/checks/automatic/aria-allowed-attr.js +6 -0
  28. package/src/checks/automatic/aria-allowed-role.js +32 -23
  29. package/src/checks/automatic/aria-braille-equivalent.js +43 -17
  30. package/src/checks/automatic/aria-conditional-attr.js +17 -10
  31. package/src/checks/automatic/aria-deprecated-role.js +12 -0
  32. package/src/checks/automatic/aria-hidden-body.js +1 -1
  33. package/src/checks/automatic/aria-hidden-focus.js +74 -18
  34. package/src/checks/automatic/aria-prohibited-attr.js +22 -4
  35. package/src/checks/automatic/aria-prohibited-children.js +6 -6
  36. package/src/checks/automatic/aria-required-attr.js +88 -12
  37. package/src/checks/automatic/aria-required-children.js +33 -16
  38. package/src/checks/automatic/aria-required-parent.js +32 -6
  39. package/src/checks/automatic/aria-role-name-present.js +20 -3
  40. package/src/checks/automatic/aria-roles-valid.js +52 -21
  41. package/src/checks/automatic/aria-valid-attr-value.js +89 -24
  42. package/src/checks/automatic/aria-valid-attr.js +14 -9
  43. package/src/checks/automatic/autocomplete-valid.js +152 -26
  44. package/src/checks/automatic/avoid-inline-spacing.js +207 -15
  45. package/src/checks/automatic/button-name-present.js +2 -1
  46. package/src/checks/automatic/canvas-text-alternative-present.js +105 -14
  47. package/src/checks/automatic/combobox-name-present.js +34 -51
  48. package/src/checks/automatic/contrast-computable.js +45 -4
  49. package/src/checks/automatic/contrast-enhanced.js +16 -4
  50. package/src/checks/automatic/contrast-minimum.js +57 -11
  51. package/src/checks/automatic/css-orientation-lock.js +171 -12
  52. package/src/checks/automatic/definition-list-children-valid.js +67 -23
  53. package/src/checks/automatic/deprecated-elements-not-used.js +43 -38
  54. package/src/checks/automatic/dialog-name-present.js +28 -9
  55. package/src/checks/automatic/duplicate-id-aria.js +5 -0
  56. package/src/checks/automatic/duplicate-id.js +19 -10
  57. package/src/checks/automatic/form-control-single-label.js +9 -0
  58. package/src/checks/automatic/identical-iframes-same-purpose.js +229 -0
  59. package/src/checks/automatic/iframe-focusable-content.js +12 -4
  60. package/src/checks/automatic/iframe-title-unique.js +36 -81
  61. package/src/checks/automatic/input-image-alt-present.js +32 -20
  62. package/src/checks/automatic/label-in-name.js +78 -69
  63. package/src/checks/automatic/language-page-present.js +12 -6
  64. package/src/checks/automatic/link-in-text-block.js +512 -44
  65. package/src/checks/automatic/link-name-present.js +13 -5
  66. package/src/checks/automatic/list-children-valid.js +18 -1
  67. package/src/checks/automatic/listbox-name-present.js +19 -49
  68. package/src/checks/automatic/listitem-parent-valid.js +4 -3
  69. package/src/checks/automatic/meta-refresh-no-exceptions.js +3 -3
  70. package/src/checks/automatic/page-title-present.js +16 -4
  71. package/src/checks/automatic/progressbar-name-present.js +11 -1
  72. package/src/checks/automatic/{role-img-alt-present.js → role-img-text-alternative-present.js} +9 -5
  73. package/src/checks/automatic/searchbox-name-present.js +32 -49
  74. package/src/checks/automatic/server-side-image-map-absent.js +48 -28
  75. package/src/checks/automatic/slider-name-present.js +38 -52
  76. package/src/checks/automatic/spinbutton-name-present.js +32 -49
  77. package/src/checks/automatic/target-size-minimum.js +84 -16
  78. package/src/checks/automatic/td-has-header.js +60 -23
  79. package/src/checks/automatic/text-spacing-content-loss.js +548 -0
  80. package/src/checks/automatic/textbox-name-present.js +32 -49
  81. package/src/checks/automatic/valid-lang.js +15 -10
  82. package/src/checks/manual/area-alt-quality-manual.js +113 -31
  83. package/src/checks/manual/canvas-text-alternative-quality-manual.js +0 -2
  84. package/src/checks/manual/css-focus-indicator-suppressed-manual.js +23 -10
  85. package/src/checks/manual/css-hidden-focus.js +215 -7
  86. package/src/checks/manual/form-control-label-quality-manual.js +243 -29
  87. package/src/checks/manual/heading-order-manual.js +9 -1
  88. package/src/checks/manual/heading-quality-manual.js +143 -9
  89. package/src/checks/manual/img-alt-decorative-manual.js +6 -3
  90. package/src/checks/manual/input-image-alt-quality-manual.js +81 -13
  91. package/src/checks/manual/landmark-complementary-is-top-level-manual.js +231 -0
  92. package/src/checks/manual/link-name-quality-manual.js +130 -4
  93. package/src/checks/manual/media-transcript-present-manual.js +65 -8
  94. package/src/checks/manual/mouse-only-event-handlers-manual.js +66 -5
  95. package/src/checks/manual/no-autoplay-audio-manual.js +93 -6
  96. package/src/checks/manual/p-as-heading-manual.js +89 -44
  97. package/src/checks/manual/page-title-patterns-manual.js +77 -8
  98. package/src/checks/manual/password-paste-enabled-manual.js +255 -0
  99. package/src/checks/manual/skip-link-manual.js +42 -14
  100. package/src/checks/manual/table-fake-caption-manual.js +32 -1
  101. package/src/checks/manual/video-caption-manual.js +47 -24
  102. package/src/checks/manual-review.js +0 -4
  103. package/src/core.js +18061 -46194
  104. package/src/coverage/en301549-map.js +187 -0
  105. package/src/coverage/standards.js +279 -0
  106. package/src/coverage/wcag-facets.js +1119 -0
  107. package/src/coverage/wcag-version-map.js +101 -0
  108. package/src/earl.js +144 -0
  109. package/src/en301549.js +33 -0
  110. package/src/junit.js +321 -0
  111. package/src/profile-kit.js +163 -0
  112. package/src/report.js +343 -74
  113. package/src/sarif.js +56 -5
  114. package/src/wcag.js +105 -0
  115. package/surea11y.browser.js +11 -41039
  116. package/surea11y.i18n.de.js +2 -21
  117. package/surea11y.i18n.es.js +2 -21
  118. package/surea11y.i18n.fr.js +2 -21
  119. package/surea11y.i18n.ja.js +3 -0
  120. package/src/checks/manual/area-alt-decorative-manual.js +0 -255
@@ -0,0 +1,163 @@
1
+ /* SPDX-License-Identifier: MPL-2.0 */
2
+
3
+ 'use strict';
4
+
5
+ /**
6
+ * The mapping of a standard whose requirements are linked to rules one by one,
7
+ * as a profile made with `npm run profile:new` describes it: what each result
8
+ * names, one rollup per requirement, and the checks the build runs on the
9
+ * tables. A profile hands it its tables and puts what it returns in its
10
+ * registry entry (ENTRY SHAPE in src/coverage/standards.js), so the code is
11
+ * core's, tested once, and a fix reaches every profile that uses it.
12
+ *
13
+ * The tables:
14
+ *
15
+ * - versions: [{ version, wcagVersion }], oldest first: the standard's version
16
+ * and the WCAG version it is built on ('2.0', '2.1' or '2.2').
17
+ * - requirements[version][id]: { title, wcagSc }. `id` is the standard's own
18
+ * number ('1.2', 'B4'...), `title` its wording, and `wcagSc` the WCAG
19
+ * criteria it corresponds to, each one a criterion of that WCAG version.
20
+ * - ruleMap[version][ruleId]: { requirements: [id, ...], note }, the
21
+ * requirements a rule checks: a core rule or one of the profile's own.
22
+ *
23
+ * A standard whose mapping needs more (tests grouped into criteria, related
24
+ * to WCAG many to many, say) writes its own functions instead.
25
+ *
26
+ * It loads no engine code, so a profile's own files may require it
27
+ * (profiles/README.md, "What a profile may use").
28
+ */
29
+
30
+ const { WCAG_VERSIONS, wcagCriterion, wcagTags } = require('./wcag.js');
31
+
32
+ // Requirement ids in their natural order: '1.2' before '1.10'.
33
+ function compareIds(a, b) {
34
+ return String(a).localeCompare(String(b), 'en', { numeric: true });
35
+ }
36
+
37
+ /**
38
+ * { mappingsFor, composites, validate, wcagTagsOf } for the registry entry of
39
+ * a standard named `standard`, whose rules and rollups carry `tag`.
40
+ */
41
+ function ruleMappedStandard({ standard, tag, versions, requirements, ruleMap }) {
42
+ function requirementsOf(version, ruleId) {
43
+ const row = (ruleMap[version] || {})[ruleId];
44
+ return row && Array.isArray(row.requirements) ? row.requirements : [];
45
+ }
46
+
47
+ function entry(version, id) {
48
+ const req = requirements[version][id];
49
+ return {
50
+ standard,
51
+ version,
52
+ requirement: id,
53
+ title: req.title,
54
+ wcagSc: Array.isArray(req.wcagSc) ? req.wcagSc.slice() : []
55
+ };
56
+ }
57
+
58
+ // The entries for a rule ({ id }) or, given `checksIds`, for a rollup: the
59
+ // requirements its rules check, oldest version first.
60
+ function mappingsFor({ id, checksIds }) {
61
+ const out = [];
62
+ for (const { version } of versions) {
63
+ const ids = new Set();
64
+ for (const ruleId of Array.isArray(checksIds) ? checksIds : [id]) {
65
+ for (const req of requirementsOf(version, ruleId)) ids.add(req);
66
+ }
67
+ for (const req of [...ids].sort(compareIds)) out.push(entry(version, req));
68
+ }
69
+ return out;
70
+ }
71
+
72
+ // One rollup per requirement a rule checks, grouping those rules. They carry
73
+ // the standard's tag, so only a run that asks for it produces them.
74
+ function composites() {
75
+ const out = [];
76
+ for (const { version } of versions) {
77
+ const table = ruleMap[version] || {};
78
+ for (const id of Object.keys(requirements[version] || {}).sort(compareIds)) {
79
+ const ruleIds = Object.keys(table)
80
+ .filter((ruleId) => requirementsOf(version, ruleId).includes(id))
81
+ .sort();
82
+ if (!ruleIds.length) continue;
83
+ out.push({
84
+ id: `${tag}-${version}-${id}`,
85
+ checksIds: ruleIds,
86
+ meta: {
87
+ title: requirements[version][id].title,
88
+ description: '',
89
+ wcagSc: [],
90
+ level: null,
91
+ standard,
92
+ version,
93
+ criterion: id,
94
+ tags: [tag],
95
+ standardMappings: [entry(version, id)]
96
+ }
97
+ });
98
+ }
99
+ }
100
+ return out;
101
+ }
102
+
103
+ // Problems with the tables, given every rule ([{ ruleId, wcagSc }]). The
104
+ // build fails on any.
105
+ function validate(rules) {
106
+ const known = new Set(rules.map((r) => r.ruleId));
107
+ const problems = [];
108
+ const listed = versions.map((v) => v.version);
109
+ for (const { version, wcagVersion } of versions) {
110
+ if (!requirements[version]) {
111
+ problems.push(`version ${version} has no requirements table`);
112
+ continue;
113
+ }
114
+ if (!WCAG_VERSIONS.includes(wcagVersion)) {
115
+ problems.push(`version ${version}: wcagVersion must be one of ${WCAG_VERSIONS.join(', ')}`);
116
+ continue;
117
+ }
118
+ // A requirement corresponds to criteria of the WCAG version it is built on.
119
+ for (const [id, req] of Object.entries(requirements[version])) {
120
+ for (const sc of (req && req.wcagSc) || []) {
121
+ if (!wcagCriterion(sc, wcagVersion)) {
122
+ problems.push(`${version} ${id}: WCAG ${wcagVersion} has no criterion ${sc}`);
123
+ }
124
+ }
125
+ }
126
+ }
127
+ for (const [version, table] of Object.entries(ruleMap)) {
128
+ if (!listed.includes(version)) {
129
+ problems.push(`the rule map names version ${version}, which versions does not list`);
130
+ continue;
131
+ }
132
+ for (const [ruleId, row] of Object.entries(table)) {
133
+ if (!known.has(ruleId)) problems.push(`${version} ${ruleId}: no such rule`);
134
+ const reqs = row && Array.isArray(row.requirements) ? row.requirements : null;
135
+ if (!reqs) {
136
+ problems.push(`${version} ${ruleId}: requirements must be a list`);
137
+ continue;
138
+ }
139
+ for (const id of reqs) {
140
+ if (!requirements[version] || !requirements[version][id]) {
141
+ problems.push(`${version} ${ruleId}: no requirement ${id}`);
142
+ }
143
+ }
144
+ if (new Set(reqs).size !== reqs.length) {
145
+ problems.push(`${version} ${ruleId}: a requirement is listed twice`);
146
+ }
147
+ }
148
+ }
149
+ return problems;
150
+ }
151
+
152
+ // The WCAG A and AA tags of the WCAG version a version is built on, for its
153
+ // profile in the registry entry.
154
+ function wcagTagsOf(version) {
155
+ const v = versions.find((x) => x.version === version);
156
+ if (!v) throw new Error(`${standard} has no version ${version}`);
157
+ return wcagTags(v.wcagVersion);
158
+ }
159
+
160
+ return { mappingsFor, composites, validate, wcagTagsOf };
161
+ }
162
+
163
+ module.exports = { ruleMappedStandard };