@surea11y/core 1.2.0 → 1.4.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 (168) hide show
  1. package/CHANGELOG.md +81 -7
  2. package/LICENSE +373 -21
  3. package/README.md +175 -35
  4. package/bin/surea11y-core.js +20 -0
  5. package/docs/API_STABILITY.md +27 -1
  6. package/docs/BINDING_AUTHORS_GUIDE.md +9 -9
  7. package/docs/CI_INTEGRATIONS.md +103 -0
  8. package/docs/ENGINE_OPTIONS.md +2 -0
  9. package/docs/I18N.md +12 -9
  10. package/docs/INTEGRATION.md +19 -1
  11. package/docs/LIMITATIONS.md +1 -1
  12. package/docs/OUTPUT_SCHEMA.md +1 -1
  13. package/docs/REPORT.md +1 -1
  14. package/docs/RULE_CATALOG.md +1 -1
  15. package/docs/SARIF.md +59 -0
  16. package/package.json +63 -18
  17. package/src/baseline.js +0 -0
  18. package/src/checks/automatic/area-alt-present.js +63 -31
  19. package/src/checks/automatic/aria-allowed-attr.js +204 -80
  20. package/src/checks/automatic/aria-allowed-role.js +23 -7
  21. package/src/checks/automatic/aria-braille-equivalent.js +34 -10
  22. package/src/checks/automatic/aria-conditional-attr.js +32 -14
  23. package/src/checks/automatic/aria-deprecated-role.js +26 -11
  24. package/src/checks/automatic/aria-hidden-body.js +48 -23
  25. package/src/checks/automatic/aria-hidden-focus.js +420 -66
  26. package/src/checks/automatic/aria-prohibited-attr.js +327 -60
  27. package/src/checks/automatic/aria-prohibited-children.js +111 -103
  28. package/src/checks/automatic/aria-required-attr.js +29 -15
  29. package/src/checks/automatic/aria-required-children.js +44 -24
  30. package/src/checks/automatic/aria-required-parent.js +64 -35
  31. package/src/checks/automatic/aria-role-name-present.js +49 -21
  32. package/src/checks/automatic/aria-roles-valid.js +24 -12
  33. package/src/checks/automatic/aria-valid-attr-value.js +46 -22
  34. package/src/checks/automatic/aria-valid-attr.js +19 -5
  35. package/src/checks/automatic/autocomplete-valid.js +76 -16
  36. package/src/checks/automatic/avoid-inline-spacing.js +23 -8
  37. package/src/checks/automatic/binary-control-name-present.js +62 -50
  38. package/src/checks/automatic/button-name-present.js +54 -24
  39. package/src/checks/automatic/bypass-blocks-present.js +51 -32
  40. package/src/checks/automatic/canvas-text-alternative-present.js +59 -26
  41. package/src/checks/automatic/combobox-name-present.js +40 -45
  42. package/src/checks/automatic/contrast-computable.js +363 -341
  43. package/src/checks/automatic/contrast-enhanced.js +489 -466
  44. package/src/checks/automatic/contrast-minimum.js +488 -465
  45. package/src/checks/automatic/css-orientation-lock.js +51 -35
  46. package/src/checks/automatic/definition-list-children-valid.js +46 -25
  47. package/src/checks/automatic/deprecated-elements-not-used.js +25 -9
  48. package/src/checks/automatic/dialog-name-present.js +47 -85
  49. package/src/checks/automatic/dlitem-parent-valid.js +25 -8
  50. package/src/checks/automatic/duplicate-id-aria.js +28 -9
  51. package/src/checks/automatic/embed-text-alternative-present.js +88 -35
  52. package/src/checks/automatic/form-control-programmatic-label-present.js +81 -196
  53. package/src/checks/automatic/form-control-single-label.js +50 -14
  54. package/src/checks/automatic/html-xml-lang-mismatch.js +36 -18
  55. package/src/checks/automatic/iframe-focusable-content.js +265 -22
  56. package/src/checks/automatic/iframe-name-present.js +33 -9
  57. package/src/checks/automatic/iframe-title-unique.js +32 -9
  58. package/src/checks/automatic/img-alt-present.js +54 -52
  59. package/src/checks/automatic/input-image-alt-present.js +141 -112
  60. package/src/checks/automatic/label-in-name.js +65 -41
  61. package/src/checks/automatic/language-page-present.js +111 -109
  62. package/src/checks/automatic/link-in-text-block.js +61 -19
  63. package/src/checks/automatic/link-name-present.js +47 -14
  64. package/src/checks/automatic/list-children-valid.js +40 -33
  65. package/src/checks/automatic/listbox-name-present.js +41 -19
  66. package/src/checks/automatic/listitem-parent-valid.js +48 -13
  67. package/src/checks/automatic/menuitem-name-present.js +41 -61
  68. package/src/checks/automatic/meta-refresh-no-exceptions.js +32 -11
  69. package/src/checks/automatic/meta-refresh-timing-absent.js +22 -6
  70. package/src/checks/automatic/meta-viewport-zoom-enabled.js +26 -7
  71. package/src/checks/automatic/meter-name-present.js +40 -36
  72. package/src/checks/automatic/nested-interactive-controls-absent.js +58 -15
  73. package/src/checks/automatic/object-text-alternative-present.js +93 -39
  74. package/src/checks/automatic/option-name-present.js +40 -21
  75. package/src/checks/automatic/page-title-present.js +19 -6
  76. package/src/checks/automatic/progressbar-name-present.js +49 -44
  77. package/src/checks/automatic/role-img-alt-present.js +211 -159
  78. package/src/checks/automatic/searchbox-name-present.js +41 -19
  79. package/src/checks/automatic/server-side-image-map-absent.js +27 -11
  80. package/src/checks/automatic/slider-name-present.js +42 -47
  81. package/src/checks/automatic/spinbutton-name-present.js +41 -19
  82. package/src/checks/automatic/summary-name-present.js +39 -17
  83. package/src/checks/automatic/svg-image-text-alternative-present.js +116 -47
  84. package/src/checks/automatic/svg-text-alternative-present.js +262 -230
  85. package/src/checks/automatic/tab-name-present.js +39 -60
  86. package/src/checks/automatic/table-headers-attr-valid.js +27 -10
  87. package/src/checks/automatic/table-th-has-data-cells.js +24 -8
  88. package/src/checks/automatic/target-size-minimum.js +123 -48
  89. package/src/checks/automatic/td-has-header.js +53 -12
  90. package/src/checks/automatic/textbox-name-present.js +41 -19
  91. package/src/checks/automatic/tooltip-name-present.js +39 -18
  92. package/src/checks/automatic/treeitem-name-present.js +40 -21
  93. package/src/checks/automatic/valid-lang.js +22 -6
  94. package/src/checks/automatic/video-poster-text-alternative-present.js +81 -36
  95. package/src/checks/manual/accesskeys-manual.js +17 -6
  96. package/src/checks/manual/area-alt-decorative-manual.js +194 -193
  97. package/src/checks/manual/area-alt-quality-manual.js +184 -141
  98. package/src/checks/manual/aria-checked-state-mismatch-manual.js +48 -34
  99. package/src/checks/manual/aria-text-manual.js +20 -11
  100. package/src/checks/manual/canvas-text-alternative-quality-manual.js +151 -114
  101. package/src/checks/manual/css-hidden-focus.js +375 -169
  102. package/src/checks/manual/embed-text-alternative-quality-manual.js +178 -162
  103. package/src/checks/manual/empty-heading-manual.js +41 -24
  104. package/src/checks/manual/empty-table-header-manual.js +69 -31
  105. package/src/checks/manual/focus-order-semantics-manual.js +60 -13
  106. package/src/checks/manual/form-control-programmatic-label-quality-manual.js +209 -246
  107. package/src/checks/manual/heading-order-manual.js +50 -8
  108. package/src/checks/manual/identical-links-same-purpose-manual.js +36 -12
  109. package/src/checks/manual/image-redundant-alt-manual.js +38 -8
  110. package/src/checks/manual/img-alt-decorative-manual.js +133 -96
  111. package/src/checks/manual/img-alt-quality-manual.js +178 -127
  112. package/src/checks/manual/input-image-alt-decorative-manual.js +127 -92
  113. package/src/checks/manual/input-image-alt-quality-manual.js +127 -92
  114. package/src/checks/manual/label-title-only-manual.js +44 -28
  115. package/src/checks/manual/landmark-banner-is-top-level-manual.js +95 -38
  116. package/src/checks/manual/landmark-contentinfo-is-top-level-manual.js +85 -32
  117. package/src/checks/manual/landmark-main-is-top-level-manual.js +69 -27
  118. package/src/checks/manual/landmark-no-duplicate-banner-manual.js +45 -33
  119. package/src/checks/manual/landmark-no-duplicate-contentinfo-manual.js +43 -31
  120. package/src/checks/manual/landmark-no-duplicate-main-manual.js +27 -21
  121. package/src/checks/manual/landmark-one-main-manual.js +38 -43
  122. package/src/checks/manual/landmark-unique-manual.js +78 -67
  123. package/src/checks/manual/link-name-quality-manual.js +45 -12
  124. package/src/checks/manual/media-transcript-present-manual.js +37 -22
  125. package/src/checks/manual/meta-viewport-large-manual.js +19 -6
  126. package/src/checks/manual/mouse-only-event-handlers-manual.js +40 -11
  127. package/src/checks/manual/no-autoplay-audio-manual.js +22 -6
  128. package/src/checks/manual/object-text-alternative-quality-manual.js +177 -154
  129. package/src/checks/manual/p-as-heading-manual.js +24 -7
  130. package/src/checks/manual/page-has-heading-one-manual.js +42 -32
  131. package/src/checks/manual/page-title-patterns-manual.js +80 -50
  132. package/src/checks/manual/presentation-role-conflict-manual.js +101 -47
  133. package/src/checks/manual/region-manual.js +244 -60
  134. package/src/checks/manual/scope-attr-valid-manual.js +13 -4
  135. package/src/checks/manual/scrollable-region-focusable-manual.js +39 -11
  136. package/src/checks/manual/skip-link-manual.js +42 -18
  137. package/src/checks/manual/svg-text-alternative-quality-manual.js +208 -165
  138. package/src/checks/manual/tabindex-manual.js +13 -4
  139. package/src/checks/manual/table-duplicate-name-manual.js +22 -11
  140. package/src/checks/manual/table-fake-caption-manual.js +48 -10
  141. package/src/checks/manual/video-caption-manual.js +17 -4
  142. package/src/checks/manual-review.js +58 -12
  143. package/src/core.js +41705 -29650
  144. package/src/index.js +2 -0
  145. package/src/report.js +109 -47
  146. package/src/sarif.js +190 -0
  147. package/surea11y.browser.js +37774 -0
  148. package/bin/core.js +0 -348
  149. package/docs/CLI.md +0 -75
  150. package/src/catalogs/composites.wcag.js +0 -490
  151. package/src/checks/rules-and-tags.full.csv +0 -19
  152. package/src/checks/rules-and-tags.full.json +0 -259
  153. package/src/core/aria-helpers.js +0 -970
  154. package/src/core/contrast-helpers.js +0 -1147
  155. package/src/core/dom-helpers.js +0 -4235
  156. package/src/core/dom-runner.js +0 -671
  157. package/src/core/frame-messaging.js +0 -210
  158. package/src/core/frame-scan.js +0 -178
  159. package/src/core/rollup-composites.js +0 -135
  160. package/src/core/rule-meta.js +0 -159
  161. package/src/coverage/wcag-facets.js +0 -1079
  162. package/src/coverage/wcag-version-map.js +0 -84
  163. package/src/i18n/en.js +0 -923
  164. package/src/i18n/fr.js +0 -844
  165. package/src/policy/contracts.js +0 -18
  166. package/src/policy/resolvePolicy.js +0 -55
  167. package/src/policy/schemas/engine-options.schema.json +0 -103
  168. package/src/policy/schemas/policy-contract.schema.json +0 -40
