@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.
Files changed (160) hide show
  1. package/CHANGELOG.md +48 -4
  2. package/LICENSE +373 -21
  3. package/README.md +70 -1
  4. package/bin/core.js +240 -11
  5. package/docs/API_STABILITY.md +61 -0
  6. package/docs/BASELINE.md +66 -0
  7. package/docs/BINDING_AUTHORS_GUIDE.md +9 -9
  8. package/docs/CI_INTEGRATIONS.md +103 -0
  9. package/docs/CLI.md +80 -1
  10. package/docs/ENGINE_OPTIONS.md +4 -0
  11. package/docs/INTEGRATION.md +19 -1
  12. package/docs/OUTPUT_SCHEMA.md +2 -2
  13. package/docs/REPORT.md +33 -0
  14. package/docs/RULE_AUTHORING.md +31 -0
  15. package/docs/RULE_CATALOG.md +1 -1
  16. package/docs/SARIF.md +59 -0
  17. package/package.json +15 -4
  18. package/src/baseline.js +0 -0
  19. package/src/catalogs/composites.wcag.js +414 -450
  20. package/src/checks/automatic/area-alt-present.js +59 -25
  21. package/src/checks/automatic/aria-allowed-attr.js +193 -33
  22. package/src/checks/automatic/aria-allowed-role.js +21 -7
  23. package/src/checks/automatic/aria-braille-equivalent.js +32 -10
  24. package/src/checks/automatic/aria-conditional-attr.js +24 -7
  25. package/src/checks/automatic/aria-deprecated-role.js +22 -8
  26. package/src/checks/automatic/aria-hidden-body.js +50 -19
  27. package/src/checks/automatic/aria-hidden-focus.js +408 -53
  28. package/src/checks/automatic/aria-prohibited-attr.js +296 -22
  29. package/src/checks/automatic/aria-prohibited-children.js +125 -29
  30. package/src/checks/automatic/aria-required-attr.js +23 -8
  31. package/src/checks/automatic/aria-required-children.js +37 -14
  32. package/src/checks/automatic/aria-required-parent.js +48 -14
  33. package/src/checks/automatic/aria-role-name-present.js +47 -21
  34. package/src/checks/automatic/aria-roles-valid.js +22 -12
  35. package/src/checks/automatic/aria-valid-attr-value.js +28 -7
  36. package/src/checks/automatic/aria-valid-attr.js +17 -5
  37. package/src/checks/automatic/autocomplete-valid.js +74 -16
  38. package/src/checks/automatic/avoid-inline-spacing.js +20 -6
  39. package/src/checks/automatic/binary-control-name-present.js +60 -50
  40. package/src/checks/automatic/button-name-present.js +48 -18
  41. package/src/checks/automatic/bypass-blocks-present.js +50 -25
  42. package/src/checks/automatic/canvas-text-alternative-present.js +57 -26
  43. package/src/checks/automatic/combobox-name-present.js +38 -45
  44. package/src/checks/automatic/contrast-computable.js +361 -341
  45. package/src/checks/automatic/contrast-enhanced.js +487 -466
  46. package/src/checks/automatic/contrast-minimum.js +486 -465
  47. package/src/checks/automatic/css-orientation-lock.js +39 -9
  48. package/src/checks/automatic/definition-list-children-valid.js +40 -19
  49. package/src/checks/automatic/deprecated-elements-not-used.js +21 -7
  50. package/src/checks/automatic/dialog-name-present.js +37 -75
  51. package/src/checks/automatic/dlitem-parent-valid.js +23 -8
  52. package/src/checks/automatic/duplicate-id-aria.js +24 -6
  53. package/src/checks/automatic/embed-text-alternative-present.js +86 -35
  54. package/src/checks/automatic/form-control-programmatic-label-present.js +79 -196
  55. package/src/checks/automatic/form-control-single-label.js +47 -10
  56. package/src/checks/automatic/html-xml-lang-mismatch.js +43 -19
  57. package/src/checks/automatic/iframe-focusable-content.js +26 -11
  58. package/src/checks/automatic/iframe-name-present.js +31 -9
  59. package/src/checks/automatic/iframe-title-unique.js +29 -8
  60. package/src/checks/automatic/img-alt-present.js +47 -43
  61. package/src/checks/automatic/input-image-alt-present.js +143 -112
  62. package/src/checks/automatic/label-in-name.js +50 -22
  63. package/src/checks/automatic/language-page-present.js +117 -109
  64. package/src/checks/automatic/link-in-text-block.js +59 -19
  65. package/src/checks/automatic/link-name-present.js +45 -14
  66. package/src/checks/automatic/list-children-valid.js +26 -9
  67. package/src/checks/automatic/listbox-name-present.js +39 -19
  68. package/src/checks/automatic/listitem-parent-valid.js +18 -6
  69. package/src/checks/automatic/menuitem-name-present.js +39 -61
  70. package/src/checks/automatic/meta-refresh-no-exceptions.js +37 -8
  71. package/src/checks/automatic/meta-refresh-timing-absent.js +28 -6
  72. package/src/checks/automatic/meta-viewport-zoom-enabled.js +32 -7
  73. package/src/checks/automatic/meter-name-present.js +36 -33
  74. package/src/checks/automatic/nested-interactive-controls-absent.js +31 -10
  75. package/src/checks/automatic/object-text-alternative-present.js +91 -39
  76. package/src/checks/automatic/option-name-present.js +38 -21
  77. package/src/checks/automatic/page-title-present.js +26 -7
  78. package/src/checks/automatic/progressbar-name-present.js +41 -34
  79. package/src/checks/automatic/role-img-alt-present.js +209 -157
  80. package/src/checks/automatic/searchbox-name-present.js +39 -19
  81. package/src/checks/automatic/server-side-image-map-absent.js +23 -8
  82. package/src/checks/automatic/slider-name-present.js +40 -47
  83. package/src/checks/automatic/spinbutton-name-present.js +39 -19
  84. package/src/checks/automatic/summary-name-present.js +37 -17
  85. package/src/checks/automatic/svg-image-text-alternative-present.js +114 -47
  86. package/src/checks/automatic/svg-text-alternative-present.js +246 -226
  87. package/src/checks/automatic/tab-name-present.js +37 -60
  88. package/src/checks/automatic/table-headers-attr-valid.js +24 -8
  89. package/src/checks/automatic/table-th-has-data-cells.js +22 -8
  90. package/src/checks/automatic/target-size-minimum.js +118 -48
  91. package/src/checks/automatic/td-has-header.js +29 -11
  92. package/src/checks/automatic/textbox-name-present.js +39 -19
  93. package/src/checks/automatic/tooltip-name-present.js +37 -18
  94. package/src/checks/automatic/treeitem-name-present.js +38 -21
  95. package/src/checks/automatic/valid-lang.js +20 -6
  96. package/src/checks/automatic/video-poster-text-alternative-present.js +79 -36
  97. package/src/checks/manual/accesskeys-manual.js +14 -5
  98. package/src/checks/manual/area-alt-decorative-manual.js +192 -193
  99. package/src/checks/manual/area-alt-quality-manual.js +182 -141
  100. package/src/checks/manual/aria-checked-state-mismatch-manual.js +34 -11
  101. package/src/checks/manual/aria-text-manual.js +14 -6
  102. package/src/checks/manual/canvas-text-alternative-quality-manual.js +149 -114
  103. package/src/checks/manual/css-hidden-focus.js +196 -165
  104. package/src/checks/manual/embed-text-alternative-quality-manual.js +171 -160
  105. package/src/checks/manual/empty-heading-manual.js +24 -12
  106. package/src/checks/manual/empty-table-header-manual.js +21 -14
  107. package/src/checks/manual/focus-order-semantics-manual.js +45 -10
  108. package/src/checks/manual/form-control-programmatic-label-quality-manual.js +207 -246
  109. package/src/checks/manual/heading-order-manual.js +22 -12
  110. package/src/checks/manual/identical-links-same-purpose-manual.js +34 -12
  111. package/src/checks/manual/image-redundant-alt-manual.js +17 -7
  112. package/src/checks/manual/img-alt-decorative-manual.js +131 -96
  113. package/src/checks/manual/img-alt-quality-manual.js +176 -127
  114. package/src/checks/manual/input-image-alt-decorative-manual.js +125 -92
  115. package/src/checks/manual/input-image-alt-quality-manual.js +125 -92
  116. package/src/checks/manual/label-title-only-manual.js +14 -5
  117. package/src/checks/manual/landmark-banner-is-top-level-manual.js +95 -29
  118. package/src/checks/manual/landmark-contentinfo-is-top-level-manual.js +82 -29
  119. package/src/checks/manual/landmark-main-is-top-level-manual.js +64 -23
  120. package/src/checks/manual/landmark-no-duplicate-banner-manual.js +54 -23
  121. package/src/checks/manual/landmark-no-duplicate-contentinfo-manual.js +54 -23
  122. package/src/checks/manual/landmark-no-duplicate-main-manual.js +17 -8
  123. package/src/checks/manual/landmark-one-main-manual.js +35 -21
  124. package/src/checks/manual/landmark-unique-manual.js +73 -45
  125. package/src/checks/manual/link-name-quality-manual.js +43 -12
  126. package/src/checks/manual/media-transcript-present-manual.js +35 -22
  127. package/src/checks/manual/meta-viewport-large-manual.js +25 -6
  128. package/src/checks/manual/mouse-only-event-handlers-manual.js +38 -11
  129. package/src/checks/manual/no-autoplay-audio-manual.js +20 -6
  130. package/src/checks/manual/object-text-alternative-quality-manual.js +175 -154
  131. package/src/checks/manual/p-as-heading-manual.js +22 -7
  132. package/src/checks/manual/page-has-heading-one-manual.js +40 -23
  133. package/src/checks/manual/page-title-patterns-manual.js +87 -51
  134. package/src/checks/manual/presentation-role-conflict-manual.js +59 -19
  135. package/src/checks/manual/region-manual.js +275 -62
  136. package/src/checks/manual/scope-attr-valid-manual.js +11 -9
  137. package/src/checks/manual/scrollable-region-focusable-manual.js +37 -11
  138. package/src/checks/manual/skip-link-manual.js +35 -12
  139. package/src/checks/manual/svg-text-alternative-quality-manual.js +206 -165
  140. package/src/checks/manual/tabindex-manual.js +11 -9
  141. package/src/checks/manual/table-duplicate-name-manual.js +17 -7
  142. package/src/checks/manual/table-fake-caption-manual.js +24 -7
  143. package/src/checks/manual/video-caption-manual.js +15 -4
  144. package/src/checks/manual-review.js +56 -12
  145. package/src/core/aria-helpers.js +1128 -823
  146. package/src/core/contrast-helpers.js +1217 -1062
  147. package/src/core/dom-helpers.js +4176 -3886
  148. package/src/core/dom-runner.js +720 -596
  149. package/src/core/frame-messaging.js +189 -138
  150. package/src/core/frame-scan.js +94 -82
  151. package/src/core/rollup-composites.js +94 -102
  152. package/src/core/rule-meta.js +71 -35
  153. package/src/core.js +37939 -29121
  154. package/src/i18n/en.js +1194 -887
  155. package/src/i18n/fr.js +1136 -793
  156. package/src/policy/contracts.js +13 -13
  157. package/src/policy/resolvePolicy.js +48 -44
  158. package/src/report.js +502 -0
  159. package/src/sarif.js +175 -0
  160. 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
