@surea11y/core 1.2.0 → 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 (157) hide show
  1. package/CHANGELOG.md +37 -7
  2. package/LICENSE +373 -21
  3. package/README.md +67 -1
  4. package/bin/core.js +144 -19
  5. package/docs/API_STABILITY.md +1 -1
  6. package/docs/BINDING_AUTHORS_GUIDE.md +9 -9
  7. package/docs/CI_INTEGRATIONS.md +103 -0
  8. package/docs/CLI.md +54 -1
  9. package/docs/ENGINE_OPTIONS.md +2 -0
  10. package/docs/INTEGRATION.md +18 -0
  11. package/docs/OUTPUT_SCHEMA.md +1 -1
  12. package/docs/RULE_CATALOG.md +1 -1
  13. package/docs/SARIF.md +59 -0
  14. package/package.json +15 -4
  15. package/src/baseline.js +0 -0
  16. package/src/catalogs/composites.wcag.js +414 -450
  17. package/src/checks/automatic/area-alt-present.js +59 -25
  18. package/src/checks/automatic/aria-allowed-attr.js +193 -33
  19. package/src/checks/automatic/aria-allowed-role.js +21 -7
  20. package/src/checks/automatic/aria-braille-equivalent.js +32 -10
  21. package/src/checks/automatic/aria-conditional-attr.js +24 -7
  22. package/src/checks/automatic/aria-deprecated-role.js +22 -8
  23. package/src/checks/automatic/aria-hidden-body.js +42 -19
  24. package/src/checks/automatic/aria-hidden-focus.js +408 -53
  25. package/src/checks/automatic/aria-prohibited-attr.js +296 -22
  26. package/src/checks/automatic/aria-prohibited-children.js +55 -16
  27. package/src/checks/automatic/aria-required-attr.js +23 -8
  28. package/src/checks/automatic/aria-required-children.js +37 -14
  29. package/src/checks/automatic/aria-required-parent.js +48 -14
  30. package/src/checks/automatic/aria-role-name-present.js +47 -21
  31. package/src/checks/automatic/aria-roles-valid.js +22 -12
  32. package/src/checks/automatic/aria-valid-attr-value.js +28 -7
  33. package/src/checks/automatic/aria-valid-attr.js +17 -5
  34. package/src/checks/automatic/autocomplete-valid.js +74 -16
  35. package/src/checks/automatic/avoid-inline-spacing.js +20 -6
  36. package/src/checks/automatic/binary-control-name-present.js +60 -50
  37. package/src/checks/automatic/button-name-present.js +48 -18
  38. package/src/checks/automatic/bypass-blocks-present.js +42 -25
  39. package/src/checks/automatic/canvas-text-alternative-present.js +57 -26
  40. package/src/checks/automatic/combobox-name-present.js +38 -45
  41. package/src/checks/automatic/contrast-computable.js +361 -341
  42. package/src/checks/automatic/contrast-enhanced.js +487 -466
  43. package/src/checks/automatic/contrast-minimum.js +486 -465
  44. package/src/checks/automatic/css-orientation-lock.js +30 -8
  45. package/src/checks/automatic/definition-list-children-valid.js +40 -19
  46. package/src/checks/automatic/deprecated-elements-not-used.js +21 -7
  47. package/src/checks/automatic/dialog-name-present.js +37 -75
  48. package/src/checks/automatic/dlitem-parent-valid.js +23 -8
  49. package/src/checks/automatic/duplicate-id-aria.js +24 -6
  50. package/src/checks/automatic/embed-text-alternative-present.js +86 -35
  51. package/src/checks/automatic/form-control-programmatic-label-present.js +79 -196
  52. package/src/checks/automatic/form-control-single-label.js +47 -10
  53. package/src/checks/automatic/html-xml-lang-mismatch.js +34 -18
  54. package/src/checks/automatic/iframe-focusable-content.js +26 -11
  55. package/src/checks/automatic/iframe-name-present.js +31 -9
  56. package/src/checks/automatic/iframe-title-unique.js +29 -8
  57. package/src/checks/automatic/img-alt-present.js +47 -43
  58. package/src/checks/automatic/input-image-alt-present.js +143 -112
  59. package/src/checks/automatic/label-in-name.js +50 -22
  60. package/src/checks/automatic/language-page-present.js +109 -109
  61. package/src/checks/automatic/link-in-text-block.js +59 -19
  62. package/src/checks/automatic/link-name-present.js +45 -14
  63. package/src/checks/automatic/list-children-valid.js +26 -9
  64. package/src/checks/automatic/listbox-name-present.js +39 -19
  65. package/src/checks/automatic/listitem-parent-valid.js +18 -6
  66. package/src/checks/automatic/menuitem-name-present.js +39 -61
  67. package/src/checks/automatic/meta-refresh-no-exceptions.js +28 -7
  68. package/src/checks/automatic/meta-refresh-timing-absent.js +20 -6
  69. package/src/checks/automatic/meta-viewport-zoom-enabled.js +24 -7
  70. package/src/checks/automatic/meter-name-present.js +36 -33
  71. package/src/checks/automatic/nested-interactive-controls-absent.js +31 -10
  72. package/src/checks/automatic/object-text-alternative-present.js +91 -39
  73. package/src/checks/automatic/option-name-present.js +38 -21
  74. package/src/checks/automatic/page-title-present.js +17 -6
  75. package/src/checks/automatic/progressbar-name-present.js +41 -34
  76. package/src/checks/automatic/role-img-alt-present.js +209 -157
  77. package/src/checks/automatic/searchbox-name-present.js +39 -19
  78. package/src/checks/automatic/server-side-image-map-absent.js +23 -8
  79. package/src/checks/automatic/slider-name-present.js +40 -47
  80. package/src/checks/automatic/spinbutton-name-present.js +39 -19
  81. package/src/checks/automatic/summary-name-present.js +37 -17
  82. package/src/checks/automatic/svg-image-text-alternative-present.js +114 -47
  83. package/src/checks/automatic/svg-text-alternative-present.js +246 -226
  84. package/src/checks/automatic/tab-name-present.js +37 -60
  85. package/src/checks/automatic/table-headers-attr-valid.js +24 -8
  86. package/src/checks/automatic/table-th-has-data-cells.js +22 -8
  87. package/src/checks/automatic/target-size-minimum.js +118 -48
  88. package/src/checks/automatic/td-has-header.js +29 -11
  89. package/src/checks/automatic/textbox-name-present.js +39 -19
  90. package/src/checks/automatic/tooltip-name-present.js +37 -18
  91. package/src/checks/automatic/treeitem-name-present.js +38 -21
  92. package/src/checks/automatic/valid-lang.js +20 -6
  93. package/src/checks/automatic/video-poster-text-alternative-present.js +79 -36
  94. package/src/checks/manual/accesskeys-manual.js +14 -5
  95. package/src/checks/manual/area-alt-decorative-manual.js +192 -193
  96. package/src/checks/manual/area-alt-quality-manual.js +182 -141
  97. package/src/checks/manual/aria-checked-state-mismatch-manual.js +34 -11
  98. package/src/checks/manual/aria-text-manual.js +14 -6
  99. package/src/checks/manual/canvas-text-alternative-quality-manual.js +149 -114
  100. package/src/checks/manual/css-hidden-focus.js +196 -165
  101. package/src/checks/manual/embed-text-alternative-quality-manual.js +171 -160
  102. package/src/checks/manual/empty-heading-manual.js +24 -7
  103. package/src/checks/manual/empty-table-header-manual.js +17 -6
  104. package/src/checks/manual/focus-order-semantics-manual.js +45 -10
  105. package/src/checks/manual/form-control-programmatic-label-quality-manual.js +207 -246
  106. package/src/checks/manual/heading-order-manual.js +22 -7
  107. package/src/checks/manual/identical-links-same-purpose-manual.js +34 -12
  108. package/src/checks/manual/image-redundant-alt-manual.js +17 -7
  109. package/src/checks/manual/img-alt-decorative-manual.js +131 -96
  110. package/src/checks/manual/img-alt-quality-manual.js +176 -127
  111. package/src/checks/manual/input-image-alt-decorative-manual.js +125 -92
  112. package/src/checks/manual/input-image-alt-quality-manual.js +125 -92
  113. package/src/checks/manual/label-title-only-manual.js +14 -5
  114. package/src/checks/manual/landmark-banner-is-top-level-manual.js +66 -16
  115. package/src/checks/manual/landmark-contentinfo-is-top-level-manual.js +53 -16
  116. package/src/checks/manual/landmark-main-is-top-level-manual.js +35 -10
  117. package/src/checks/manual/landmark-no-duplicate-banner-manual.js +29 -10
  118. package/src/checks/manual/landmark-no-duplicate-contentinfo-manual.js +29 -10
  119. package/src/checks/manual/landmark-no-duplicate-main-manual.js +17 -8
  120. package/src/checks/manual/landmark-one-main-manual.js +26 -20
  121. package/src/checks/manual/landmark-unique-manual.js +41 -15
  122. package/src/checks/manual/link-name-quality-manual.js +43 -12
  123. package/src/checks/manual/media-transcript-present-manual.js +35 -22
  124. package/src/checks/manual/meta-viewport-large-manual.js +16 -5
  125. package/src/checks/manual/mouse-only-event-handlers-manual.js +38 -11
  126. package/src/checks/manual/no-autoplay-audio-manual.js +20 -6
  127. package/src/checks/manual/object-text-alternative-quality-manual.js +175 -154
  128. package/src/checks/manual/p-as-heading-manual.js +22 -7
  129. package/src/checks/manual/page-has-heading-one-manual.js +31 -22
  130. package/src/checks/manual/page-title-patterns-manual.js +78 -50
  131. package/src/checks/manual/presentation-role-conflict-manual.js +59 -19
  132. package/src/checks/manual/region-manual.js +241 -48
  133. package/src/checks/manual/scope-attr-valid-manual.js +10 -3
  134. package/src/checks/manual/scrollable-region-focusable-manual.js +37 -11
  135. package/src/checks/manual/skip-link-manual.js +35 -12
  136. package/src/checks/manual/svg-text-alternative-quality-manual.js +206 -165
  137. package/src/checks/manual/tabindex-manual.js +10 -3
  138. package/src/checks/manual/table-duplicate-name-manual.js +17 -7
  139. package/src/checks/manual/table-fake-caption-manual.js +24 -7
  140. package/src/checks/manual/video-caption-manual.js +15 -4
  141. package/src/checks/manual-review.js +56 -12
  142. package/src/core/aria-helpers.js +1127 -886
  143. package/src/core/contrast-helpers.js +1217 -1062
  144. package/src/core/dom-helpers.js +4175 -3917
  145. package/src/core/dom-runner.js +720 -604
  146. package/src/core/frame-messaging.js +189 -138
  147. package/src/core/frame-scan.js +94 -82
  148. package/src/core/rollup-composites.js +94 -102
  149. package/src/core/rule-meta.js +58 -41
  150. package/src/core.js +36552 -29111
  151. package/src/i18n/en.js +1194 -889
  152. package/src/i18n/fr.js +1136 -795
  153. package/src/policy/contracts.js +13 -13
  154. package/src/policy/resolvePolicy.js +48 -44
  155. package/src/report.js +63 -43
  156. package/src/sarif.js +175 -0
  157. package/surea11y.browser.js +36042 -0