@@ -1,3 +1,5 @@
1
+ /* SPDX-License-Identifier: MPL-2.0 */
2
+
1
3
  'use strict';
2
4
 
3
5
  /**
@@ -11,20 +13,27 @@
11
13
  * Human review is required to confirm that the provided text alternative is accurate and appropriate.
12
14
  */
13
15
 
14
- const id = "canvas-text-alternative-quality";
16
+ const id = 'canvas-text-alternative-quality';
15
17
 
16
18
  const meta = {
17
- title: "<canvas> text alternative must be appropriate (manual review)",
18
- description: "Flags <canvas> elements with a detected text alternative for human review of equivalence and appropriateness.",
19
+ title: '<canvas> text alternative must be appropriate (manual review)',
20
+ description:
21
+ 'Flags <canvas> elements with a detected text alternative for human review of equivalence and appropriateness.',
19
22
  i18n: {
20
- titleKey: "canvas_textAltQuality_title",
21
- descriptionKey: "canvas_textAltQuality_description"
23
+ titleKey: 'canvas_textAltQuality_title',
24
+ descriptionKey: 'canvas_textAltQuality_description'
22
25
  },
23
26
  helpUrl: null,
24
- tags: ["wcag2a", "wcag111", "nontext", "canvas", "manual", "atomic"],
27
+ tags: ['wcag2a', 'wcag111', 'nontext', 'canvas', 'manual', 'atomic'],
25
28
  wcagSc: ['1.1.1'],
26
29
  normativeMappings: [
27
- { standard: 'WCAG', version: '2.2', requirement: '1.1.1', title: 'Non-text Content', conformanceLevel: 'A' }
30
+ {
31
+ standard: 'WCAG',
32
+ version: '2.2',
33
+ requirement: '1.1.1',
34
+ title: 'Non-text Content',
35
+ conformanceLevel: 'A'
36
+ }
28
37
  ],
29
38
  defaultSeverity: 'minor',
30
39
  category: 'perceivable',
@@ -38,133 +47,161 @@ const meta = {
38
47
  };
39
48
 
40
49
  function runInPage(ctx) {
41
-
42
- const { document, root, helpers, rule } = ctx;
43
- const safeRoot = root || document;
44
-
45
- const queryAllSmart = helpers && typeof helpers.queryAllSmart === 'function' ? helpers.queryAllSmart : null;
46
- const queryAll = helpers && typeof helpers.queryAll === 'function'
47
- ? helpers.queryAll
48
- : (sel) => {
49
- try { return safeRoot && safeRoot.querySelectorAll ? Array.from(safeRoot.querySelectorAll(sel)) : []; }
50
- catch { return []; }
50
+ const { document, root, helpers, rule } = ctx;
51
+ const safeRoot = root || document;
52
+
53
+ const queryAllSmart =
54
+ helpers && typeof helpers.queryAllSmart === 'function' ? helpers.queryAllSmart : null;
55
+ const queryAll =
56
+ helpers && typeof helpers.queryAll === 'function'
57
+ ? helpers.queryAll
58
+ : (sel) => {
59
+ try {
60
+ return safeRoot && safeRoot.querySelectorAll
61
+ ? Array.from(safeRoot.querySelectorAll(sel))
62
+ : [];
63
+ } catch {
64
+ return [];
65
+ }
51
66
  };
52
67
 
53
- const getEligibilityInfo = helpers && typeof helpers.getEligibilityInfo === 'function'
54
- ? helpers.getEligibilityInfo
55
- : null;
56
-
57
- const isAccTreeEligible = helpers && typeof helpers.isAccTreeEligible === 'function'
58
- ? helpers.isAccTreeEligible
59
- : null;
60
-
61
- const __accEligCache = new WeakMap();
62
- function accEligibleCached(node) {
63
- if (!isAccTreeEligible) return { eligible: true, reasons: [] };
64
- if (!node || typeof node !== 'object') return { eligible: true, reasons: [] };
65
- const c = __accEligCache.get(node);
66
- if (c) return c;
67
- let r;
68
- try { r = isAccTreeEligible(node, ctx); } catch { r = { eligible: true, reasons: [] }; }
69
- r = r && typeof r === 'object' ? r : { eligible: !!r, reasons: [] };
70
- __accEligCache.set(node, r);
71
- return r;
68
+ const getEligibilityInfo =
69
+ helpers && typeof helpers.getEligibilityInfo === 'function' ? helpers.getEligibilityInfo : null;
70
+
71
+ const isAccTreeEligible =
72
+ helpers && typeof helpers.isAccTreeEligible === 'function' ? helpers.isAccTreeEligible : null;
73
+
74
+ const __accEligCache = new WeakMap();
75
+ function accEligibleCached(node) {
76
+ if (!isAccTreeEligible) return { eligible: true, reasons: [] };
77
+ if (!node || typeof node !== 'object') return { eligible: true, reasons: [] };
78
+ const c = __accEligCache.get(node);
79
+ if (c) return c;
80
+ let r;
81
+ try {
82
+ r = isAccTreeEligible(node, ctx);
83
+ } catch {
84
+ r = { eligible: true, reasons: [] };
72
85
  }
86
+ r = r && typeof r === 'object' ? r : { eligible: !!r, reasons: [] };
87
+ __accEligCache.set(node, r);
88
+ return r;
89
+ }
73
90
 
74
- const getFocusableInfo = helpers && typeof helpers.getFocusableInfo === 'function'
75
- ? helpers.getFocusableInfo
76
- : null;
77
-
78
- function isRolePresentationExcluded(el) {
79
- const role = (() => {
80
- try { return String(el.getAttribute('role') || '').trim().toLowerCase(); }
81
- catch { return ''; }
82
- })();
83
- if (role !== 'presentation' && role !== 'none') return false;
84
-
85
- // Exclude only when NOT focusable (mirrors img-alt-present policy)
86
- let focusable = false;
87
- if (getFocusableInfo) {
88
- const fi = (() => { try { return getFocusableInfo(el, ctx); } catch { return null; } })();
89
- focusable = !!(fi && fi.focusable);
90
- } else {
91
- let tabindex = null;
92
- try { tabindex = el.getAttribute('tabindex'); } catch { tabindex = null; }
93
- const t = tabindex == null ? '' : String(tabindex).trim();
94
- focusable = t !== '' && !Number.isNaN(Number(t));
91
+ const getFocusableInfo =
92
+ helpers && typeof helpers.getFocusableInfo === 'function' ? helpers.getFocusableInfo : null;
93
+
94
+ function isRolePresentationExcluded(el) {
95
+ const role = (() => {
96
+ try {
97
+ return String(el.getAttribute('role') || '')
98
+ .trim()
99
+ .toLowerCase();
100
+ } catch {
101
+ return '';
102
+ }
103
+ })();
104
+ if (role !== 'presentation' && role !== 'none') return false;
105
+
106
+ // Exclude only when NOT focusable (mirrors img-alt-present policy)
107
+ let focusable;
108
+ if (getFocusableInfo) {
109
+ const fi = (() => {
110
+ try {
111
+ return getFocusableInfo(el, ctx);
112
+ } catch {
113
+ return null;
95
114
  }
96
- return !focusable;
115
+ })();
116
+ focusable = !!(fi && fi.focusable);
117
+ } else {
118
+ let tabindex;
119
+ try {
120
+ tabindex = el.getAttribute('tabindex');
121
+ } catch {
122
+ tabindex = null;
123
+ }
124
+ const t = tabindex == null ? '' : String(tabindex).trim();
125
+ focusable = t !== '' && !Number.isNaN(Number(t));
97
126
  }
127
+ return !focusable;
128
+ }
98
129
 
99
-
100
- const els = (() => {
101
- try { return Array.from((queryAllSmart ? queryAllSmart('canvas') : queryAll('canvas')) || []); }
102
- catch { return queryAll('canvas'); }
103
- })();
104
-
105
- if (!els.length) {
106
- return { ruleId: rule.ruleId, outcome: 'notApplicable', severity: 'minor', occurrences: [] };
130
+ const els = (() => {
131
+ try {
132
+ return Array.from((queryAllSmart ? queryAllSmart('canvas') : queryAll('canvas')) || []);
133
+ } catch {
134
+ return queryAll('canvas');
107
135
  }
136
+ })();
108
137
 
109
- const occurrences = [];
110
- let applicableCount = 0;
138
+ if (!els.length) {
139
+ return { ruleId: rule.ruleId, outcome: 'notApplicable', severity: 'minor', occurrences: [] };
140
+ }
111
141
 
112
- for (const el of els) {
113
- if (!el || !el.getAttribute) continue;
142
+ const occurrences = [];
143
+ let applicableCount = 0;
114
144
 
115
- if (isAccTreeEligible) {
116
- const elig = accEligibleCached(el);
117
- if (elig && elig.eligible === false) continue;
118
- }
145
+ for (const el of els) {
146
+ if (!el || !el.getAttribute) continue;
119
147
 
120
- if (isRolePresentationExcluded(el)) continue;
121
-
122
- const getTextAlternativeInfo =
123
- helpers && typeof helpers.getTextAlternativeInfo === 'function'
124
- ? helpers.getTextAlternativeInfo
125
- : null;
148
+ if (isAccTreeEligible) {
149
+ const elig = accEligibleCached(el);
150
+ if (elig && elig.eligible === false) continue;
151
+ }
126
152
 
127
- const trim = (v) => (v == null ? '' : String(v)).trim();
153
+ if (isRolePresentationExcluded(el)) continue;
128
154
 
129
- // Rule-specific applicability (only elements that already have a text alternative mechanism)
130
- let textAltInfo = null;
131
- if (getTextAlternativeInfo) {
132
- try { textAltInfo = getTextAlternativeInfo(el, ctx); } catch { textAltInfo = null; }
133
- }
155
+ const getTextAlternativeInfo =
156
+ helpers && typeof helpers.getTextAlternativeInfo === 'function'
157
+ ? helpers.getTextAlternativeInfo
158
+ : null;
134
159
 
135
- const hasTextAlt = !!(textAltInfo && textAltInfo.present);
136
- if (!hasTextAlt) continue;
137
-
138
- applicableCount += 1;
139
-
140
- const eligInfo = getEligibilityInfo ? getEligibilityInfo(el, ctx, { targetSet: 'acc' }) : null;
141
-
142
- const baseOccurrence = {
143
- summary: 'Review text alternative for <canvas> for equivalence and appropriateness.',
144
- hint: 'Confirm the fallback text or accessible name conveys the same information/function as the canvas content.',
145
- i18n: {
146
- summaryKey: 'canvas_textAltQuality_summary_cantTell',
147
- hintKey: 'canvas_textAltQuality_hint_cantTell',
148
- params: { element: (el.tagName || '').toLowerCase() }
149
- },
150
- data: {
151
- visibilityFilter: eligInfo || { targetSet: 'acc', accEligible: null, reasons: [] },
152
- details: textAltInfo || null
153
- }
154
- };
160
+ const trim = (v) => (v == null ? '' : String(v)).trim();
155
161
 
156
- if (helpers && typeof helpers.reportOccurrence === 'function') {
157
- occurrences.push(helpers.reportOccurrence(el, baseOccurrence));
158
- } else {
159
- occurrences.push({ selector: '', html: '', ...baseOccurrence });
160
- }
162
+ // Rule-specific applicability (only elements that already have a text alternative mechanism)
163
+ let textAltInfo = null;
164
+ if (getTextAlternativeInfo) {
165
+ try {
166
+ textAltInfo = getTextAlternativeInfo(el, ctx);
167
+ } catch {
168
+ textAltInfo = null;
169
+ }
161
170
  }
162
171
 
163
- if (applicableCount === 0) {
164
- return { ruleId: rule.ruleId, outcome: 'notApplicable', severity: 'minor', occurrences: [] };
172
+ const hasTextAlt = !!(textAltInfo && textAltInfo.present);
173
+ if (!hasTextAlt) continue;
174
+
175
+ applicableCount += 1;
176
+
177
+ const eligInfo = getEligibilityInfo ? getEligibilityInfo(el, ctx, { targetSet: 'acc' }) : null;
178
+
179
+ const baseOccurrence = {
180
+ summary: 'Review text alternative for <canvas> for equivalence and appropriateness.',
181
+ hint: 'Confirm the fallback text or accessible name conveys the same information/function as the canvas content.',
182
+ i18n: {
183
+ summaryKey: 'canvas_textAltQuality_summary_cantTell',
184
+ hintKey: 'canvas_textAltQuality_hint_cantTell',
185
+ params: { element: (el.tagName || '').toLowerCase() }
186
+ },
187
+ data: {
188
+ visibilityFilter: eligInfo || { targetSet: 'acc', accEligible: null, reasons: [] },
189
+ details: textAltInfo || null
190
+ }
191
+ };
192
+
193
+ if (helpers && typeof helpers.reportOccurrence === 'function') {
194
+ occurrences.push(helpers.reportOccurrence(el, baseOccurrence));
195
+ } else {
196
+ occurrences.push({ selector: '', html: '', ...baseOccurrence });
165
197
  }
198
+ }
199
+
200
+ if (applicableCount === 0) {
201
+ return { ruleId: rule.ruleId, outcome: 'notApplicable', severity: 'minor', occurrences: [] };
202
+ }
166
203
 
167
- return { ruleId: rule.ruleId, outcome: 'cantTell', severity: 'minor', occurrences };
204
+ return { ruleId: rule.ruleId, outcome: 'cantTell', severity: 'minor', occurrences };
168
205
  }
169
206
 
170
207
  module.exports = { id, meta, runInPage };