- { standard: 'WCAG', version: '2.2', requirement: '4.1.2', title: 'Name, Role, Value', conformanceLevel: 'A' }
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 { document, helpers, rule } = ctx;
62
+ const { helpers, rule } = ctx;
57
63
 
58
- function trim(v) { return (v == null ? '' : String(v)).trim(); }
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) : (el.outerHTML || '');
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: 'This element has aria-errormessage but aria-invalid is missing or "false", so the error message is not exposed.',
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: { reasonCode: 'ARIA_ERRORMESSAGE_WITHOUT_TRUTHY_INVALID', ariaInvalid: invalidValue }
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 { ruleId: rule.ruleId, outcome: 'fail', severity: rule.defaultSeverity || 'serious', occurrences };
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: '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").',
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
- { standard: 'WCAG', version: '2.2', requirement: '4.1.2', title: 'Name, Role, Value', conformanceLevel: 'A' }
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 { document, helpers, rule } = ctx;
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 ? helpers.queryAllSmart('[role]') : helpers.queryAll('[role]');
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) : (el.outerHTML || '');
84
+ const html = helpers.getOuterHtmlSnippet ? helpers.getOuterHtmlSnippet(el) : el.outerHTML || '';
76
85
  const guidance = ariaHelpers.getDeprecatedRoleGuidance