@@ -3,8 +3,8 @@
3
3
  const LEVEL_RANK = Object.freeze({ A: 1, AA: 2, AAA: 3 });
4
4
 
5
5
  function normalizeScanLevel(scanLevel) {
6
- if (scanLevel === 'A' || scanLevel === 'AA' || scanLevel === 'AAA') return scanLevel;
7
- return null;
6
+ if (scanLevel === 'A' || scanLevel === 'AA' || scanLevel === 'AAA') return scanLevel;
7
+ return null;
8
8
  }
9
9
 
10
10
  /**
@@ -12,38 +12,38 @@ function normalizeScanLevel(scanLevel) {
12
12
  * (If you already pass scanLevel explicitly, you can ignore this.)
13
13
  */
14
14
  function inferScanLevelFromRunOnly(runOnly) {
15
- const tags = runOnly && Array.isArray(runOnly.tags) ? runOnly.tags : null;
16
- if (!tags || tags.length === 0) return null;
15
+ const tags = runOnly && Array.isArray(runOnly.tags) ? runOnly.tags : null;
16
+ if (!tags || tags.length === 0) return null;
17
17
 
18
- // Common reference-engine-style tags; adjust if your app uses different ones.
19
- if (tags.includes('wcag2aaa') || tags.includes('wcag22aaa')) return 'AAA';
20
- if (tags.includes('wcag2aa') || tags.includes('wcag22aa')) return 'AA';
21
- if (tags.includes('wcag2a') || tags.includes('wcag22a')) return 'A';
18
+ // Common reference-engine-style tags; adjust if your app uses different ones.
19
+ if (tags.includes('wcag2aaa') || tags.includes('wcag22aaa')) return 'AAA';
20
+ if (tags.includes('wcag2aa') || tags.includes('wcag22aa')) return 'AA';
21
+ if (tags.includes('wcag2a') || tags.includes('wcag22a')) return 'A';
22
22
 
23
- return null;
23
+ return null;
24
24
  }
