Skip to main content

fallow_types/
issue_meta.rs

1//! Shared issue-type contract metadata.
2
3use std::sync::LazyLock;
4
5use crate::suppress::IssueKind;
6
7/// Shared contract facts for issue-like diagnostics.
8///
9/// Curated prose stays with the surface that owns it. This table is only for
10/// stable machine-facing facts that otherwise drift across CLI schema, LSP,
11/// MCP, and suppression helpers.
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub struct IssueKindMeta {
14    /// Backing suppression issue kind, when this row maps to one.
15    pub kind: Option<IssueKind>,
16    /// Canonical issue code used in output and diagnostic codes.
17    pub code: &'static str,
18    /// Accepted aliases for config, suppression, or migration compatibility.
19    pub aliases: &'static [&'static str],
20    /// User-facing label for editor and capability surfaces.
21    pub label: &'static str,
22    /// Canonical `[rules]` key when the issue is configurable.
23    pub config_key: Option<&'static str>,
24    /// Dead-code CLI filter flag, when one exists.
25    pub filter_flag: Option<&'static str>,
26    /// MCP `issue_types` selector, when this issue can be selected there.
27    pub mcp_issue_type: Option<&'static str>,
28    /// Suppression token agents should emit, when suppressible.
29    pub suppress_token: Option<&'static str>,
30    /// Whether the suppression comment should use `fallow-ignore-file`.
31    pub suppress_file_level: bool,
32    /// Whether the LSP exposes this row through initialization options and
33    /// `fallow/issueTypes`.
34    pub lsp: bool,
35    /// Broad documentation category for authoring and generated manifests.
36    pub docs_category: &'static str,
37}
38
39impl IssueKindMeta {
40    /// Return the filter flag as an MCP selector pair.
41    #[must_use]
42    pub const fn mcp_pair(self) -> Option<(&'static str, &'static str)> {
43        match (self.mcp_issue_type, self.filter_flag) {
44            (Some(issue_type), Some(flag)) => Some((issue_type, flag)),
45            _ => None,
46        }
47    }
48
49    /// Whether this row owns a serialized dead-code result contract.
50    #[must_use]
51    pub fn has_result_contract(self) -> bool {
52        issue_result_meta_by_code(self.code).is_some()
53    }
54
55    /// SARIF rule ids used by CI formatters for this issue row.
56    #[must_use]
57    pub fn sarif_rule_ids(self) -> Vec<String> {
58        issue_sarif_rule_ids(self.code)
59    }
60
61    /// CodeClimate check names used by CI formatters for this issue row.
62    #[must_use]
63    pub fn codeclimate_check_names(self) -> Vec<String> {
64        issue_codeclimate_check_names(self.code)
65    }
66
67    /// Documentation anchor under `/explanations/dead-code`.
68    #[must_use]
69    pub fn docs_anchor(self) -> Option<&'static str> {
70        issue_docs_anchor(self.code)
71    }
72
73    /// Published TypeScript backwards-compat alias policy.
74    #[must_use]
75    pub fn ts_alias(self) -> Option<TsAliasMeta> {
76        issue_ts_alias(self.code)
77    }
78}
79
80/// All shared issue metadata rows.
81pub const ISSUE_KIND_META: &[IssueKindMeta] = &[
82    IssueKindMeta {
83        kind: Some(IssueKind::CodeDuplication),
84        code: "code-duplication",
85        aliases: &[],
86        label: "Code Duplication",
87        config_key: None,
88        filter_flag: None,
89        mcp_issue_type: None,
90        suppress_token: Some("code-duplication"),
91        suppress_file_level: false,
92        lsp: true,
93        docs_category: "dupes",
94    },
95    IssueKindMeta {
96        kind: Some(IssueKind::UnusedFile),
97        code: "unused-file",
98        aliases: &[],
99        label: "Unused Files",
100        config_key: Some("unused-files"),
101        filter_flag: Some("--unused-files"),
102        mcp_issue_type: Some("unused-files"),
103        suppress_token: Some("unused-file"),
104        suppress_file_level: true,
105        lsp: true,
106        docs_category: "source",
107    },
108    IssueKindMeta {
109        kind: Some(IssueKind::UnusedExport),
110        code: "unused-export",
111        aliases: &[],
112        label: "Unused Exports",
113        config_key: Some("unused-exports"),
114        filter_flag: Some("--unused-exports"),
115        mcp_issue_type: Some("unused-exports"),
116        suppress_token: Some("unused-export"),
117        suppress_file_level: false,
118        lsp: true,
119        docs_category: "source",
120    },
121    IssueKindMeta {
122        kind: Some(IssueKind::UnusedType),
123        code: "unused-type",
124        aliases: &[],
125        label: "Unused Types",
126        config_key: Some("unused-types"),
127        filter_flag: Some("--unused-types"),
128        mcp_issue_type: Some("unused-types"),
129        suppress_token: Some("unused-type"),
130        suppress_file_level: false,
131        lsp: true,
132        docs_category: "source",
133    },
134    IssueKindMeta {
135        kind: Some(IssueKind::PrivateTypeLeak),
136        code: "private-type-leak",
137        aliases: &[],
138        label: "Private Type Leaks",
139        config_key: Some("private-type-leaks"),
140        filter_flag: Some("--private-type-leaks"),
141        mcp_issue_type: Some("private-type-leaks"),
142        suppress_token: Some("private-type-leak"),
143        suppress_file_level: false,
144        lsp: true,
145        docs_category: "source",
146    },
147    IssueKindMeta {
148        kind: Some(IssueKind::DeprecatedExportInUse),
149        code: "deprecated-export-in-use",
150        aliases: &[],
151        label: "Deprecated Exports in Use",
152        config_key: Some("deprecated-exports-in-use"),
153        filter_flag: Some("--deprecated-exports-in-use"),
154        mcp_issue_type: Some("deprecated-exports-in-use"),
155        suppress_token: Some("deprecated-export-in-use"),
156        suppress_file_level: false,
157        lsp: false,
158        docs_category: "source",
159    },
160    IssueKindMeta {
161        kind: Some(IssueKind::UnusedDependency),
162        code: "unused-dependency",
163        aliases: &[],
164        label: "Unused Dependencies",
165        config_key: Some("unused-dependencies"),
166        filter_flag: Some("--unused-deps"),
167        mcp_issue_type: Some("unused-deps"),
168        suppress_token: None,
169        suppress_file_level: false,
170        lsp: true,
171        docs_category: "dependency",
172    },
173    IssueKindMeta {
174        kind: Some(IssueKind::UnusedDevDependency),
175        code: "unused-dev-dependency",
176        aliases: &["unused-dev-deps", "unused-dev-dependencies"],
177        label: "Unused Dev Dependencies",
178        config_key: Some("unused-dev-dependencies"),
179        filter_flag: Some("--unused-deps"),
180        mcp_issue_type: None,
181        suppress_token: None,
182        suppress_file_level: false,
183        lsp: true,
184        docs_category: "dependency",
185    },
186    IssueKindMeta {
187        kind: None,
188        code: "unused-optional-dependency",
189        aliases: &["unused-optional-deps", "unused-optional-dependencies"],
190        label: "Unused Optional Dependencies",
191        config_key: Some("unused-optional-dependencies"),
192        filter_flag: Some("--unused-deps"),
193        mcp_issue_type: None,
194        suppress_token: None,
195        suppress_file_level: false,
196        lsp: true,
197        docs_category: "dependency",
198    },
199    IssueKindMeta {
200        kind: Some(IssueKind::UnusedEnumMember),
201        code: "unused-enum-member",
202        aliases: &[],
203        label: "Unused Enum Members",
204        config_key: Some("unused-enum-members"),
205        filter_flag: Some("--unused-enum-members"),
206        mcp_issue_type: Some("unused-enum-members"),
207        suppress_token: Some("unused-enum-member"),
208        suppress_file_level: false,
209        lsp: true,
210        docs_category: "source",
211    },
212    IssueKindMeta {
213        kind: Some(IssueKind::UnusedClassMember),
214        code: "unused-class-member",
215        aliases: &[],
216        label: "Unused Class Members",
217        config_key: Some("unused-class-members"),
218        filter_flag: Some("--unused-class-members"),
219        mcp_issue_type: Some("unused-class-members"),
220        suppress_token: Some("unused-class-member"),
221        suppress_file_level: false,
222        lsp: true,
223        docs_category: "source",
224    },
225    IssueKindMeta {
226        kind: Some(IssueKind::UnusedStoreMember),
227        code: "unused-store-member",
228        aliases: &["unused-store-members"],
229        label: "Unused Store Members",
230        config_key: Some("unused-store-members"),
231        filter_flag: Some("--unused-store-members"),
232        mcp_issue_type: Some("unused-store-members"),
233        suppress_token: Some("unused-store-member"),
234        suppress_file_level: false,
235        lsp: true,
236        docs_category: "framework",
237    },
238    IssueKindMeta {
239        kind: Some(IssueKind::UnresolvedImport),
240        code: "unresolved-import",
241        aliases: &[],
242        label: "Unresolved Imports",
243        config_key: Some("unresolved-imports"),
244        filter_flag: Some("--unresolved-imports"),
245        mcp_issue_type: Some("unresolved-imports"),
246        suppress_token: Some("unresolved-import"),
247        suppress_file_level: false,
248        lsp: true,
249        docs_category: "dependency",
250    },
251    IssueKindMeta {
252        kind: Some(IssueKind::UnlistedDependency),
253        code: "unlisted-dependency",
254        aliases: &[],
255        label: "Unlisted Dependencies",
256        config_key: Some("unlisted-dependencies"),
257        filter_flag: Some("--unlisted-deps"),
258        mcp_issue_type: Some("unlisted-deps"),
259        suppress_token: None,
260        suppress_file_level: false,
261        lsp: true,
262        docs_category: "dependency",
263    },
264    IssueKindMeta {
265        kind: Some(IssueKind::DuplicateExport),
266        code: "duplicate-export",
267        aliases: &[],
268        label: "Duplicate Exports",
269        config_key: Some("duplicate-exports"),
270        filter_flag: Some("--duplicate-exports"),
271        mcp_issue_type: Some("duplicate-exports"),
272        suppress_token: Some("duplicate-export"),
273        suppress_file_level: true,
274        lsp: true,
275        docs_category: "source",
276    },
277    IssueKindMeta {
278        kind: Some(IssueKind::TypeOnlyDependency),
279        code: "type-only-dependency",
280        aliases: &[],
281        label: "Type-Only Dependencies",
282        config_key: Some("type-only-dependencies"),
283        filter_flag: Some("--unused-deps"),
284        mcp_issue_type: None,
285        suppress_token: None,
286        suppress_file_level: false,
287        lsp: true,
288        docs_category: "dependency",
289    },
290    IssueKindMeta {
291        kind: Some(IssueKind::TestOnlyDependency),
292        code: "test-only-dependency",
293        aliases: &[],
294        label: "Test-Only Dependencies",
295        config_key: Some("test-only-dependencies"),
296        filter_flag: Some("--unused-deps"),
297        mcp_issue_type: None,
298        suppress_token: None,
299        suppress_file_level: false,
300        lsp: true,
301        docs_category: "dependency",
302    },
303    IssueKindMeta {
304        kind: Some(IssueKind::DevDependencyInProduction),
305        code: "dev-dependency-in-production",
306        aliases: &[],
307        label: "Dev Dependencies Used in Production",
308        config_key: Some("dev-dependencies-in-production"),
309        filter_flag: Some("--unused-deps"),
310        mcp_issue_type: None,
311        suppress_token: None,
312        suppress_file_level: false,
313        lsp: true,
314        docs_category: "dependency",
315    },
316    IssueKindMeta {
317        kind: Some(IssueKind::CircularDependency),
318        code: "circular-dependency",
319        aliases: &["circular-dependencies"],
320        label: "Circular Dependencies",
321        config_key: Some("circular-dependencies"),
322        filter_flag: Some("--circular-deps"),
323        mcp_issue_type: Some("circular-deps"),
324        suppress_token: Some("circular-dependency"),
325        suppress_file_level: false,
326        lsp: true,
327        docs_category: "architecture",
328    },
329    IssueKindMeta {
330        kind: Some(IssueKind::ReExportCycle),
331        code: "re-export-cycle",
332        aliases: &["re-export-cycles", "reexport-cycle", "reexport-cycles"],
333        label: "Re-Export Cycles",
334        config_key: Some("re-export-cycle"),
335        filter_flag: Some("--re-export-cycles"),
336        mcp_issue_type: Some("re-export-cycles"),
337        suppress_token: Some("re-export-cycle"),
338        suppress_file_level: true,
339        lsp: true,
340        docs_category: "architecture",
341    },
342    IssueKindMeta {
343        kind: Some(IssueKind::PackageCycle),
344        code: "package-cycle",
345        aliases: &["package-cycles"],
346        label: "Package Cycles",
347        config_key: Some("package-cycle"),
348        filter_flag: Some("--package-cycles"),
349        mcp_issue_type: Some("package-cycles"),
350        suppress_token: Some("package-cycle"),
351        suppress_file_level: false,
352        lsp: true,
353        docs_category: "architecture",
354    },
355    IssueKindMeta {
356        kind: Some(IssueKind::BoundaryViolation),
357        code: "boundary-violation",
358        aliases: &[],
359        label: "Boundary Violations",
360        config_key: Some("boundary-violation"),
361        filter_flag: Some("--boundary-violations"),
362        mcp_issue_type: Some("boundary-violations"),
363        suppress_token: Some("boundary-violation"),
364        suppress_file_level: false,
365        lsp: true,
366        docs_category: "architecture",
367    },
368    IssueKindMeta {
369        kind: None,
370        code: "boundary-coverage",
371        aliases: &["boundary-coverage-violations"],
372        label: "Boundary Coverage",
373        config_key: Some("boundary-violation"),
374        filter_flag: Some("--boundary-violations"),
375        mcp_issue_type: None,
376        suppress_token: Some("boundary-violation"),
377        suppress_file_level: true,
378        lsp: false,
379        docs_category: "architecture",
380    },
381    IssueKindMeta {
382        kind: Some(IssueKind::BoundaryViolation),
383        code: "boundary-call-violation",
384        aliases: &["boundary-calls", "boundary-call-violations"],
385        label: "Boundary Call Violations",
386        config_key: Some("boundary-violation"),
387        filter_flag: Some("--boundary-violations"),
388        mcp_issue_type: None,
389        suppress_token: Some("boundary-call-violation"),
390        suppress_file_level: false,
391        lsp: false,
392        docs_category: "architecture",
393    },
394    IssueKindMeta {
395        kind: Some(IssueKind::PolicyViolation),
396        code: "policy-violation",
397        aliases: &["policy-violations"],
398        label: "Policy Violations",
399        config_key: Some("policy-violation"),
400        filter_flag: Some("--policy-violations"),
401        mcp_issue_type: Some("policy-violations"),
402        suppress_token: Some("policy-violation"),
403        suppress_file_level: false,
404        lsp: true,
405        docs_category: "architecture",
406    },
407    IssueKindMeta {
408        kind: Some(IssueKind::InvalidClientExport),
409        code: "invalid-client-export",
410        aliases: &["invalid-client-exports"],
411        label: "Invalid Client Exports",
412        config_key: Some("invalid-client-export"),
413        filter_flag: None,
414        mcp_issue_type: None,
415        suppress_token: Some("invalid-client-export"),
416        suppress_file_level: false,
417        lsp: true,
418        docs_category: "framework",
419    },
420    IssueKindMeta {
421        kind: Some(IssueKind::MixedClientServerBarrel),
422        code: "mixed-client-server-barrel",
423        aliases: &["mixed-client-server-barrels"],
424        label: "Mixed Client/Server Barrels",
425        config_key: Some("mixed-client-server-barrel"),
426        filter_flag: None,
427        mcp_issue_type: None,
428        suppress_token: Some("mixed-client-server-barrel"),
429        suppress_file_level: false,
430        lsp: true,
431        docs_category: "framework",
432    },
433    IssueKindMeta {
434        kind: Some(IssueKind::MisplacedDirective),
435        code: "misplaced-directive",
436        aliases: &["misplaced-directives"],
437        label: "Misplaced Directives",
438        config_key: Some("misplaced-directive"),
439        filter_flag: None,
440        mcp_issue_type: None,
441        suppress_token: Some("misplaced-directive"),
442        suppress_file_level: false,
443        lsp: true,
444        docs_category: "framework",
445    },
446    IssueKindMeta {
447        kind: Some(IssueKind::UnprovidedInject),
448        code: "unprovided-inject",
449        aliases: &["unprovided-injects"],
450        label: "Unprovided Injects",
451        config_key: Some("unprovided-injects"),
452        filter_flag: Some("--unprovided-injects"),
453        mcp_issue_type: Some("unprovided-injects"),
454        suppress_token: Some("unprovided-inject"),
455        suppress_file_level: false,
456        lsp: true,
457        docs_category: "framework",
458    },
459    IssueKindMeta {
460        kind: Some(IssueKind::UnrenderedComponent),
461        code: "unrendered-component",
462        aliases: &["unrendered-components"],
463        label: "Unrendered Components",
464        config_key: Some("unrendered-components"),
465        filter_flag: Some("--unrendered-components"),
466        mcp_issue_type: Some("unrendered-components"),
467        suppress_token: Some("unrendered-component"),
468        suppress_file_level: false,
469        lsp: true,
470        docs_category: "framework",
471    },
472    IssueKindMeta {
473        kind: Some(IssueKind::UnusedComponentProp),
474        code: "unused-component-prop",
475        aliases: &["unused-component-props"],
476        label: "Unused Component Props",
477        config_key: Some("unused-component-props"),
478        filter_flag: Some("--unused-component-props"),
479        mcp_issue_type: Some("unused-component-props"),
480        suppress_token: Some("unused-component-prop"),
481        suppress_file_level: false,
482        lsp: true,
483        docs_category: "framework",
484    },
485    IssueKindMeta {
486        kind: Some(IssueKind::UnusedComponentEmit),
487        code: "unused-component-emit",
488        aliases: &["unused-component-emits"],
489        label: "Unused Component Emits",
490        config_key: Some("unused-component-emits"),
491        filter_flag: Some("--unused-component-emits"),
492        mcp_issue_type: Some("unused-component-emits"),
493        suppress_token: Some("unused-component-emit"),
494        suppress_file_level: false,
495        lsp: true,
496        docs_category: "framework",
497    },
498    IssueKindMeta {
499        kind: Some(IssueKind::UnusedComponentInput),
500        code: "unused-component-input",
501        aliases: &["unused-component-inputs"],
502        label: "Unused Component Inputs",
503        config_key: Some("unused-component-inputs"),
504        filter_flag: Some("--unused-component-inputs"),
505        mcp_issue_type: Some("unused-component-inputs"),
506        suppress_token: Some("unused-component-input"),
507        suppress_file_level: false,
508        lsp: true,
509        docs_category: "framework",
510    },
511    IssueKindMeta {
512        kind: Some(IssueKind::UnusedComponentOutput),
513        code: "unused-component-output",
514        aliases: &["unused-component-outputs"],
515        label: "Unused Component Outputs",
516        config_key: Some("unused-component-outputs"),
517        filter_flag: Some("--unused-component-outputs"),
518        mcp_issue_type: Some("unused-component-outputs"),
519        suppress_token: Some("unused-component-output"),
520        suppress_file_level: false,
521        lsp: true,
522        docs_category: "framework",
523    },
524    IssueKindMeta {
525        kind: Some(IssueKind::UnusedSvelteEvent),
526        code: "unused-svelte-event",
527        aliases: &["unused-svelte-events"],
528        label: "Unused Svelte Events",
529        config_key: Some("unused-svelte-events"),
530        filter_flag: Some("--unused-svelte-events"),
531        mcp_issue_type: Some("unused-svelte-events"),
532        suppress_token: Some("unused-svelte-event"),
533        suppress_file_level: false,
534        lsp: true,
535        docs_category: "framework",
536    },
537    IssueKindMeta {
538        kind: Some(IssueKind::UnusedServerAction),
539        code: "unused-server-action",
540        aliases: &["unused-server-actions"],
541        label: "Unused Server Actions",
542        config_key: Some("unused-server-actions"),
543        filter_flag: Some("--unused-server-actions"),
544        mcp_issue_type: Some("unused-server-actions"),
545        suppress_token: Some("unused-server-action"),
546        suppress_file_level: false,
547        lsp: true,
548        docs_category: "framework",
549    },
550    IssueKindMeta {
551        kind: Some(IssueKind::UnusedLoadDataKey),
552        code: "unused-load-data-key",
553        aliases: &["unused-load-data-keys"],
554        label: "Unused Load Data Keys",
555        config_key: Some("unused-load-data-keys"),
556        filter_flag: Some("--unused-load-data-keys"),
557        mcp_issue_type: Some("unused-load-data-keys"),
558        suppress_token: Some("unused-load-data-key"),
559        suppress_file_level: false,
560        lsp: true,
561        docs_category: "framework",
562    },
563    IssueKindMeta {
564        kind: Some(IssueKind::RouteCollision),
565        code: "route-collision",
566        aliases: &["route-collisions"],
567        label: "Route Collisions",
568        config_key: Some("route-collision"),
569        filter_flag: None,
570        mcp_issue_type: None,
571        suppress_token: Some("route-collision"),
572        suppress_file_level: true,
573        lsp: true,
574        docs_category: "framework",
575    },
576    IssueKindMeta {
577        kind: Some(IssueKind::DynamicSegmentNameConflict),
578        code: "dynamic-segment-name-conflict",
579        aliases: &["dynamic-segment-name-conflicts"],
580        label: "Dynamic Segment Conflicts",
581        config_key: Some("dynamic-segment-name-conflict"),
582        filter_flag: None,
583        mcp_issue_type: None,
584        suppress_token: Some("dynamic-segment-name-conflict"),
585        suppress_file_level: true,
586        lsp: true,
587        docs_category: "framework",
588    },
589    IssueKindMeta {
590        kind: Some(IssueKind::StaleSuppression),
591        code: "stale-suppression",
592        aliases: &[],
593        label: "Stale Suppressions",
594        config_key: Some("stale-suppressions"),
595        filter_flag: Some("--stale-suppressions"),
596        mcp_issue_type: Some("stale-suppressions"),
597        suppress_token: None,
598        suppress_file_level: false,
599        lsp: true,
600        docs_category: "source",
601    },
602    IssueKindMeta {
603        kind: Some(IssueKind::StaleSuppression),
604        code: "missing-suppression-reason",
605        aliases: &["missing-suppression-reasons"],
606        label: "Missing Suppression Reasons",
607        config_key: Some("require-suppression-reason"),
608        filter_flag: Some("--stale-suppressions"),
609        mcp_issue_type: None,
610        suppress_token: None,
611        suppress_file_level: false,
612        lsp: false,
613        docs_category: "source",
614    },
615    IssueKindMeta {
616        kind: Some(IssueKind::PnpmCatalogEntry),
617        code: "unused-catalog-entry",
618        aliases: &["catalog", "unused-catalog-entries"],
619        label: "Unused Catalog Entries",
620        config_key: Some("unused-catalog-entries"),
621        filter_flag: Some("--unused-catalog-entries"),
622        mcp_issue_type: Some("unused-catalog-entries"),
623        suppress_token: None,
624        suppress_file_level: false,
625        lsp: true,
626        docs_category: "dependency",
627    },
628    IssueKindMeta {
629        kind: Some(IssueKind::EmptyCatalogGroup),
630        code: "empty-catalog-group",
631        aliases: &["empty-catalog", "empty-catalog-groups"],
632        label: "Empty Catalog Groups",
633        config_key: Some("empty-catalog-groups"),
634        filter_flag: Some("--empty-catalog-groups"),
635        mcp_issue_type: Some("empty-catalog-groups"),
636        suppress_token: None,
637        suppress_file_level: false,
638        lsp: true,
639        docs_category: "dependency",
640    },
641    IssueKindMeta {
642        kind: Some(IssueKind::UnresolvedCatalogReference),
643        code: "unresolved-catalog-reference",
644        aliases: &["unresolved-catalog", "unresolved-catalog-references"],
645        label: "Unresolved Catalog References",
646        config_key: Some("unresolved-catalog-references"),
647        filter_flag: Some("--unresolved-catalog-references"),
648        mcp_issue_type: Some("unresolved-catalog-references"),
649        suppress_token: None,
650        suppress_file_level: false,
651        lsp: true,
652        docs_category: "dependency",
653    },
654    IssueKindMeta {
655        kind: Some(IssueKind::UnusedDependencyOverride),
656        code: "unused-dependency-override",
657        aliases: &[
658            "unused-dependency-overrides",
659            "unused-override",
660            "unused-overrides",
661        ],
662        label: "Unused Dependency Overrides",
663        config_key: Some("unused-dependency-overrides"),
664        filter_flag: Some("--unused-dependency-overrides"),
665        mcp_issue_type: Some("unused-dependency-overrides"),
666        suppress_token: None,
667        suppress_file_level: false,
668        lsp: true,
669        docs_category: "dependency",
670    },
671    IssueKindMeta {
672        kind: Some(IssueKind::MisconfiguredDependencyOverride),
673        code: "misconfigured-dependency-override",
674        aliases: &[
675            "misconfigured-dependency-overrides",
676            "misconfigured-override",
677            "misconfigured-overrides",
678        ],
679        label: "Misconfigured Dependency Overrides",
680        config_key: Some("misconfigured-dependency-overrides"),
681        filter_flag: Some("--misconfigured-dependency-overrides"),
682        mcp_issue_type: Some("misconfigured-dependency-overrides"),
683        suppress_token: None,
684        suppress_file_level: false,
685        lsp: true,
686        docs_category: "dependency",
687    },
688    IssueKindMeta {
689        kind: Some(IssueKind::SecuritySink),
690        code: "security-sink",
691        aliases: &[],
692        label: "Security Sink Candidates",
693        config_key: Some("security-sink"),
694        filter_flag: None,
695        mcp_issue_type: None,
696        suppress_token: Some("security-sink"),
697        suppress_file_level: false,
698        lsp: true,
699        docs_category: "security",
700    },
701    IssueKindMeta {
702        kind: Some(IssueKind::SecurityClientServerLeak),
703        code: "security-client-server-leak",
704        aliases: &[],
705        label: "Security Client-Server Leaks",
706        config_key: Some("security-client-server-leak"),
707        filter_flag: None,
708        mcp_issue_type: None,
709        suppress_token: Some("security-client-server-leak"),
710        suppress_file_level: true,
711        lsp: true,
712        docs_category: "security",
713    },
714    IssueKindMeta {
715        kind: Some(IssueKind::CoverageGaps),
716        code: "coverage-gaps",
717        aliases: &[],
718        label: "Coverage Gaps",
719        config_key: Some("coverage-gaps"),
720        filter_flag: None,
721        mcp_issue_type: None,
722        suppress_token: Some("coverage-gaps"),
723        suppress_file_level: true,
724        lsp: false,
725        docs_category: "health",
726    },
727    IssueKindMeta {
728        kind: Some(IssueKind::FeatureFlag),
729        code: "feature-flag",
730        aliases: &[],
731        label: "Feature Flags",
732        config_key: Some("feature-flags"),
733        filter_flag: None,
734        mcp_issue_type: None,
735        suppress_token: Some("feature-flag"),
736        suppress_file_level: false,
737        lsp: false,
738        docs_category: "flags",
739    },
740    IssueKindMeta {
741        kind: Some(IssueKind::Complexity),
742        code: "complexity",
743        aliases: &[],
744        label: "Complexity",
745        config_key: None,
746        filter_flag: None,
747        mcp_issue_type: None,
748        suppress_token: Some("complexity"),
749        suppress_file_level: false,
750        lsp: false,
751        docs_category: "health",
752    },
753    IssueKindMeta {
754        kind: Some(IssueKind::PropDrilling),
755        code: "prop-drilling",
756        aliases: &[],
757        label: "Prop Drilling",
758        config_key: Some("prop-drilling"),
759        filter_flag: None,
760        mcp_issue_type: None,
761        suppress_token: Some("prop-drilling"),
762        suppress_file_level: false,
763        lsp: true,
764        docs_category: "source",
765    },
766    IssueKindMeta {
767        kind: Some(IssueKind::ThinWrapper),
768        code: "thin-wrapper",
769        aliases: &["thin-wrappers"],
770        label: "Thin Wrappers",
771        config_key: Some("thin-wrapper"),
772        filter_flag: None,
773        mcp_issue_type: None,
774        suppress_token: Some("thin-wrapper"),
775        suppress_file_level: false,
776        lsp: true,
777        docs_category: "source",
778    },
779    IssueKindMeta {
780        kind: Some(IssueKind::DuplicatePropShape),
781        code: "duplicate-prop-shape",
782        aliases: &["duplicate-prop-shapes"],
783        label: "Duplicate Prop Shapes",
784        config_key: Some("duplicate-prop-shape"),
785        filter_flag: None,
786        mcp_issue_type: None,
787        suppress_token: Some("duplicate-prop-shape"),
788        suppress_file_level: false,
789        lsp: true,
790        docs_category: "source",
791    },
792    IssueKindMeta {
793        kind: Some(IssueKind::CssTokenDrift),
794        code: "css-token-drift",
795        aliases: &[],
796        label: "CSS Token Drift",
797        config_key: Some("css-token-drift"),
798        filter_flag: None,
799        mcp_issue_type: None,
800        suppress_token: Some("css-token-drift"),
801        suppress_file_level: false,
802        lsp: false,
803        docs_category: "health",
804    },
805    IssueKindMeta {
806        kind: Some(IssueKind::CssDuplicateBlock),
807        code: "css-duplicate-block",
808        aliases: &[],
809        label: "CSS Duplicate Block",
810        config_key: Some("css-duplicate-block"),
811        filter_flag: None,
812        mcp_issue_type: None,
813        suppress_token: Some("css-duplicate-block"),
814        suppress_file_level: false,
815        lsp: false,
816        docs_category: "health",
817    },
818    IssueKindMeta {
819        kind: Some(IssueKind::CssSelectorComplexity),
820        code: "css-selector-complexity",
821        aliases: &[],
822        label: "CSS Selector Complexity",
823        config_key: Some("css-selector-complexity"),
824        filter_flag: None,
825        mcp_issue_type: None,
826        suppress_token: Some("css-selector-complexity"),
827        suppress_file_level: false,
828        lsp: false,
829        docs_category: "health",
830    },
831    IssueKindMeta {
832        kind: Some(IssueKind::CssDeadSurface),
833        code: "css-dead-surface",
834        aliases: &[],
835        label: "CSS Dead Surface",
836        config_key: Some("css-dead-surface"),
837        filter_flag: None,
838        mcp_issue_type: None,
839        suppress_token: Some("css-dead-surface"),
840        suppress_file_level: false,
841        lsp: false,
842        docs_category: "health",
843    },
844    IssueKindMeta {
845        kind: Some(IssueKind::CssBrokenReference),
846        code: "css-broken-reference",
847        aliases: &[],
848        label: "CSS Broken Reference",
849        config_key: Some("css-broken-reference"),
850        filter_flag: None,
851        mcp_issue_type: None,
852        suppress_token: Some("css-broken-reference"),
853        suppress_file_level: false,
854        lsp: false,
855        docs_category: "health",
856    },
857];
858
859/// Shared contract facts for serialized `AnalysisResults` arrays.
860#[derive(Debug, Clone, Copy, PartialEq, Eq)]
861#[non_exhaustive]
862pub struct IssueResultMeta {
863    /// Canonical issue code that owns this result array.
864    pub code: &'static str,
865    /// Short SARIF rule description.
866    pub sarif_description: &'static str,
867    /// Explanation emitted in dead-code `_meta.rules`.
868    pub meta_description: &'static str,
869    /// Documentation path emitted in dead-code `_meta.rules`.
870    pub meta_docs_path: &'static str,
871    /// Human-readable name emitted in dead-code `_meta.rules`.
872    pub meta_name: &'static str,
873    /// Label used by CI summary tables.
874    pub summary_label: &'static str,
875    /// Documentation anchor under `/explanations/dead-code`.
876    pub docs_anchor: &'static str,
877    /// Serialized `AnalysisResults` array key that carries this issue row.
878    pub result_key: &'static str,
879    /// Whether `result_key` contributes to `AnalysisResults::total_issues()`.
880    pub counts_in_total: bool,
881}
882
883/// TypeScript backwards-compat alias emitted for a dead-code result row.
884#[derive(Debug, Clone, Copy, PartialEq, Eq)]
885pub struct TsAliasMeta {
886    /// Bare alias name kept available from the published `fallow/types` subpath.
887    pub name: &'static str,
888    /// Generated `*Finding` wrapper type the alias resolves to.
889    pub parent: &'static str,
890}
891
892/// TypeScript backwards-compat alias row for a dead-code result code.
893#[derive(Debug, Clone, Copy, PartialEq, Eq)]
894pub struct IssueTsAliasMeta {
895    /// Canonical issue code that owns this alias.
896    pub code: &'static str,
897    /// Published TypeScript alias policy.
898    pub alias: TsAliasMeta,
899}
900
901/// Published TypeScript backwards-compat aliases.
902pub const ISSUE_TS_ALIAS_META: &[IssueTsAliasMeta] = &[
903    IssueTsAliasMeta {
904        code: "unused-file",
905        alias: TsAliasMeta {
906            name: "UnusedFile",
907            parent: "UnusedFileFinding",
908        },
909    },
910    IssueTsAliasMeta {
911        code: "unused-export",
912        alias: TsAliasMeta {
913            name: "UnusedExport",
914            parent: "UnusedExportFinding",
915        },
916    },
917    IssueTsAliasMeta {
918        code: "private-type-leak",
919        alias: TsAliasMeta {
920            name: "PrivateTypeLeak",
921            parent: "PrivateTypeLeakFinding",
922        },
923    },
924    IssueTsAliasMeta {
925        code: "deprecated-export-in-use",
926        alias: TsAliasMeta {
927            name: "DeprecatedExportInUse",
928            parent: "DeprecatedExportInUseFinding",
929        },
930    },
931    IssueTsAliasMeta {
932        code: "unused-dependency",
933        alias: TsAliasMeta {
934            name: "UnusedDependency",
935            parent: "UnusedDependencyFinding",
936        },
937    },
938    IssueTsAliasMeta {
939        code: "unused-dev-dependency",
940        alias: TsAliasMeta {
941            name: "UnusedDependency",
942            parent: "UnusedDevDependencyFinding",
943        },
944    },
945    IssueTsAliasMeta {
946        code: "unused-optional-dependency",
947        alias: TsAliasMeta {
948            name: "UnusedDependency",
949            parent: "UnusedOptionalDependencyFinding",
950        },
951    },
952    IssueTsAliasMeta {
953        code: "unused-enum-member",
954        alias: TsAliasMeta {
955            name: "UnusedMember",
956            parent: "UnusedEnumMemberFinding",
957        },
958    },
959    IssueTsAliasMeta {
960        code: "unused-class-member",
961        alias: TsAliasMeta {
962            name: "UnusedMember",
963            parent: "UnusedClassMemberFinding",
964        },
965    },
966    IssueTsAliasMeta {
967        code: "unused-store-member",
968        alias: TsAliasMeta {
969            name: "UnusedMember",
970            parent: "UnusedStoreMemberFinding",
971        },
972    },
973    IssueTsAliasMeta {
974        code: "unresolved-import",
975        alias: TsAliasMeta {
976            name: "UnresolvedImport",
977            parent: "UnresolvedImportFinding",
978        },
979    },
980    IssueTsAliasMeta {
981        code: "unlisted-dependency",
982        alias: TsAliasMeta {
983            name: "UnlistedDependency",
984            parent: "UnlistedDependencyFinding",
985        },
986    },
987    IssueTsAliasMeta {
988        code: "duplicate-export",
989        alias: TsAliasMeta {
990            name: "DuplicateExport",
991            parent: "DuplicateExportFinding",
992        },
993    },
994    IssueTsAliasMeta {
995        code: "type-only-dependency",
996        alias: TsAliasMeta {
997            name: "TypeOnlyDependency",
998            parent: "TypeOnlyDependencyFinding",
999        },
1000    },
1001    IssueTsAliasMeta {
1002        code: "test-only-dependency",
1003        alias: TsAliasMeta {
1004            name: "TestOnlyDependency",
1005            parent: "TestOnlyDependencyFinding",
1006        },
1007    },
1008    IssueTsAliasMeta {
1009        code: "dev-dependency-in-production",
1010        alias: TsAliasMeta {
1011            name: "DevDependencyInProduction",
1012            parent: "DevDependencyInProductionFinding",
1013        },
1014    },
1015    IssueTsAliasMeta {
1016        code: "circular-dependency",
1017        alias: TsAliasMeta {
1018            name: "CircularDependency",
1019            parent: "CircularDependencyFinding",
1020        },
1021    },
1022    IssueTsAliasMeta {
1023        code: "re-export-cycle",
1024        alias: TsAliasMeta {
1025            name: "ReExportCycle",
1026            parent: "ReExportCycleFinding",
1027        },
1028    },
1029    IssueTsAliasMeta {
1030        code: "package-cycle",
1031        alias: TsAliasMeta {
1032            name: "PackageCycle",
1033            parent: "PackageCycleFinding",
1034        },
1035    },
1036    IssueTsAliasMeta {
1037        code: "boundary-violation",
1038        alias: TsAliasMeta {
1039            name: "BoundaryViolation",
1040            parent: "BoundaryViolationFinding",
1041        },
1042    },
1043    IssueTsAliasMeta {
1044        code: "unused-catalog-entry",
1045        alias: TsAliasMeta {
1046            name: "UnusedCatalogEntry",
1047            parent: "UnusedCatalogEntryFinding",
1048        },
1049    },
1050    IssueTsAliasMeta {
1051        code: "empty-catalog-group",
1052        alias: TsAliasMeta {
1053            name: "EmptyCatalogGroup",
1054            parent: "EmptyCatalogGroupFinding",
1055        },
1056    },
1057    IssueTsAliasMeta {
1058        code: "unresolved-catalog-reference",
1059        alias: TsAliasMeta {
1060            name: "UnresolvedCatalogReference",
1061            parent: "UnresolvedCatalogReferenceFinding",
1062        },
1063    },
1064    IssueTsAliasMeta {
1065        code: "unused-dependency-override",
1066        alias: TsAliasMeta {
1067            name: "UnusedDependencyOverride",
1068            parent: "UnusedDependencyOverrideFinding",
1069        },
1070    },
1071    IssueTsAliasMeta {
1072        code: "misconfigured-dependency-override",
1073        alias: TsAliasMeta {
1074            name: "MisconfiguredDependencyOverride",
1075            parent: "MisconfiguredDependencyOverrideFinding",
1076        },
1077    },
1078];
1079
1080/// All shared issue-to-result metadata rows.
1081pub const ISSUE_RESULT_META: &[IssueResultMeta] = &[
1082    IssueResultMeta {
1083        code: "unused-file",
1084        sarif_description: "File is not reachable from any entry point",
1085        meta_description: "Source files that are not imported by any other module and are not entry points. Detection uses graph reachability from configured entry points.",
1086        meta_docs_path: "explanations/dead-code#unused-files",
1087        meta_name: "Unused Files",
1088        summary_label: "Unused files",
1089        docs_anchor: "unused-files",
1090        result_key: "unused_files",
1091        counts_in_total: true,
1092    },
1093    IssueResultMeta {
1094        code: "unused-export",
1095        sarif_description: "Export is never imported",
1096        meta_description: "Named exports that are never imported by any other module in the project, including direct exports and re-exports through barrel files.",
1097        meta_docs_path: "explanations/dead-code#unused-exports",
1098        meta_name: "Unused Exports",
1099        summary_label: "Unused exports",
1100        docs_anchor: "unused-exports",
1101        result_key: "unused_exports",
1102        counts_in_total: true,
1103    },
1104    IssueResultMeta {
1105        code: "unused-type",
1106        sarif_description: "Type export is never imported",
1107        meta_description: "Type-only exports that are never imported. These do not generate runtime code but add maintenance burden.",
1108        meta_docs_path: "explanations/dead-code#unused-types",
1109        meta_name: "Unused Type Exports",
1110        summary_label: "Unused types",
1111        docs_anchor: "unused-types",
1112        result_key: "unused_types",
1113        counts_in_total: true,
1114    },
1115    IssueResultMeta {
1116        code: "private-type-leak",
1117        sarif_description: "Exported signature references a private type",
1118        meta_description: "Exported values or types whose public TypeScript signature references a same-file type declaration that is not exported.",
1119        meta_docs_path: "explanations/dead-code#private-type-leaks",
1120        meta_name: "Private Type Leaks",
1121        summary_label: "Private type leaks",
1122        docs_anchor: "private-type-leaks",
1123        result_key: "private_type_leaks",
1124        counts_in_total: true,
1125    },
1126    IssueResultMeta {
1127        code: "deprecated-export-in-use",
1128        sarif_description: "Export marked @deprecated is still referenced",
1129        meta_description: "Exports whose leading JSDoc carries @deprecated and that still have at least one consumer in a reachable file. Each finding lists the consumer count and a capped consumer sample.",
1130        meta_docs_path: "explanations/dead-code#deprecated-exports-in-use",
1131        meta_name: "Deprecated Exports in Use",
1132        summary_label: "Deprecated exports in use",
1133        docs_anchor: "deprecated-exports-in-use",
1134        result_key: "deprecated_exports_in_use",
1135        counts_in_total: true,
1136    },
1137    IssueResultMeta {
1138        code: "unused-dependency",
1139        sarif_description: "Dependency listed but never imported",
1140        meta_description: "Packages listed in dependencies that are never imported or required by any source file.",
1141        meta_docs_path: "explanations/dead-code#unused-dependencies",
1142        meta_name: "Unused Dependencies",
1143        summary_label: "Unused dependencies",
1144        docs_anchor: "unused-dependencies",
1145        result_key: "unused_dependencies",
1146        counts_in_total: true,
1147    },
1148    IssueResultMeta {
1149        code: "unused-dev-dependency",
1150        sarif_description: "Dev dependency listed but never imported",
1151        meta_description: "Packages listed in devDependencies that are never imported by test files, config files, or scripts.",
1152        meta_docs_path: "explanations/dead-code#unused-devdependencies",
1153        meta_name: "Unused Dev Dependencies",
1154        summary_label: "Unused devDependencies",
1155        docs_anchor: "unused-dependencies",
1156        result_key: "unused_dev_dependencies",
1157        counts_in_total: true,
1158    },
1159    IssueResultMeta {
1160        code: "unused-optional-dependency",
1161        sarif_description: "Optional dependency listed but never imported",
1162        meta_description: "Packages listed in optionalDependencies that are never imported.",
1163        meta_docs_path: "explanations/dead-code#unused-optionaldependencies",
1164        meta_name: "Unused Optional Dependencies",
1165        summary_label: "Unused optionalDependencies",
1166        docs_anchor: "unused-dependencies",
1167        result_key: "unused_optional_dependencies",
1168        counts_in_total: true,
1169    },
1170    IssueResultMeta {
1171        code: "unused-enum-member",
1172        sarif_description: "Enum member is never referenced",
1173        meta_description: "Enum members that are never referenced in the codebase.",
1174        meta_docs_path: "explanations/dead-code#unused-enum-members",
1175        meta_name: "Unused Enum Members",
1176        summary_label: "Unused enum members",
1177        docs_anchor: "unused-enum-members",
1178        result_key: "unused_enum_members",
1179        counts_in_total: true,
1180    },
1181    IssueResultMeta {
1182        code: "unused-class-member",
1183        sarif_description: "Class member is never referenced",
1184        meta_description: "Class methods and properties that are never referenced outside the class.",
1185        meta_docs_path: "explanations/dead-code#unused-class-members",
1186        meta_name: "Unused Class Members",
1187        summary_label: "Unused class members",
1188        docs_anchor: "unused-class-members",
1189        result_key: "unused_class_members",
1190        counts_in_total: true,
1191    },
1192    IssueResultMeta {
1193        code: "unused-store-member",
1194        sarif_description: "Store member is never accessed by any consumer",
1195        meta_description: "Pinia store members declared but never accessed by any consumer project-wide.",
1196        meta_docs_path: "explanations/dead-code#unused-store-members",
1197        meta_name: "Unused Store Members",
1198        summary_label: "Unused store members",
1199        docs_anchor: "unused-store-members",
1200        result_key: "unused_store_members",
1201        counts_in_total: true,
1202    },
1203    IssueResultMeta {
1204        code: "unresolved-import",
1205        sarif_description: "Import could not be resolved",
1206        meta_description: "Import specifiers that could not be resolved to a file on disk.",
1207        meta_docs_path: "explanations/dead-code#unresolved-imports",
1208        meta_name: "Unresolved Imports",
1209        summary_label: "Unresolved imports",
1210        docs_anchor: "unresolved-imports",
1211        result_key: "unresolved_imports",
1212        counts_in_total: true,
1213    },
1214    IssueResultMeta {
1215        code: "unlisted-dependency",
1216        sarif_description: "Dependency used but not in package.json",
1217        meta_description: "Packages imported in source code but not listed in package.json.",
1218        meta_docs_path: "explanations/dead-code#unlisted-dependencies",
1219        meta_name: "Unlisted Dependencies",
1220        summary_label: "Unlisted dependencies",
1221        docs_anchor: "unlisted-dependencies",
1222        result_key: "unlisted_dependencies",
1223        counts_in_total: true,
1224    },
1225    IssueResultMeta {
1226        code: "duplicate-export",
1227        sarif_description: "Export name appears in multiple modules",
1228        meta_description: "The same export name is defined in multiple modules.",
1229        meta_docs_path: "explanations/dead-code#duplicate-exports",
1230        meta_name: "Duplicate Exports",
1231        summary_label: "Duplicate exports",
1232        docs_anchor: "duplicate-exports",
1233        result_key: "duplicate_exports",
1234        counts_in_total: true,
1235    },
1236    IssueResultMeta {
1237        code: "type-only-dependency",
1238        sarif_description: "Production dependency only used via type-only imports",
1239        meta_description: "Production dependencies that are only imported via type-only imports.",
1240        meta_docs_path: "explanations/dead-code#type-only-dependencies",
1241        meta_name: "Type-only Dependencies",
1242        summary_label: "Type-only dependencies",
1243        docs_anchor: "type-only-dependencies",
1244        result_key: "type_only_dependencies",
1245        counts_in_total: true,
1246    },
1247    IssueResultMeta {
1248        code: "test-only-dependency",
1249        sarif_description: "Production dependency only imported by test files",
1250        meta_description: "Production dependencies that are only imported from test files.",
1251        meta_docs_path: "explanations/dead-code#test-only-dependencies",
1252        meta_name: "Test-only Dependencies",
1253        summary_label: "Test-only dependencies",
1254        docs_anchor: "test-only-dependencies",
1255        result_key: "test_only_dependencies",
1256        counts_in_total: true,
1257    },
1258    IssueResultMeta {
1259        code: "dev-dependency-in-production",
1260        sarif_description: "devDependency imported by production code with a runtime import",
1261        meta_description: "devDependencies imported by production source code via a runtime/value import; they should be promoted to dependencies.",
1262        meta_docs_path: "explanations/dead-code#dev-dependencies-in-production",
1263        meta_name: "Dev Dependencies Used in Production",
1264        summary_label: "Dev dependencies used in production",
1265        docs_anchor: "dev-dependencies-in-production",
1266        result_key: "dev_dependencies_in_production",
1267        counts_in_total: true,
1268    },
1269    IssueResultMeta {
1270        code: "circular-dependency",
1271        sarif_description: "Circular dependency chain detected",
1272        meta_description: "A cycle in the module import graph.",
1273        meta_docs_path: "explanations/dead-code#circular-dependencies",
1274        meta_name: "Circular Dependencies",
1275        summary_label: "Circular dependencies",
1276        docs_anchor: "circular-dependencies",
1277        result_key: "circular_dependencies",
1278        counts_in_total: true,
1279    },
1280    IssueResultMeta {
1281        code: "re-export-cycle",
1282        sarif_description: "Two or more barrel files re-export from each other in a loop",
1283        meta_description: "A barrel file re-exports from another barrel that ultimately re-exports back.",
1284        meta_docs_path: "explanations/dead-code#re-export-cycles",
1285        meta_name: "Re-Export Cycles",
1286        summary_label: "Re-export cycles",
1287        docs_anchor: "re-export-cycles",
1288        result_key: "re_export_cycles",
1289        counts_in_total: true,
1290    },
1291    IssueResultMeta {
1292        code: "package-cycle",
1293        sarif_description: "Two or more workspace packages import each other in a loop",
1294        meta_description: "Workspace packages whose resolved imports form a cycle, even when no file-level cycle exists.",
1295        meta_docs_path: "explanations/dead-code#package-cycles",
1296        meta_name: "Package Cycles",
1297        summary_label: "Package cycles",
1298        docs_anchor: "package-cycles",
1299        result_key: "package_cycles",
1300        counts_in_total: true,
1301    },
1302    IssueResultMeta {
1303        code: "boundary-violation",
1304        sarif_description: "Import crosses a configured architecture boundary",
1305        meta_description: "A module imports from a zone that its configured boundary rules do not allow.",
1306        meta_docs_path: "explanations/dead-code#boundary-violations",
1307        meta_name: "Boundary Violations",
1308        summary_label: "Boundary violations",
1309        docs_anchor: "boundary-violations",
1310        result_key: "boundary_violations",
1311        counts_in_total: true,
1312    },
1313    IssueResultMeta {
1314        code: "boundary-coverage",
1315        sarif_description: "Source file matches no configured architecture boundary zone",
1316        meta_description: "A reachable source file is not assigned to any configured boundary zone while boundary coverage is required.",
1317        meta_docs_path: "explanations/dead-code#boundary-violations",
1318        meta_name: "Boundary Coverage",
1319        summary_label: "Boundary coverage",
1320        docs_anchor: "boundary-violations",
1321        result_key: "boundary_coverage_violations",
1322        counts_in_total: true,
1323    },
1324    IssueResultMeta {
1325        code: "boundary-call-violation",
1326        sarif_description: "Zoned file calls a callee its zone forbids",
1327        meta_description: "A file classified into a boundary zone calls a callee matching one of the zone's forbidden call patterns.",
1328        meta_docs_path: "explanations/dead-code#boundary-violations",
1329        meta_name: "Boundary Call Violation",
1330        summary_label: "Boundary calls",
1331        docs_anchor: "boundary-violations",
1332        result_key: "boundary_call_violations",
1333        counts_in_total: true,
1334    },
1335    IssueResultMeta {
1336        code: "policy-violation",
1337        sarif_description: "Banned usage matched a rule-pack rule",
1338        meta_description: "A call site, import, or catalogue-derived effect matched a configured rule pack rule.",
1339        meta_docs_path: "explanations/dead-code#policy-violations",
1340        meta_name: "Policy Violation",
1341        summary_label: "Policy violations",
1342        docs_anchor: "policy-violations",
1343        result_key: "policy_violations",
1344        counts_in_total: true,
1345    },
1346    IssueResultMeta {
1347        code: "invalid-client-export",
1348        sarif_description: "\"use client\" file exports a server-only / route-config name",
1349        meta_description: "A file carrying the use client directive also exports a Next.js server-only or route-segment config name.",
1350        meta_docs_path: "explanations/dead-code#invalid-client-exports",
1351        meta_name: "Invalid client export",
1352        summary_label: "Invalid client exports",
1353        docs_anchor: "invalid-client-exports",
1354        result_key: "invalid_client_exports",
1355        counts_in_total: true,
1356    },
1357    IssueResultMeta {
1358        code: "mixed-client-server-barrel",
1359        sarif_description: "Barrel re-exports both a \"use client\" module and a server-only module",
1360        meta_description: "A barrel file forwards a name from a use client module alongside a name from a server-only module.",
1361        meta_docs_path: "explanations/dead-code#mixed-client-server-barrels",
1362        meta_name: "Mixed client/server barrel",
1363        summary_label: "Mixed client/server barrels",
1364        docs_anchor: "mixed-client-server-barrels",
1365        result_key: "mixed_client_server_barrels",
1366        counts_in_total: true,
1367    },
1368    IssueResultMeta {
1369        code: "misplaced-directive",
1370        sarif_description: "\"use client\" / \"use server\" directive is not in the leading position and is ignored",
1371        meta_description: "A use client or use server directive string appears after a non-directive statement and is ignored.",
1372        meta_docs_path: "explanations/dead-code#misplaced-directives",
1373        meta_name: "Misplaced directive",
1374        summary_label: "Misplaced directives",
1375        docs_anchor: "misplaced-directives",
1376        result_key: "misplaced_directives",
1377        counts_in_total: true,
1378    },
1379    IssueResultMeta {
1380        code: "unprovided-inject",
1381        sarif_description: "inject() / getContext() reads a key that no provide() / setContext() supplies",
1382        meta_description: "A Vue inject or Svelte getContext reads a dependency-injection key that no matching provider supplies.",
1383        meta_docs_path: "explanations/dead-code#unprovided-injects",
1384        meta_name: "Unprovided injects",
1385        summary_label: "Unprovided injects",
1386        docs_anchor: "unprovided-inject",
1387        result_key: "unprovided_injects",
1388        counts_in_total: true,
1389    },
1390    IssueResultMeta {
1391        code: "unrendered-component",
1392        sarif_description: "A Vue / Svelte component is reachable through a barrel but rendered nowhere",
1393        meta_description: "A Vue or Svelte single-file component is reachable through the graph but rendered nowhere in the project.",
1394        meta_docs_path: "explanations/dead-code#unrendered-components",
1395        meta_name: "Unrendered components",
1396        summary_label: "Unrendered components",
1397        docs_anchor: "unrendered-component",
1398        result_key: "unrendered_components",
1399        counts_in_total: true,
1400    },
1401    IssueResultMeta {
1402        code: "unused-component-prop",
1403        sarif_description: "A Vue, Svelte, or React component prop is referenced nowhere in its own component",
1404        meta_description: "A declared Vue, Svelte, React, or Preact component prop is referenced nowhere inside its own component.",
1405        meta_docs_path: "explanations/dead-code#unused-component-props",
1406        meta_name: "Unused component props",
1407        summary_label: "Unused component props",
1408        docs_anchor: "unused-component-prop",
1409        result_key: "unused_component_props",
1410        counts_in_total: true,
1411    },
1412    IssueResultMeta {
1413        code: "unused-component-emit",
1414        sarif_description: "A Vue <script setup> defineEmits event is emitted nowhere in its own component",
1415        meta_description: "A Vue script setup defineEmits event is emitted nowhere in its own component.",
1416        meta_docs_path: "explanations/dead-code#unused-component-emits",
1417        meta_name: "Unused component emits",
1418        summary_label: "Unused component emits",
1419        docs_anchor: "unused-component-emit",
1420        result_key: "unused_component_emits",
1421        counts_in_total: true,
1422    },
1423    IssueResultMeta {
1424        code: "unused-component-input",
1425        sarif_description: "An Angular @Input() / signal input() / model() input is read nowhere in its own component",
1426        meta_description: "An Angular input is read nowhere in its own component.",
1427        meta_docs_path: "explanations/dead-code#unused-component-inputs",
1428        meta_name: "Unused component inputs",
1429        summary_label: "Unused component inputs",
1430        docs_anchor: "unused-component-input",
1431        result_key: "unused_component_inputs",
1432        counts_in_total: true,
1433    },
1434    IssueResultMeta {
1435        code: "unused-component-output",
1436        sarif_description: "An Angular @Output() / signal output() output is emitted nowhere in its own component",
1437        meta_description: "An Angular output is emitted nowhere in its own component.",
1438        meta_docs_path: "explanations/dead-code#unused-component-outputs",
1439        meta_name: "Unused component outputs",
1440        summary_label: "Unused component outputs",
1441        docs_anchor: "unused-component-output",
1442        result_key: "unused_component_outputs",
1443        counts_in_total: true,
1444    },
1445    IssueResultMeta {
1446        code: "unused-svelte-event",
1447        sarif_description: "A Svelte component dispatches a createEventDispatcher event whose name is listened to nowhere in the project",
1448        meta_description: "A Svelte component dispatches a custom event whose name is listened to nowhere in the analyzed project.",
1449        meta_docs_path: "explanations/dead-code#unused-svelte-events",
1450        meta_name: "Unused Svelte events",
1451        summary_label: "Unused Svelte events",
1452        docs_anchor: "unused-svelte-event",
1453        result_key: "unused_svelte_events",
1454        counts_in_total: true,
1455    },
1456    IssueResultMeta {
1457        code: "unused-server-action",
1458        sarif_description: "A Next.js Server Action exported from a \"use server\" file is referenced by no code in the project",
1459        meta_description: "A Next.js Server Action exported from a use server file is referenced by no code in the project.",
1460        meta_docs_path: "explanations/dead-code#unused-server-actions",
1461        meta_name: "Unused server actions",
1462        summary_label: "Unused server actions",
1463        docs_anchor: "unused-server-action",
1464        result_key: "unused_server_actions",
1465        counts_in_total: true,
1466    },
1467    IssueResultMeta {
1468        code: "unused-load-data-key",
1469        sarif_description: "A SvelteKit load() return-object key is read by no consumer",
1470        meta_description: "A SvelteKit load return-object key is read by no route or project-wide consumer.",
1471        meta_docs_path: "explanations/dead-code#unused-load-data-keys",
1472        meta_name: "Unused load data keys",
1473        summary_label: "Unused load data keys",
1474        docs_anchor: "unused-load-data-key",
1475        result_key: "unused_load_data_keys",
1476        counts_in_total: true,
1477    },
1478    IssueResultMeta {
1479        code: "route-collision",
1480        sarif_description: "Two or more Next.js App Router route files resolve to the same URL",
1481        meta_description: "Two or more Next.js App Router route files resolve to the same URL within one app root.",
1482        meta_docs_path: "explanations/dead-code#route-collisions",
1483        meta_name: "Route collision",
1484        summary_label: "Route collisions",
1485        docs_anchor: "route-collisions",
1486        result_key: "route_collisions",
1487        counts_in_total: true,
1488    },
1489    IssueResultMeta {
1490        code: "dynamic-segment-name-conflict",
1491        sarif_description: "Sibling Next.js dynamic route segments use different slug names at the same position",
1492        meta_description: "Sibling Next.js dynamic route segments use different slug names at the same position.",
1493        meta_docs_path: "explanations/dead-code#dynamic-segment-name-conflicts",
1494        meta_name: "Dynamic segment name conflict",
1495        summary_label: "Dynamic segment conflicts",
1496        docs_anchor: "dynamic-segment-name-conflicts",
1497        result_key: "dynamic_segment_name_conflicts",
1498        counts_in_total: true,
1499    },
1500    IssueResultMeta {
1501        code: "stale-suppression",
1502        sarif_description: "Suppression comment or tag no longer matches any issue",
1503        meta_description: "A fallow suppression comment or tag no longer matches any active issue.",
1504        meta_docs_path: "explanations/dead-code#stale-suppressions",
1505        meta_name: "Stale Suppressions",
1506        summary_label: "Stale suppressions",
1507        docs_anchor: "stale-suppressions",
1508        result_key: "stale_suppressions",
1509        counts_in_total: true,
1510    },
1511    IssueResultMeta {
1512        code: "unused-catalog-entry",
1513        sarif_description: "Catalog entry not referenced by any workspace package",
1514        meta_description: "A package manager catalog entry is not referenced by any workspace package.json.",
1515        meta_docs_path: "explanations/dead-code#unused-catalog-entries",
1516        meta_name: "Unused catalog entry",
1517        summary_label: "Unused catalog entries",
1518        docs_anchor: "unused-catalog-entries",
1519        result_key: "unused_catalog_entries",
1520        counts_in_total: true,
1521    },
1522    IssueResultMeta {
1523        code: "empty-catalog-group",
1524        sarif_description: "Named catalog group has no entries",
1525        meta_description: "A named package manager catalog group has no package entries.",
1526        meta_docs_path: "explanations/dead-code#empty-catalog-groups",
1527        meta_name: "Empty catalog group",
1528        summary_label: "Empty catalog groups",
1529        docs_anchor: "empty-catalog-groups",
1530        result_key: "empty_catalog_groups",
1531        counts_in_total: true,
1532    },
1533    IssueResultMeta {
1534        code: "unresolved-catalog-reference",
1535        sarif_description: "package.json references a catalog that does not declare the package",
1536        meta_description: "A workspace package.json uses a catalog protocol reference that no catalog declares.",
1537        meta_docs_path: "explanations/dead-code#unresolved-catalog-references",
1538        meta_name: "Unresolved catalog reference",
1539        summary_label: "Unresolved catalog references",
1540        docs_anchor: "unresolved-catalog-references",
1541        result_key: "unresolved_catalog_references",
1542        counts_in_total: true,
1543    },
1544    IssueResultMeta {
1545        code: "unused-dependency-override",
1546        sarif_description: "Package-manager override target is not declared or resolved",
1547        meta_description: "A dependency override targets a package not declared by any workspace package and not present in the active readable lockfile.",
1548        meta_docs_path: "explanations/dead-code#unused-dependency-overrides",
1549        meta_name: "Unused dependency override",
1550        summary_label: "Unused dependency overrides",
1551        docs_anchor: "unused-dependency-overrides",
1552        result_key: "unused_dependency_overrides",
1553        counts_in_total: true,
1554    },
1555    IssueResultMeta {
1556        code: "misconfigured-dependency-override",
1557        sarif_description: "Package-manager override has an unparsable key or value",
1558        meta_description: "A dependency override key or value does not parse according to its package manager's grammar.",
1559        meta_docs_path: "explanations/dead-code#misconfigured-dependency-overrides",
1560        meta_name: "Misconfigured dependency override",
1561        summary_label: "Misconfigured dependency overrides",
1562        docs_anchor: "misconfigured-dependency-overrides",
1563        result_key: "misconfigured_dependency_overrides",
1564        counts_in_total: true,
1565    },
1566    IssueResultMeta {
1567        code: "prop-drilling",
1568        sarif_description: "A React/Preact prop is forwarded unchanged through 3+ pass-through components to a distant consumer",
1569        meta_description: "A React or Preact prop is forwarded unchanged through multiple pass-through components to a distant consumer.",
1570        meta_docs_path: "explanations/dead-code#prop-drilling",
1571        meta_name: "Prop drilling",
1572        summary_label: "Prop drilling",
1573        docs_anchor: "prop-drilling",
1574        result_key: "prop_drilling_chains",
1575        counts_in_total: false,
1576    },
1577    IssueResultMeta {
1578        code: "thin-wrapper",
1579        sarif_description: "A React/Preact component whose whole body is a single spread-forwarded child render (a candidate for inlining)",
1580        meta_description: "A React or Preact component is structural indirection around a single spread-forwarded child render.",
1581        meta_docs_path: "explanations/dead-code#thin-wrapper",
1582        meta_name: "Thin wrapper",
1583        summary_label: "Thin wrappers",
1584        docs_anchor: "thin-wrapper",
1585        result_key: "thin_wrappers",
1586        counts_in_total: false,
1587    },
1588    IssueResultMeta {
1589        code: "duplicate-prop-shape",
1590        sarif_description: "Three or more React/Preact components across two or more files declare an identical prop-name set (a missing shared Props type)",
1591        meta_description: "Multiple React or Preact components declare an identical significant prop-name set.",
1592        meta_docs_path: "explanations/dead-code#duplicate-prop-shape",
1593        meta_name: "Duplicate prop shape",
1594        summary_label: "Duplicate prop shapes",
1595        docs_anchor: "duplicate-prop-shape",
1596        result_key: "duplicate_prop_shapes",
1597        counts_in_total: false,
1598    },
1599];
1600
1601/// Canonical names and aliases accepted by `IssueKind::parse`.
1602pub static KNOWN_ISSUE_KIND_NAMES: LazyLock<Vec<&'static str>> =
1603    LazyLock::new(known_issue_kind_names_from_meta);
1604
1605/// CLI filter flags on `fallow dead-code` that scope output to one issue family.
1606pub static DEAD_CODE_FILTER_FLAGS: LazyLock<Vec<&'static str>> =
1607    LazyLock::new(dead_code_filter_flags_from_meta);
1608
1609/// MCP issue selector names mapped to dead-code CLI flags.
1610pub static MCP_ISSUE_TYPE_FLAGS: LazyLock<Vec<(&'static str, &'static str)>> =
1611    LazyLock::new(mcp_issue_type_flags_from_meta);
1612
1613/// Result issue codes emitted by the dead-code CodeClimate formatter.
1614pub static CODECLIMATE_RESULT_CODES: LazyLock<Vec<&'static str>> =
1615    LazyLock::new(codeclimate_result_codes_from_meta);
1616
1617fn known_issue_kind_names_from_meta() -> Vec<&'static str> {
1618    let mut names = Vec::new();
1619    for meta in ISSUE_KIND_META.iter().filter(|meta| meta.kind.is_some()) {
1620        push_unique(&mut names, meta.code);
1621        for alias in meta.aliases {
1622            push_unique(&mut names, *alias);
1623        }
1624    }
1625    names
1626}
1627
1628fn dead_code_filter_flags_from_meta() -> Vec<&'static str> {
1629    let mut flags = Vec::new();
1630    for meta in ISSUE_KIND_META {
1631        if let Some(flag) = meta.filter_flag {
1632            push_unique(&mut flags, flag);
1633        }
1634    }
1635    flags
1636}
1637
1638fn mcp_issue_type_flags_from_meta() -> Vec<(&'static str, &'static str)> {
1639    ISSUE_KIND_META
1640        .iter()
1641        .filter_map(|meta| meta.mcp_pair())
1642        .collect()
1643}
1644
1645fn codeclimate_result_codes_from_meta() -> Vec<&'static str> {
1646    ISSUE_RESULT_META
1647        .iter()
1648        .filter(|meta| meta.counts_in_total)
1649        .map(|meta| meta.code)
1650        .collect()
1651}
1652
1653fn push_unique<T: Copy + PartialEq>(items: &mut Vec<T>, item: T) {
1654    if !items.contains(&item) {
1655        items.push(item);
1656    }
1657}
1658
1659/// Lookup metadata by canonical code.
1660#[must_use]
1661pub fn issue_meta_by_code(code: &str) -> Option<&'static IssueKindMeta> {
1662    ISSUE_KIND_META.iter().find(|meta| meta.code == code)
1663}
1664
1665/// Lookup metadata by canonical code or alias.
1666#[must_use]
1667pub fn issue_meta_for_token(token: &str) -> Option<&'static IssueKindMeta> {
1668    ISSUE_KIND_META
1669        .iter()
1670        .find(|meta| meta.code == token || meta.aliases.contains(&token))
1671}
1672
1673/// Lookup metadata by any shared contract token: canonical code, alias,
1674/// config key, MCP selector, suppression token, or CLI filter flag.
1675#[must_use]
1676pub fn issue_meta_for_contract_token(token: &str) -> Option<&'static IssueKindMeta> {
1677    let normalized = token.trim().trim_start_matches("--").replace('_', "-");
1678    ISSUE_KIND_META
1679        .iter()
1680        .find(|meta| issue_meta_matches_contract_token(meta, &normalized))
1681}
1682
1683/// Whether a metadata row owns the provided shared contract token.
1684#[must_use]
1685pub fn issue_meta_matches_contract_token(meta: &IssueKindMeta, token: &str) -> bool {
1686    let normalized = token.trim().trim_start_matches("--").replace('_', "-");
1687    meta.code == normalized
1688        || meta.aliases.contains(&normalized.as_str())
1689        || meta.config_key == Some(normalized.as_str())
1690        || meta.mcp_issue_type == Some(normalized.as_str())
1691        || meta.suppress_token == Some(normalized.as_str())
1692        || meta.filter_flag.map(|flag| flag.trim_start_matches("--")) == Some(normalized.as_str())
1693}
1694
1695/// Lookup metadata by backing issue kind.
1696#[must_use]
1697pub fn issue_meta_by_kind(kind: IssueKind) -> Option<&'static IssueKindMeta> {
1698    ISSUE_KIND_META.iter().find(|meta| meta.kind == Some(kind))
1699}
1700
1701/// Lookup serialized result metadata by canonical issue code.
1702#[must_use]
1703pub fn issue_result_meta_by_code(code: &str) -> Option<&'static IssueResultMeta> {
1704    ISSUE_RESULT_META.iter().find(|meta| meta.code == code)
1705}
1706
1707/// SARIF rule ids used by CI formatters for a canonical issue code.
1708#[must_use]
1709pub fn issue_sarif_rule_ids(code: &str) -> Vec<String> {
1710    let mut ids = vec![format!("fallow/{code}")];
1711    if code == "stale-suppression" {
1712        ids.push("fallow/missing-suppression-reason".to_string());
1713    }
1714    ids
1715}
1716
1717/// Short SARIF rule description for a rule id emitted by dead-code output.
1718#[must_use]
1719pub fn issue_sarif_rule_description(rule_id: &str) -> Option<&'static str> {
1720    let code = rule_id.strip_prefix("fallow/")?;
1721    if code == "missing-suppression-reason" {
1722        return Some("Suppression comment omits a required reason");
1723    }
1724    issue_result_meta_by_code(code).map(|meta| meta.sarif_description)
1725}
1726
1727/// CodeClimate check names used by CI formatters for a canonical issue code.
1728#[must_use]
1729pub fn issue_codeclimate_check_names(code: &str) -> Vec<String> {
1730    if !CODECLIMATE_RESULT_CODES.contains(&code) {
1731        return Vec::new();
1732    }
1733    issue_sarif_rule_ids(code)
1734}
1735
1736/// Documentation anchor under `/explanations/dead-code` for a canonical issue
1737/// code.
1738#[must_use]
1739pub fn issue_docs_anchor(code: &str) -> Option<&'static str> {
1740    issue_result_meta_by_code(code).map(|meta| meta.docs_anchor)
1741}
1742
1743/// Published TypeScript alias policy for backwards-compatible bare names.
1744#[must_use]
1745pub fn issue_ts_alias(code: &str) -> Option<TsAliasMeta> {
1746    ISSUE_TS_ALIAS_META
1747        .iter()
1748        .find(|meta| meta.code == code)
1749        .map(|meta| meta.alias)
1750}
1751
1752/// Rows exposed by the LSP issue-type capability.
1753pub fn diagnostic_issue_metas() -> impl Iterator<Item = &'static IssueKindMeta> {
1754    ISSUE_KIND_META.iter().filter(|meta| meta.lsp)
1755}
1756
1757/// Rows that map to a serialized `AnalysisResults` array.
1758pub fn result_issue_metas() -> impl Iterator<Item = &'static IssueResultMeta> {
1759    ISSUE_RESULT_META.iter()
1760}
1761
1762/// Rows whose serialized `AnalysisResults` array contributes to `total_issues`.
1763pub fn counted_result_issue_metas() -> impl Iterator<Item = &'static IssueResultMeta> {
1764    result_issue_metas().filter(|meta| meta.counts_in_total)
1765}
1766
1767/// Canonical issue codes `fallow fix` can rewrite automatically. Shared by the
1768/// `fallow schema` `issue_types` rows (`fixable`) and the `fallow://issue-types`
1769/// MCP resource so the two surfaces never disagree on fixability.
1770pub const FIXABLE_ISSUE_CODES: &[&str] = &[
1771    "unused-export",
1772    "unused-enum-member",
1773    "unused-dependency",
1774    "unused-dev-dependency",
1775    "unused-optional-dependency",
1776    "unused-catalog-entry",
1777];
1778
1779/// Whether `fallow fix` can rewrite findings of a canonical issue code.
1780#[must_use]
1781pub fn issue_is_fixable(code: &str) -> bool {
1782    FIXABLE_ISSUE_CODES.contains(&code)
1783}
1784
1785#[cfg(test)]
1786mod tests {
1787    use std::collections::BTreeSet;
1788
1789    use crate::results::TOTAL_ISSUE_RESULT_KEYS;
1790
1791    use super::*;
1792
1793    #[test]
1794    fn fixable_codes_are_canonical_issue_codes() {
1795        for code in FIXABLE_ISSUE_CODES {
1796            assert!(
1797                issue_meta_by_code(code).is_some(),
1798                "fixable code {code} is not a canonical ISSUE_KIND_META code"
1799            );
1800        }
1801        assert!(issue_is_fixable("unused-export"));
1802        assert!(!issue_is_fixable("unused-file"));
1803    }
1804
1805    #[test]
1806    fn known_names_round_trip_through_metadata() {
1807        for name in KNOWN_ISSUE_KIND_NAMES.iter() {
1808            let meta = issue_meta_for_token(name)
1809                .unwrap_or_else(|| panic!("known issue name {name} missing metadata row"));
1810            assert!(
1811                meta.kind.is_some(),
1812                "known issue name {name} maps to non-IssueKind metadata"
1813            );
1814        }
1815    }
1816
1817    #[test]
1818    fn issue_kind_variants_have_metadata() {
1819        for &kind in IssueKind::ALL {
1820            assert!(
1821                issue_meta_by_kind(kind).is_some(),
1822                "IssueKind {kind:?} has no metadata row"
1823            );
1824        }
1825    }
1826
1827    #[test]
1828    fn dead_code_filter_flags_match_metadata() {
1829        let from_constants: BTreeSet<&str> = DEAD_CODE_FILTER_FLAGS.iter().copied().collect();
1830        let from_meta: BTreeSet<&str> = ISSUE_KIND_META
1831            .iter()
1832            .filter_map(|meta| meta.filter_flag)
1833            .collect();
1834        assert_eq!(from_constants, from_meta);
1835    }
1836
1837    #[test]
1838    fn mcp_issue_type_flags_match_metadata() {
1839        let from_constants: BTreeSet<(&str, &str)> = MCP_ISSUE_TYPE_FLAGS.iter().copied().collect();
1840        let from_meta: BTreeSet<(&str, &str)> = ISSUE_KIND_META
1841            .iter()
1842            .filter_map(|meta| meta.mcp_pair())
1843            .collect();
1844        assert_eq!(from_constants, from_meta);
1845    }
1846
1847    #[test]
1848    fn lsp_exposes_only_actual_diagnostic_codes() {
1849        let codes: BTreeSet<&str> = diagnostic_issue_metas().map(|meta| meta.code).collect();
1850        assert!(codes.contains("boundary-violation"));
1851        assert!(!codes.contains("boundary-coverage"));
1852        assert!(!codes.contains("boundary-call-violation"));
1853    }
1854
1855    #[test]
1856    fn issue_codes_are_unique() {
1857        let mut seen = BTreeSet::new();
1858        for meta in ISSUE_KIND_META {
1859            assert!(seen.insert(meta.code), "duplicate issue code {}", meta.code);
1860        }
1861    }
1862
1863    #[test]
1864    fn contract_tokens_resolve_through_metadata() {
1865        for meta in ISSUE_KIND_META {
1866            assert_eq!(
1867                issue_meta_for_contract_token(meta.code).map(|resolved| resolved.code),
1868                Some(meta.code),
1869                "canonical issue code {} should resolve through contract token lookup",
1870                meta.code
1871            );
1872            for token in meta.aliases {
1873                assert!(
1874                    issue_meta_matches_contract_token(meta, token),
1875                    "alias {token} should match {}",
1876                    meta.code
1877                );
1878            }
1879            for token in [
1880                meta.config_key,
1881                meta.mcp_issue_type,
1882                meta.suppress_token,
1883                meta.filter_flag,
1884            ]
1885            .into_iter()
1886            .flatten()
1887            {
1888                assert!(
1889                    issue_meta_for_contract_token(token).is_some(),
1890                    "contract token {token} should resolve through metadata"
1891                );
1892                assert!(
1893                    issue_meta_matches_contract_token(meta, token),
1894                    "contract token {token} should match {}",
1895                    meta.code
1896                );
1897            }
1898        }
1899    }
1900
1901    #[test]
1902    fn sarif_rule_descriptions_cover_result_metadata() {
1903        for meta in result_issue_metas() {
1904            for rule_id in issue_sarif_rule_ids(meta.code) {
1905                assert!(
1906                    issue_sarif_rule_description(&rule_id).is_some(),
1907                    "SARIF rule {rule_id} for {} needs a central description",
1908                    meta.code
1909                );
1910            }
1911        }
1912    }
1913
1914    #[test]
1915    fn result_meta_codes_have_issue_metadata() {
1916        for meta in ISSUE_RESULT_META {
1917            assert!(
1918                issue_meta_by_code(meta.code).is_some(),
1919                "result metadata code {} has no issue metadata row",
1920                meta.code
1921            );
1922        }
1923    }
1924
1925    #[test]
1926    fn result_meta_codes_have_docs_anchors() {
1927        for meta in ISSUE_RESULT_META {
1928            let issue = issue_meta_by_code(meta.code)
1929                .unwrap_or_else(|| panic!("result metadata code {} has no issue row", meta.code));
1930            assert_eq!(
1931                issue.docs_anchor(),
1932                Some(meta.docs_anchor),
1933                "result metadata code {} has mismatched docs anchor",
1934                meta.code
1935            );
1936        }
1937    }
1938
1939    #[test]
1940    fn result_meta_codes_have_summary_labels() {
1941        for meta in ISSUE_RESULT_META {
1942            assert!(
1943                !meta.summary_label.is_empty(),
1944                "result metadata code {} has no summary label",
1945                meta.code
1946            );
1947        }
1948    }
1949
1950    #[test]
1951    fn result_meta_codes_have_meta_names() {
1952        for meta in ISSUE_RESULT_META {
1953            assert!(
1954                !meta.meta_name.is_empty(),
1955                "result metadata code {} has no meta name",
1956                meta.code
1957            );
1958        }
1959    }
1960
1961    #[test]
1962    fn result_meta_codes_have_meta_docs_paths() {
1963        for meta in ISSUE_RESULT_META {
1964            assert!(
1965                meta.meta_docs_path.starts_with("explanations/dead-code#"),
1966                "result metadata code {} has invalid meta docs path",
1967                meta.code
1968            );
1969        }
1970    }
1971
1972    #[test]
1973    fn result_meta_codes_have_meta_descriptions() {
1974        for meta in ISSUE_RESULT_META {
1975            assert!(
1976                !meta.meta_description.is_empty(),
1977                "result metadata code {} has no meta description",
1978                meta.code
1979            );
1980        }
1981    }
1982
1983    #[test]
1984    fn ci_format_ids_are_prefixed_and_known() {
1985        let result_codes: BTreeSet<&str> = result_issue_metas().map(|meta| meta.code).collect();
1986        let codeclimate_codes: BTreeSet<&str> = CODECLIMATE_RESULT_CODES.iter().copied().collect();
1987        assert!(codeclimate_codes.is_subset(&result_codes));
1988
1989        for meta in result_issue_metas() {
1990            let issue = issue_meta_by_code(meta.code)
1991                .unwrap_or_else(|| panic!("result metadata code {} has no issue row", meta.code));
1992            assert!(issue.has_result_contract());
1993            let sarif_ids = issue.sarif_rule_ids();
1994            assert!(sarif_ids.contains(&format!("fallow/{}", meta.code)));
1995            for rule_id in sarif_ids {
1996                assert!(
1997                    rule_id.starts_with("fallow/"),
1998                    "result metadata code {} has unprefixed SARIF rule id {rule_id}",
1999                    meta.code
2000                );
2001            }
2002            for check_name in issue.codeclimate_check_names() {
2003                assert!(
2004                    check_name.starts_with("fallow/"),
2005                    "result metadata code {} has unprefixed CodeClimate check name {check_name}",
2006                    meta.code
2007                );
2008            }
2009        }
2010    }
2011
2012    #[test]
2013    fn ts_alias_policy_is_explicit() {
2014        let aliases: BTreeSet<(&str, &str)> = ISSUE_TS_ALIAS_META
2015            .iter()
2016            .map(|meta| (meta.alias.name, meta.alias.parent))
2017            .collect();
2018
2019        assert_eq!(
2020            BTreeSet::from([
2021                ("BoundaryViolation", "BoundaryViolationFinding"),
2022                ("CircularDependency", "CircularDependencyFinding"),
2023                ("DeprecatedExportInUse", "DeprecatedExportInUseFinding"),
2024                (
2025                    "DevDependencyInProduction",
2026                    "DevDependencyInProductionFinding",
2027                ),
2028                ("DuplicateExport", "DuplicateExportFinding"),
2029                ("EmptyCatalogGroup", "EmptyCatalogGroupFinding"),
2030                (
2031                    "MisconfiguredDependencyOverride",
2032                    "MisconfiguredDependencyOverrideFinding",
2033                ),
2034                ("PrivateTypeLeak", "PrivateTypeLeakFinding"),
2035                ("PackageCycle", "PackageCycleFinding"),
2036                ("ReExportCycle", "ReExportCycleFinding"),
2037                ("TestOnlyDependency", "TestOnlyDependencyFinding"),
2038                ("TypeOnlyDependency", "TypeOnlyDependencyFinding"),
2039                (
2040                    "UnresolvedCatalogReference",
2041                    "UnresolvedCatalogReferenceFinding",
2042                ),
2043                ("UnresolvedImport", "UnresolvedImportFinding"),
2044                ("UnlistedDependency", "UnlistedDependencyFinding"),
2045                ("UnusedCatalogEntry", "UnusedCatalogEntryFinding"),
2046                ("UnusedDependency", "UnusedDependencyFinding"),
2047                ("UnusedDependency", "UnusedDevDependencyFinding"),
2048                ("UnusedDependency", "UnusedOptionalDependencyFinding"),
2049                (
2050                    "UnusedDependencyOverride",
2051                    "UnusedDependencyOverrideFinding",
2052                ),
2053                ("UnusedExport", "UnusedExportFinding"),
2054                ("UnusedFile", "UnusedFileFinding"),
2055                ("UnusedMember", "UnusedClassMemberFinding"),
2056                ("UnusedMember", "UnusedEnumMemberFinding"),
2057                ("UnusedMember", "UnusedStoreMemberFinding"),
2058            ]),
2059            aliases
2060        );
2061    }
2062
2063    #[test]
2064    fn ts_alias_registry_rows_match_result_metadata() {
2065        let result_codes: BTreeSet<&str> = result_issue_metas().map(|meta| meta.code).collect();
2066        let mut seen_codes = BTreeSet::new();
2067        for meta in ISSUE_TS_ALIAS_META {
2068            assert!(
2069                seen_codes.insert(meta.code),
2070                "duplicate TypeScript alias row for {}",
2071                meta.code
2072            );
2073            assert!(
2074                result_codes.contains(meta.code),
2075                "TypeScript alias row {} has no result metadata",
2076                meta.code
2077            );
2078            assert!(
2079                meta.alias.parent.ends_with("Finding"),
2080                "TypeScript alias row {} must point at a generated Finding wrapper",
2081                meta.code
2082            );
2083            assert_eq!(
2084                Some(meta.alias),
2085                issue_meta_by_code(meta.code).and_then(|issue| issue.ts_alias()),
2086                "IssueKindMeta helper must resolve TypeScript alias row {} through the registry",
2087                meta.code
2088            );
2089        }
2090    }
2091
2092    #[test]
2093    fn result_keys_are_unique() {
2094        let mut seen = BTreeSet::new();
2095        for meta in ISSUE_RESULT_META {
2096            assert!(
2097                seen.insert(meta.result_key),
2098                "duplicate result key {}",
2099                meta.result_key
2100            );
2101        }
2102    }
2103
2104    #[test]
2105    fn counted_result_keys_match_total_issue_fields() {
2106        let from_total: BTreeSet<&str> = TOTAL_ISSUE_RESULT_KEYS.iter().copied().collect();
2107        let from_meta: BTreeSet<&str> = counted_result_issue_metas()
2108            .map(|meta| meta.result_key)
2109            .collect();
2110        assert_eq!(from_total, from_meta);
2111    }
2112
2113    #[test]
2114    fn advisory_result_keys_are_explicitly_excluded_from_total() {
2115        let expected = BTreeSet::from([
2116            "duplicate_prop_shapes",
2117            "prop_drilling_chains",
2118            "thin_wrappers",
2119        ]);
2120        let from_meta: BTreeSet<&str> = result_issue_metas()
2121            .filter(|meta| !meta.counts_in_total)
2122            .map(|meta| meta.result_key)
2123            .collect();
2124        assert_eq!(expected, from_meta);
2125    }
2126}