77
- ? ariaHelpers.getDeprecatedRoleGuidance(role)
78
- : 'Replace the deprecated role with its recommended replacement.';
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 { ruleId: rule.ruleId, outcome: 'fail', severity: rule.defaultSeverity || 'moderate', occurrences };
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: 'Checks that <body> does not have aria-hidden="true", which would remove the entire page from the accessibility tree.',
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
- { standard: 'WCAG', version: '2.2', requirement: '1.3.1', title: 'Info and Relationships', conformanceLevel: 'A' },
37
- { standard: 'WCAG', version: '2.2', requirement: '4.1.2', title: 'Name, Role, Value', conformanceLevel: 'A' }
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 ? helpers.getOuterHtmlSnippet(body) : (body.outerHTML || '').slice(0, 200);
88
+ const html = helpers.getOuterHtmlSnippet
89
+ ? helpers.getOuterHtmlSnippet(body)
90
+ : (body.outerHTML || '').slice(0, 200);
68
91
 
69
- const occurrences = [{
70
- selector: stableSelector,
71
- html,
72
- summary: 'The document body has aria-hidden="true", which hides the entire page from assistive technologies.',
73
- hint: 'Remove aria-hidden from <body>. Hide specific elements instead, if that was the intent.',
74
- i18n: {
75
- summaryKey: 'ariaHiddenBody_summary_fail',
76
- hintKey: 'ariaHiddenBody_hint_fail',
77
- params: {}
78
- },
79
- data: {
80
- details: { reasonCode: 'ARIA_HIDDEN_BODY' }
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 { ruleId: rule.ruleId, outcome: 'fail', severity: rule.defaultSeverity || 'critical', occurrences };
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 };