25
25
 
26
26
  function isWithinTargetLevel(compositeLevel, scanLevel) {
27
- const c = LEVEL_RANK[compositeLevel];
28
- const s = LEVEL_RANK[scanLevel];
29
- if (!c || !s) return false;
30
- return c <= s;
27
+ const c = LEVEL_RANK[compositeLevel];
28
+ const s = LEVEL_RANK[scanLevel];
29
+ if (!c || !s) return false;
30
+ return c <= s;
31
31
  }
32
32
 
33
33
  function aggregateOutcome(childOutcomes) {
34
- // Deterministic priority: fail > cantTell > pass > notApplicable
35
- let hasPass = false;
36
- let hasCantTell = false;
37
-
38
- for (const o of childOutcomes) {
39
- if (o === 'fail') return 'fail';
40
- if (o === 'cantTell') hasCantTell = true;
41
- else if (o === 'pass') hasPass = true;
42
- }
43
-
44
- if (hasCantTell) return 'cantTell';
45
- if (hasPass) return 'pass';
46
- return 'notApplicable';
34
+ // Deterministic priority: fail > cantTell > pass > notApplicable
35
+ let hasPass = false;
36
+ let hasCantTell = false;
37
+
38
+ for (const o of childOutcomes) {
39
+ if (o === 'fail') return 'fail';
40
+ if (o === 'cantTell') hasCantTell = true;
41
+ else if (o === 'pass') hasPass = true;
42
+ }
43
+
44
+ if (hasCantTell) return 'cantTell';
45
+ if (hasPass) return 'pass';
46
+ return 'notApplicable';
47
47
  }
48
48
 
49
49
  /**
@@ -51,85 +51,77 @@ function aggregateOutcome(childOutcomes) {
51
51
  *
52
52
  * Inputs are plain JS objects/arrays; function never throws and is deterministic.
53
53
  */
54
- function rollupComposites({
55
- atomicResults,
56
- composites,
57
- scanLevel,
58
- runOnly
59
- }) {
60
- try {
61
- const target =
62
- normalizeScanLevel(scanLevel) ||
63
- inferScanLevelFromRunOnly(runOnly) ||
64
- 'AAA'; // safest default: if unspecified, do not hide anything
65
-
66
- const atomicByRuleId = Object.create(null);
67
- if (Array.isArray(atomicResults)) {
68
- for (const r of atomicResults) {
69
- if (!r || typeof r.ruleId !== 'string') continue;
70
- atomicByRuleId[r.ruleId] = r;
71
- }
72
- }
54
+ function rollupComposites({ atomicResults, composites, scanLevel, runOnly }) {
55
+ try {
56
+ const target = normalizeScanLevel(scanLevel) || inferScanLevelFromRunOnly(runOnly) || 'AAA'; // safest default: if unspecified, do not hide anything
57
+
58
+ const atomicByRuleId = Object.create(null);
59
+ if (Array.isArray(atomicResults)) {
60
+ for (const r of atomicResults) {
61
+ if (!r || typeof r.ruleId !== 'string') continue;
62
+ atomicByRuleId[r.ruleId] = r;
63
+ }
64
+ }
73
65
 
74
- const out = [];
75
- const list = Array.isArray(composites) ? composites : [];
76
- for (const c of list) {
77
- const level = c && c.meta && c.meta.level;
78
- if (!isWithinTargetLevel(level, target)) continue;
79
-
80
- const checksIds = Array.isArray(c.checksIds) ? c.checksIds : [];
81
- const childOutcomes = checksIds.map((id) => {
82
- const rr = atomicByRuleId[id];
83
- return rr && typeof rr.outcome === 'string' ? rr.outcome : 'notApplicable';
84
- });
85
-
86
- const outcome = aggregateOutcome(childOutcomes);
87
-
88
- // Deterministic i18n keys + structured details (matches your reporting schema expectations)
89
- const i18nKey =
90
- outcome === 'fail'
91
- ? 'composite.outcome.fail'
92
- : outcome === 'cantTell'
93
- ? 'composite.outcome.cantTell'
94
- : outcome === 'pass'
95
- ? 'composite.outcome.pass'
96
- : 'composite.outcome.notApplicable';
97
-
98
- out.push({
99
- ruleId: c.id,
100
- outcome,
101
- confidence: outcome === 'cantTell' ? 'low' : 'high',
102
- summaryKey: i18nKey, // resolved at build-time by your i18n layer
103
- i18nKey,
104
- i18nParams: {
105
- wcagSc: (c.meta && c.meta.wcagSc && c.meta.wcagSc[0]) || null,
106
- level: level || null,
107
- scanLevel: target
108
- },
109
- data: {
110
- details: {
111
- reasonCode:
112
- outcome === 'cantTell'
113
- ? 'oneOrMoreChecksCantTell'
114
- : outcome === 'fail'
115
- ? 'oneOrMoreChecksFailed'
116
- : outcome === 'pass'
117
- ? 'allApplicableChecksPassed'
118
- : 'allChecksNotApplicable',
119
- scanLevel: target,
120
- compositeLevel: level || null,
121
- checksIds,
122
- childOutcomes
123
- }
124
- }
125
- });
66
+ const out = [];
67
+ const list = Array.isArray(composites) ? composites : [];
68
+ for (const c of list) {
69
+ const level = c && c.meta && c.meta.level;
70
+ if (!isWithinTargetLevel(level, target)) continue;
71
+
72
+ const checksIds = Array.isArray(c.checksIds) ? c.checksIds : [];
73
+ const childOutcomes = checksIds.map((id) => {
74
+ const rr = atomicByRuleId[id];
75
+ return rr && typeof rr.outcome === 'string' ? rr.outcome : 'notApplicable';
76
+ });
77
+
78
+ const outcome = aggregateOutcome(childOutcomes);
79
+
80
+ // Deterministic i18n keys + structured details (matches your reporting schema expectations)
81
+ const i18nKey =
82
+ outcome === 'fail'
83
+ ? 'composite.outcome.fail'
84
+ : outcome === 'cantTell'
85
+ ? 'composite.outcome.cantTell'
86
+ : outcome === 'pass'
87
+ ? 'composite.outcome.pass'
88
+ : 'composite.outcome.notApplicable';
89
+
90
+ out.push({
91
+ ruleId: c.id,
92
+ outcome,
93
+ confidence: outcome === 'cantTell' ? 'low' : 'high',
94
+ summaryKey: i18nKey, // resolved at build-time by your i18n layer
95
+ i18nKey,
96
+ i18nParams: {
97
+ wcagSc: (c.meta && c.meta.wcagSc && c.meta.wcagSc[0]) || null,
98
+ level: level || null,
99
+ scanLevel: target
100
+ },
101
+ data: {
102
+ details: {
103
+ reasonCode:
104
+ outcome === 'cantTell'
105
+ ? 'oneOrMoreChecksCantTell'
106
+ : outcome === 'fail'
107
+ ? 'oneOrMoreChecksFailed'
108
+ : outcome === 'pass'
109
+ ? 'allApplicableChecksPassed'
110
+ : 'allChecksNotApplicable',
111
+ scanLevel: target,
112
+ compositeLevel: level || null,
113
+ checksIds,
114
+ childOutcomes
115
+ }
126
116
  }
127
-
128
- return out;
129
- } catch (e) {
130
- // no-throws guarantee
131
- return [];
117
+ });
132
118
  }
119
+
120
+ return out;
121
+ } catch (e) {
122
+ // no-throws guarantee
123
+ return [];
124
+ }
133
125
  }
134
126
 
135
127
  module.exports = { rollupComposites };
@@ -14,14 +14,17 @@
14
14
  function normalizeRuleMeta(ruleId, id, meta, engineTag) {
15
15
  function normalizeStringArray(value) {
16
16
  if (!Array.isArray(value)) return [];
17
- return value.map(String).map((s) => s.trim()).filter(Boolean);
17
+ return value
18
+ .map(String)
19
+ .map((s) => s.trim())
20
+ .filter(Boolean);
18
21
  }
19
22
 
20
23
  function normalizeObjectArray(value) {
21
24
  if (!Array.isArray(value)) return [];
22
25
  return value
23
- .filter((v) => v && typeof v === 'object' && !Array.isArray(v))
24
- .map((v) => ({ ...v }));
26
+ .filter((v) => v && typeof v === 'object' && !Array.isArray(v))
27
+ .map((v) => ({ ...v }));
25
28
  }
26
29
 
27
30
  function deriveWcagScFromNormativeMappings(normativeMappings) {
@@ -36,15 +39,14 @@ function normalizeRuleMeta(ruleId, id, meta, engineTag) {
36
39
  return Array.from(out).sort();
37
40
  }
38
41
 
39
- const m = (meta && typeof meta === 'object') ? meta : {};
42
+ const m = meta && typeof meta === 'object' ? meta : {};
40
43
 
41
- const title = (typeof m.title === 'string' && m.title.trim()) ? m.title.trim() : id;
42
- const description = (typeof m.description === 'string') ? m.description : '';
43
- const helpUrl = (typeof m.helpUrl === 'string') ? m.helpUrl : '';
44
+ const title = typeof m.title === 'string' && m.title.trim() ? m.title.trim() : id;
45
+ const description = typeof m.description === 'string' ? m.description : '';
46
+ const helpUrl = typeof m.helpUrl === 'string' ? m.helpUrl : '';
44
47
 
45
- const i18n = (m.i18n && typeof m.i18n === 'object' && !Array.isArray(m.i18n))
46
- ? { ...m.i18n }
47
- : null;
48
+ const i18n =
49
+ m.i18n && typeof m.i18n === 'object' && !Array.isArray(m.i18n) ? { ...m.i18n } : null;
48
50
 
49
51
  const tags = normalizeStringArray(m.tags).map((t) => t.toLowerCase());
50
52
  if (!tags.includes(engineTag)) tags.push(engineTag);
@@ -53,76 +55,91 @@ function normalizeRuleMeta(ruleId, id, meta, engineTag) {
53
55
  const wcagSc = deriveWcagScFromNormativeMappings(normativeMappings);
54
56
  const informativeReferences = normalizeObjectArray(m.informativeReferences);
55
57
 
56
- const defaultSeverity = (typeof m.defaultSeverity === 'string' && m.defaultSeverity.trim())
58
+ const defaultSeverity =
59
+ typeof m.defaultSeverity === 'string' && m.defaultSeverity.trim()
57
60
  ? m.defaultSeverity.trim()
58
61
  : 'moderate';
59
62
 
60
- const defaultConfidence = (typeof m.defaultConfidence === 'string' && m.defaultConfidence.trim())
63
+ const defaultConfidence =
64
+ typeof m.defaultConfidence === 'string' && m.defaultConfidence.trim()
61
65
  ? m.defaultConfidence.trim()
62
66
  : 'medium';
63
67
 
64
- const type = (m.type === 'manual' || m.type === 'automatic')
65
- ? m.type
66
- : 'automatic';
68
+ const type = m.type === 'manual' || m.type === 'automatic' ? m.type : 'automatic';
67
69
 
68
- const coverage = (m.coverage === null || typeof m.coverage === 'string' || typeof m.coverage === 'object')
70
+ const coverage =
71
+ m.coverage === null || typeof m.coverage === 'string' || typeof m.coverage === 'object'
69
72
  ? m.coverage
70
73
  : null;
71
74
 
72
- const ruleInterfaceVersion = (typeof m.ruleInterfaceVersion === 'string' && m.ruleInterfaceVersion.trim())
75
+ const ruleInterfaceVersion =
76
+ typeof m.ruleInterfaceVersion === 'string' && m.ruleInterfaceVersion.trim()
73
77
  ? m.ruleInterfaceVersion.trim()
74
78
  : '1.0.0';
75
79
 
76
- const ruleVersion = (typeof m.ruleVersion === 'string' && m.ruleVersion.trim())
77
- ? m.ruleVersion.trim()
78
- : '0.0.0';
80
+ const ruleVersion =
81
+ typeof m.ruleVersion === 'string' && m.ruleVersion.trim() ? m.ruleVersion.trim() : '0.0.0';
79
82
 
80
- const normative = (typeof m.normative === 'boolean') ? m.normative : true;
81
- const atomic = (typeof m.atomic === 'boolean') ? m.atomic : true;
83
+ const normative = typeof m.normative === 'boolean' ? m.normative : true;
84
+ const atomic = typeof m.atomic === 'boolean' ? m.atomic : true;
82
85
 
83
86
  // Deprecation signal for the rule catalog (see docs/API_STABILITY.md).
84
87
  // Purely informational -- a deprecated rule still runs and produces
85
88
  // results completely normally; this is a catalog-level migration signal
86
89
  // for integrators, not an automatic exclusion.
87
- const deprecated = (typeof m.deprecated === 'boolean') ? m.deprecated : false;
88
- const deprecation = (deprecated && m.deprecation && typeof m.deprecation === 'object' && !Array.isArray(m.deprecation))
90
+ const deprecated = typeof m.deprecated === 'boolean' ? m.deprecated : false;
91
+ const deprecation =
92
+ deprecated &&
93
+ m.deprecation &&
94
+ typeof m.deprecation === 'object' &&
95
+ !Array.isArray(m.deprecation)
89
96
  ? {
90
- replacedBy: (typeof m.deprecation.replacedBy === 'string' && m.deprecation.replacedBy.trim()) ? m.deprecation.replacedBy.trim() : null,
91
- reason: (typeof m.deprecation.reason === 'string') ? m.deprecation.reason.trim() : '',
92
- sinceVersion: (typeof m.deprecation.sinceVersion === 'string') ? m.deprecation.sinceVersion.trim() : ''
97
+ replacedBy:
98
+ typeof m.deprecation.replacedBy === 'string' && m.deprecation.replacedBy.trim()
99
+ ? m.deprecation.replacedBy.trim()
100
+ : null,
101
+ reason: typeof m.deprecation.reason === 'string' ? m.deprecation.reason.trim() : '',
102
+ sinceVersion:
103
+ typeof m.deprecation.sinceVersion === 'string' ? m.deprecation.sinceVersion.trim() : ''
93
104
  }
94
105
  : null;
95
106
 
96
107
  if (deprecated && (!deprecation || !deprecation.reason || !deprecation.sinceVersion)) {
97
- throw new Error(`Rule ${ruleId}: meta.deprecated:true requires meta.deprecation.reason and meta.deprecation.sinceVersion`);
108
+ throw new Error(
109
+ `Rule ${ruleId}: meta.deprecated:true requires meta.deprecation.reason and meta.deprecation.sinceVersion`
110
+ );
98
111
  }
99
112
 
100
- const category = (typeof m.category === 'string' && m.category.trim()) ? m.category.trim() : null;
101
- const standard = (typeof m.standard === 'string' && m.standard.trim()) ? m.standard.trim() : null;
113
+ const category = typeof m.category === 'string' && m.category.trim() ? m.category.trim() : null;
114
+ const standard = typeof m.standard === 'string' && m.standard.trim() ? m.standard.trim() : null;
102
115
 
103
- const applicability = (typeof m.applicability === 'string') ? m.applicability : '';
104
- const expectation = (typeof m.expectation === 'string') ? m.expectation : '';
116
+ const applicability = typeof m.applicability === 'string' ? m.applicability : '';
117
+ const expectation = typeof m.expectation === 'string' ? m.expectation : '';
105
118
 
106
119
  const references = Array.isArray(m.references) ? m.references.slice() : [];
107
- const requirements = (m.requirements === null || typeof m.requirements === 'string' || typeof m.requirements === 'object')
120
+ const requirements =
121
+ m.requirements === null ||
122
+ typeof m.requirements === 'string' ||
123
+ typeof m.requirements === 'object'
108
124
  ? m.requirements
109
125
  : null;
110
126
 
111
- const mappings = (m.mappings === null || typeof m.mappings === 'string' || typeof m.mappings === 'object')
127
+ const mappings =
128
+ m.mappings === null || typeof m.mappings === 'string' || typeof m.mappings === 'object'
112
129
  ? m.mappings
113
130
  : null;
114
131
 
115
- if (!Array.isArray(tags)) throw new Error(`Rule ${ruleId}: meta.tags must be an array`);
116
- if (!Array.isArray(normativeMappings)) throw new Error(`Rule ${ruleId}: meta.normativeMappings must be an array`);
117
- if (!Array.isArray(informativeReferences)) throw new Error(`Rule ${ruleId}: meta.informativeReferences must be an array`);
118
- if (type !== 'automatic' && type !== 'manual') throw new Error(`Rule ${ruleId}: meta.type must be "automatic" or "manual"`);
119
-
120
132
  if (i18n) {
121
133
  if (typeof i18n.titleKey !== 'string' || !i18n.titleKey.trim()) {
122
134
  throw new Error(`Rule ${ruleId}: meta.i18n.titleKey must be a non-empty string`);
123
135
  }
124
- if (i18n.descriptionKey != null && (typeof i18n.descriptionKey !== 'string' || !i18n.descriptionKey.trim())) {
125
- throw new Error(`Rule ${ruleId}: meta.i18n.descriptionKey must be a non-empty string when provided`);
136
+ if (
137
+ i18n.descriptionKey != null &&
138
+ (typeof i18n.descriptionKey !== 'string' || !i18n.descriptionKey.trim())
139
+ ) {
140
+ throw new Error(
141
+ `Rule ${ruleId}: meta.i18n.descriptionKey must be a non-empty string when provided`
142
+ );
126
143
  }
127
144
  }
128
145