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::AbsentComponentProp),
487        code: "absent-component-prop",
488        aliases: &["absent-component-props"],
489        label: "Absent Optional Component Props",
490        config_key: Some("absent-component-props"),
491        filter_flag: Some("--absent-component-props"),
492        mcp_issue_type: Some("absent-component-props"),
493        suppress_token: Some("absent-component-prop"),
494        suppress_file_level: false,
495        lsp: true,
496        docs_category: "framework",
497    },
498    IssueKindMeta {
499        kind: Some(IssueKind::UnusedComponentEmit),
500        code: "unused-component-emit",
501        aliases: &["unused-component-emits"],
502        label: "Unused Component Emits",
503        config_key: Some("unused-component-emits"),
504        filter_flag: Some("--unused-component-emits"),
505        mcp_issue_type: Some("unused-component-emits"),
506        suppress_token: Some("unused-component-emit"),
507        suppress_file_level: false,
508        lsp: true,
509        docs_category: "framework",
510    },
511    IssueKindMeta {
512        kind: Some(IssueKind::UnusedComponentInput),
513        code: "unused-component-input",
514        aliases: &["unused-component-inputs"],
515        label: "Unused Component Inputs",
516        config_key: Some("unused-component-inputs"),
517        filter_flag: Some("--unused-component-inputs"),
518        mcp_issue_type: Some("unused-component-inputs"),
519        suppress_token: Some("unused-component-input"),
520        suppress_file_level: false,
521        lsp: true,
522        docs_category: "framework",
523    },
524    IssueKindMeta {
525        kind: Some(IssueKind::UnusedComponentOutput),
526        code: "unused-component-output",
527        aliases: &["unused-component-outputs"],
528        label: "Unused Component Outputs",
529        config_key: Some("unused-component-outputs"),
530        filter_flag: Some("--unused-component-outputs"),
531        mcp_issue_type: Some("unused-component-outputs"),
532        suppress_token: Some("unused-component-output"),
533        suppress_file_level: false,
534        lsp: true,
535        docs_category: "framework",
536    },
537    IssueKindMeta {
538        kind: Some(IssueKind::UnusedSvelteEvent),
539        code: "unused-svelte-event",
540        aliases: &["unused-svelte-events"],
541        label: "Unused Svelte Events",
542        config_key: Some("unused-svelte-events"),
543        filter_flag: Some("--unused-svelte-events"),
544        mcp_issue_type: Some("unused-svelte-events"),
545        suppress_token: Some("unused-svelte-event"),
546        suppress_file_level: false,
547        lsp: true,
548        docs_category: "framework",
549    },
550    IssueKindMeta {
551        kind: Some(IssueKind::UnusedServerAction),
552        code: "unused-server-action",
553        aliases: &["unused-server-actions"],
554        label: "Unused Server Actions",
555        config_key: Some("unused-server-actions"),
556        filter_flag: Some("--unused-server-actions"),
557        mcp_issue_type: Some("unused-server-actions"),
558        suppress_token: Some("unused-server-action"),
559        suppress_file_level: false,
560        lsp: true,
561        docs_category: "framework",
562    },
563    IssueKindMeta {
564        kind: Some(IssueKind::UnusedLoadDataKey),
565        code: "unused-load-data-key",
566        aliases: &["unused-load-data-keys"],
567        label: "Unused Load Data Keys",
568        config_key: Some("unused-load-data-keys"),
569        filter_flag: Some("--unused-load-data-keys"),
570        mcp_issue_type: Some("unused-load-data-keys"),
571        suppress_token: Some("unused-load-data-key"),
572        suppress_file_level: false,
573        lsp: true,
574        docs_category: "framework",
575    },
576    IssueKindMeta {
577        kind: Some(IssueKind::UnusedEffectLayer),
578        code: "unused-effect-layer",
579        aliases: &["unused-effect-layers"],
580        label: "Unused Effect Layers",
581        config_key: Some("unused-effect-layers"),
582        filter_flag: Some("--unused-effect-layers"),
583        mcp_issue_type: Some("unused-effect-layers"),
584        suppress_token: Some("unused-effect-layer"),
585        suppress_file_level: false,
586        lsp: true,
587        docs_category: "framework",
588    },
589    IssueKindMeta {
590        kind: Some(IssueKind::UnusedTaggedError),
591        code: "unused-tagged-error",
592        aliases: &["unused-tagged-errors"],
593        label: "Unused Tagged Errors",
594        config_key: Some("unused-tagged-errors"),
595        filter_flag: Some("--unused-tagged-errors"),
596        mcp_issue_type: Some("unused-tagged-errors"),
597        suppress_token: Some("unused-tagged-error"),
598        suppress_file_level: false,
599        lsp: true,
600        docs_category: "framework",
601    },
602    IssueKindMeta {
603        kind: Some(IssueKind::UnproducedCatchTag),
604        code: "unproduced-catch-tag",
605        aliases: &["unproduced-catch-tags"],
606        label: "Unproduced Catch Tags",
607        config_key: Some("unproduced-catch-tags"),
608        filter_flag: Some("--unproduced-catch-tags"),
609        mcp_issue_type: Some("unproduced-catch-tags"),
610        suppress_token: Some("unproduced-catch-tag"),
611        suppress_file_level: false,
612        lsp: true,
613        docs_category: "framework",
614    },
615    IssueKindMeta {
616        kind: Some(IssueKind::RouteCollision),
617        code: "route-collision",
618        aliases: &["route-collisions"],
619        label: "Route Collisions",
620        config_key: Some("route-collision"),
621        filter_flag: None,
622        mcp_issue_type: None,
623        suppress_token: Some("route-collision"),
624        suppress_file_level: true,
625        lsp: true,
626        docs_category: "framework",
627    },
628    IssueKindMeta {
629        kind: Some(IssueKind::DynamicSegmentNameConflict),
630        code: "dynamic-segment-name-conflict",
631        aliases: &["dynamic-segment-name-conflicts"],
632        label: "Dynamic Segment Conflicts",
633        config_key: Some("dynamic-segment-name-conflict"),
634        filter_flag: None,
635        mcp_issue_type: None,
636        suppress_token: Some("dynamic-segment-name-conflict"),
637        suppress_file_level: true,
638        lsp: true,
639        docs_category: "framework",
640    },
641    IssueKindMeta {
642        kind: Some(IssueKind::StaleSuppression),
643        code: "stale-suppression",
644        aliases: &[],
645        label: "Stale Suppressions",
646        config_key: Some("stale-suppressions"),
647        filter_flag: Some("--stale-suppressions"),
648        mcp_issue_type: Some("stale-suppressions"),
649        suppress_token: None,
650        suppress_file_level: false,
651        lsp: true,
652        docs_category: "source",
653    },
654    IssueKindMeta {
655        kind: Some(IssueKind::StaleSuppression),
656        code: "missing-suppression-reason",
657        aliases: &["missing-suppression-reasons"],
658        label: "Missing Suppression Reasons",
659        config_key: Some("require-suppression-reason"),
660        filter_flag: Some("--stale-suppressions"),
661        mcp_issue_type: None,
662        suppress_token: None,
663        suppress_file_level: false,
664        lsp: false,
665        docs_category: "source",
666    },
667    IssueKindMeta {
668        kind: Some(IssueKind::PnpmCatalogEntry),
669        code: "unused-catalog-entry",
670        aliases: &["catalog", "unused-catalog-entries"],
671        label: "Unused Catalog Entries",
672        config_key: Some("unused-catalog-entries"),
673        filter_flag: Some("--unused-catalog-entries"),
674        mcp_issue_type: Some("unused-catalog-entries"),
675        suppress_token: None,
676        suppress_file_level: false,
677        lsp: true,
678        docs_category: "dependency",
679    },
680    IssueKindMeta {
681        kind: Some(IssueKind::EmptyCatalogGroup),
682        code: "empty-catalog-group",
683        aliases: &["empty-catalog", "empty-catalog-groups"],
684        label: "Empty Catalog Groups",
685        config_key: Some("empty-catalog-groups"),
686        filter_flag: Some("--empty-catalog-groups"),
687        mcp_issue_type: Some("empty-catalog-groups"),
688        suppress_token: None,
689        suppress_file_level: false,
690        lsp: true,
691        docs_category: "dependency",
692    },
693    IssueKindMeta {
694        kind: Some(IssueKind::UnresolvedCatalogReference),
695        code: "unresolved-catalog-reference",
696        aliases: &["unresolved-catalog", "unresolved-catalog-references"],
697        label: "Unresolved Catalog References",
698        config_key: Some("unresolved-catalog-references"),
699        filter_flag: Some("--unresolved-catalog-references"),
700        mcp_issue_type: Some("unresolved-catalog-references"),
701        suppress_token: None,
702        suppress_file_level: false,
703        lsp: true,
704        docs_category: "dependency",
705    },
706    IssueKindMeta {
707        kind: Some(IssueKind::UnusedDependencyOverride),
708        code: "unused-dependency-override",
709        aliases: &[
710            "unused-dependency-overrides",
711            "unused-override",
712            "unused-overrides",
713        ],
714        label: "Unused Dependency Overrides",
715        config_key: Some("unused-dependency-overrides"),
716        filter_flag: Some("--unused-dependency-overrides"),
717        mcp_issue_type: Some("unused-dependency-overrides"),
718        suppress_token: None,
719        suppress_file_level: false,
720        lsp: true,
721        docs_category: "dependency",
722    },
723    IssueKindMeta {
724        kind: Some(IssueKind::MisconfiguredDependencyOverride),
725        code: "misconfigured-dependency-override",
726        aliases: &[
727            "misconfigured-dependency-overrides",
728            "misconfigured-override",
729            "misconfigured-overrides",
730        ],
731        label: "Misconfigured Dependency Overrides",
732        config_key: Some("misconfigured-dependency-overrides"),
733        filter_flag: Some("--misconfigured-dependency-overrides"),
734        mcp_issue_type: Some("misconfigured-dependency-overrides"),
735        suppress_token: None,
736        suppress_file_level: false,
737        lsp: true,
738        docs_category: "dependency",
739    },
740    IssueKindMeta {
741        kind: Some(IssueKind::SecuritySink),
742        code: "security-sink",
743        aliases: &[],
744        label: "Security Sink Candidates",
745        config_key: Some("security-sink"),
746        filter_flag: None,
747        mcp_issue_type: None,
748        suppress_token: Some("security-sink"),
749        suppress_file_level: false,
750        lsp: true,
751        docs_category: "security",
752    },
753    IssueKindMeta {
754        kind: Some(IssueKind::SecurityClientServerLeak),
755        code: "security-client-server-leak",
756        aliases: &[],
757        label: "Security Client-Server Leaks",
758        config_key: Some("security-client-server-leak"),
759        filter_flag: None,
760        mcp_issue_type: None,
761        suppress_token: Some("security-client-server-leak"),
762        suppress_file_level: true,
763        lsp: true,
764        docs_category: "security",
765    },
766    IssueKindMeta {
767        kind: Some(IssueKind::CoverageGaps),
768        code: "coverage-gaps",
769        aliases: &[],
770        label: "Coverage Gaps",
771        config_key: Some("coverage-gaps"),
772        filter_flag: None,
773        mcp_issue_type: None,
774        suppress_token: Some("coverage-gaps"),
775        suppress_file_level: true,
776        lsp: false,
777        docs_category: "health",
778    },
779    IssueKindMeta {
780        kind: Some(IssueKind::FeatureFlag),
781        code: "feature-flag",
782        aliases: &[],
783        label: "Feature Flags",
784        config_key: Some("feature-flags"),
785        filter_flag: None,
786        mcp_issue_type: None,
787        suppress_token: Some("feature-flag"),
788        suppress_file_level: false,
789        lsp: false,
790        docs_category: "flags",
791    },
792    IssueKindMeta {
793        kind: Some(IssueKind::Complexity),
794        code: "complexity",
795        aliases: &[],
796        label: "Complexity",
797        config_key: None,
798        filter_flag: None,
799        mcp_issue_type: None,
800        suppress_token: Some("complexity"),
801        suppress_file_level: false,
802        lsp: false,
803        docs_category: "health",
804    },
805    IssueKindMeta {
806        kind: Some(IssueKind::PropDrilling),
807        code: "prop-drilling",
808        aliases: &[],
809        label: "Prop Drilling",
810        config_key: Some("prop-drilling"),
811        filter_flag: None,
812        mcp_issue_type: None,
813        suppress_token: Some("prop-drilling"),
814        suppress_file_level: false,
815        lsp: true,
816        docs_category: "source",
817    },
818    IssueKindMeta {
819        kind: Some(IssueKind::ThinWrapper),
820        code: "thin-wrapper",
821        aliases: &["thin-wrappers"],
822        label: "Thin Wrappers",
823        config_key: Some("thin-wrapper"),
824        filter_flag: None,
825        mcp_issue_type: None,
826        suppress_token: Some("thin-wrapper"),
827        suppress_file_level: false,
828        lsp: true,
829        docs_category: "source",
830    },
831    IssueKindMeta {
832        kind: Some(IssueKind::DuplicatePropShape),
833        code: "duplicate-prop-shape",
834        aliases: &["duplicate-prop-shapes"],
835        label: "Duplicate Prop Shapes",
836        config_key: Some("duplicate-prop-shape"),
837        filter_flag: None,
838        mcp_issue_type: None,
839        suppress_token: Some("duplicate-prop-shape"),
840        suppress_file_level: false,
841        lsp: true,
842        docs_category: "source",
843    },
844    IssueKindMeta {
845        kind: Some(IssueKind::CssTokenDrift),
846        code: "css-token-drift",
847        aliases: &[],
848        label: "CSS Token Drift",
849        config_key: Some("css-token-drift"),
850        filter_flag: None,
851        mcp_issue_type: None,
852        suppress_token: Some("css-token-drift"),
853        suppress_file_level: false,
854        lsp: false,
855        docs_category: "health",
856    },
857    IssueKindMeta {
858        kind: Some(IssueKind::CssDuplicateBlock),
859        code: "css-duplicate-block",
860        aliases: &[],
861        label: "CSS Duplicate Block",
862        config_key: Some("css-duplicate-block"),
863        filter_flag: None,
864        mcp_issue_type: None,
865        suppress_token: Some("css-duplicate-block"),
866        suppress_file_level: false,
867        lsp: false,
868        docs_category: "health",
869    },
870    IssueKindMeta {
871        kind: Some(IssueKind::CssSelectorComplexity),
872        code: "css-selector-complexity",
873        aliases: &[],
874        label: "CSS Selector Complexity",
875        config_key: Some("css-selector-complexity"),
876        filter_flag: None,
877        mcp_issue_type: None,
878        suppress_token: Some("css-selector-complexity"),
879        suppress_file_level: false,
880        lsp: false,
881        docs_category: "health",
882    },
883    IssueKindMeta {
884        kind: Some(IssueKind::CssDeadSurface),
885        code: "css-dead-surface",
886        aliases: &[],
887        label: "CSS Dead Surface",
888        config_key: Some("css-dead-surface"),
889        filter_flag: None,
890        mcp_issue_type: None,
891        suppress_token: Some("css-dead-surface"),
892        suppress_file_level: false,
893        lsp: false,
894        docs_category: "health",
895    },
896    IssueKindMeta {
897        kind: Some(IssueKind::CssBrokenReference),
898        code: "css-broken-reference",
899        aliases: &[],
900        label: "CSS Broken Reference",
901        config_key: Some("css-broken-reference"),
902        filter_flag: None,
903        mcp_issue_type: None,
904        suppress_token: Some("css-broken-reference"),
905        suppress_file_level: false,
906        lsp: false,
907        docs_category: "health",
908    },
909    IssueKindMeta {
910        kind: Some(IssueKind::WeakenedTests),
911        code: "weakened-tests",
912        aliases: &[],
913        label: "Weakened Tests",
914        config_key: Some("weakened-tests"),
915        filter_flag: None,
916        mcp_issue_type: None,
917        suppress_token: Some("weakened-tests"),
918        suppress_file_level: false,
919        lsp: false,
920        docs_category: "audit",
921    },
922    IssueKindMeta {
923        kind: Some(IssueKind::AddedSuppression),
924        code: "added-suppression",
925        aliases: &[],
926        label: "Added Suppressions",
927        config_key: Some("added-suppression"),
928        filter_flag: None,
929        mcp_issue_type: None,
930        suppress_token: Some("added-suppression"),
931        suppress_file_level: false,
932        lsp: false,
933        docs_category: "audit",
934    },
935    IssueKindMeta {
936        kind: Some(IssueKind::TestOnlyBranch),
937        code: "test-only-branch",
938        aliases: &[],
939        label: "Test-Only Branches",
940        config_key: Some("test-only-branch"),
941        filter_flag: None,
942        mcp_issue_type: None,
943        suppress_token: Some("test-only-branch"),
944        suppress_file_level: false,
945        lsp: false,
946        docs_category: "audit",
947    },
948    IssueKindMeta {
949        kind: Some(IssueKind::DivergedCloneSibling),
950        code: "diverged-clone-sibling",
951        aliases: &[],
952        label: "Diverged Clone Siblings",
953        config_key: Some("diverged-clone-sibling"),
954        filter_flag: None,
955        mcp_issue_type: None,
956        suppress_token: Some("diverged-clone-sibling"),
957        suppress_file_level: false,
958        lsp: false,
959        docs_category: "audit",
960    },
961    IssueKindMeta {
962        kind: Some(IssueKind::AddedDependency),
963        code: "added-dependency",
964        aliases: &[],
965        label: "Added Dependencies",
966        config_key: Some("added-dependency"),
967        filter_flag: None,
968        mcp_issue_type: None,
969        // `package.json` cannot hold a comment, so the only suppression is
970        // an `overrides` entry that sets the rule to `off`.
971        suppress_token: None,
972        suppress_file_level: false,
973        lsp: false,
974        docs_category: "audit",
975    },
976];
977
978/// Shared contract facts for serialized `AnalysisResults` arrays.
979#[derive(Debug, Clone, Copy, PartialEq, Eq)]
980#[non_exhaustive]
981pub struct IssueResultMeta {
982    /// Canonical issue code that owns this result array.
983    pub code: &'static str,
984    /// Short SARIF rule description.
985    pub sarif_description: &'static str,
986    /// Explanation emitted in dead-code `_meta.rules`.
987    pub meta_description: &'static str,
988    /// Documentation path emitted in dead-code `_meta.rules`.
989    pub meta_docs_path: &'static str,
990    /// Human-readable name emitted in dead-code `_meta.rules`.
991    pub meta_name: &'static str,
992    /// Label used by CI summary tables.
993    pub summary_label: &'static str,
994    /// Documentation anchor under `/explanations/dead-code`.
995    pub docs_anchor: &'static str,
996    /// Serialized `AnalysisResults` array key that carries this issue row.
997    pub result_key: &'static str,
998    /// Whether `result_key` can contribute to `AnalysisResults::total_issues()`.
999    /// Prop-drilling contributes only when its saved severity is `error`.
1000    pub counts_in_total: bool,
1001}
1002
1003/// TypeScript backwards-compat alias emitted for a dead-code result row.
1004#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1005pub struct TsAliasMeta {
1006    /// Bare alias name kept available from the published `fallow/types` subpath.
1007    pub name: &'static str,
1008    /// Generated `*Finding` wrapper type the alias resolves to.
1009    pub parent: &'static str,
1010}
1011
1012/// TypeScript backwards-compat alias row for a dead-code result code.
1013#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1014pub struct IssueTsAliasMeta {
1015    /// Canonical issue code that owns this alias.
1016    pub code: &'static str,
1017    /// Published TypeScript alias policy.
1018    pub alias: TsAliasMeta,
1019}
1020
1021/// Published TypeScript backwards-compat aliases.
1022pub const ISSUE_TS_ALIAS_META: &[IssueTsAliasMeta] = &[
1023    IssueTsAliasMeta {
1024        code: "unused-file",
1025        alias: TsAliasMeta {
1026            name: "UnusedFile",
1027            parent: "UnusedFileFinding",
1028        },
1029    },
1030    IssueTsAliasMeta {
1031        code: "unused-export",
1032        alias: TsAliasMeta {
1033            name: "UnusedExport",
1034            parent: "UnusedExportFinding",
1035        },
1036    },
1037    IssueTsAliasMeta {
1038        code: "private-type-leak",
1039        alias: TsAliasMeta {
1040            name: "PrivateTypeLeak",
1041            parent: "PrivateTypeLeakFinding",
1042        },
1043    },
1044    IssueTsAliasMeta {
1045        code: "deprecated-export-in-use",
1046        alias: TsAliasMeta {
1047            name: "DeprecatedExportInUse",
1048            parent: "DeprecatedExportInUseFinding",
1049        },
1050    },
1051    IssueTsAliasMeta {
1052        code: "unused-dependency",
1053        alias: TsAliasMeta {
1054            name: "UnusedDependency",
1055            parent: "UnusedDependencyFinding",
1056        },
1057    },
1058    IssueTsAliasMeta {
1059        code: "unused-dev-dependency",
1060        alias: TsAliasMeta {
1061            name: "UnusedDependency",
1062            parent: "UnusedDevDependencyFinding",
1063        },
1064    },
1065    IssueTsAliasMeta {
1066        code: "unused-optional-dependency",
1067        alias: TsAliasMeta {
1068            name: "UnusedDependency",
1069            parent: "UnusedOptionalDependencyFinding",
1070        },
1071    },
1072    IssueTsAliasMeta {
1073        code: "unused-enum-member",
1074        alias: TsAliasMeta {
1075            name: "UnusedMember",
1076            parent: "UnusedEnumMemberFinding",
1077        },
1078    },
1079    IssueTsAliasMeta {
1080        code: "unused-class-member",
1081        alias: TsAliasMeta {
1082            name: "UnusedMember",
1083            parent: "UnusedClassMemberFinding",
1084        },
1085    },
1086    IssueTsAliasMeta {
1087        code: "unused-store-member",
1088        alias: TsAliasMeta {
1089            name: "UnusedMember",
1090            parent: "UnusedStoreMemberFinding",
1091        },
1092    },
1093    IssueTsAliasMeta {
1094        code: "unresolved-import",
1095        alias: TsAliasMeta {
1096            name: "UnresolvedImport",
1097            parent: "UnresolvedImportFinding",
1098        },
1099    },
1100    IssueTsAliasMeta {
1101        code: "unlisted-dependency",
1102        alias: TsAliasMeta {
1103            name: "UnlistedDependency",
1104            parent: "UnlistedDependencyFinding",
1105        },
1106    },
1107    IssueTsAliasMeta {
1108        code: "duplicate-export",
1109        alias: TsAliasMeta {
1110            name: "DuplicateExport",
1111            parent: "DuplicateExportFinding",
1112        },
1113    },
1114    IssueTsAliasMeta {
1115        code: "type-only-dependency",
1116        alias: TsAliasMeta {
1117            name: "TypeOnlyDependency",
1118            parent: "TypeOnlyDependencyFinding",
1119        },
1120    },
1121    IssueTsAliasMeta {
1122        code: "test-only-dependency",
1123        alias: TsAliasMeta {
1124            name: "TestOnlyDependency",
1125            parent: "TestOnlyDependencyFinding",
1126        },
1127    },
1128    IssueTsAliasMeta {
1129        code: "dev-dependency-in-production",
1130        alias: TsAliasMeta {
1131            name: "DevDependencyInProduction",
1132            parent: "DevDependencyInProductionFinding",
1133        },
1134    },
1135    IssueTsAliasMeta {
1136        code: "circular-dependency",
1137        alias: TsAliasMeta {
1138            name: "CircularDependency",
1139            parent: "CircularDependencyFinding",
1140        },
1141    },
1142    IssueTsAliasMeta {
1143        code: "re-export-cycle",
1144        alias: TsAliasMeta {
1145            name: "ReExportCycle",
1146            parent: "ReExportCycleFinding",
1147        },
1148    },
1149    IssueTsAliasMeta {
1150        code: "package-cycle",
1151        alias: TsAliasMeta {
1152            name: "PackageCycle",
1153            parent: "PackageCycleFinding",
1154        },
1155    },
1156    IssueTsAliasMeta {
1157        code: "boundary-violation",
1158        alias: TsAliasMeta {
1159            name: "BoundaryViolation",
1160            parent: "BoundaryViolationFinding",
1161        },
1162    },
1163    IssueTsAliasMeta {
1164        code: "unused-catalog-entry",
1165        alias: TsAliasMeta {
1166            name: "UnusedCatalogEntry",
1167            parent: "UnusedCatalogEntryFinding",
1168        },
1169    },
1170    IssueTsAliasMeta {
1171        code: "empty-catalog-group",
1172        alias: TsAliasMeta {
1173            name: "EmptyCatalogGroup",
1174            parent: "EmptyCatalogGroupFinding",
1175        },
1176    },
1177    IssueTsAliasMeta {
1178        code: "unresolved-catalog-reference",
1179        alias: TsAliasMeta {
1180            name: "UnresolvedCatalogReference",
1181            parent: "UnresolvedCatalogReferenceFinding",
1182        },
1183    },
1184    IssueTsAliasMeta {
1185        code: "unused-dependency-override",
1186        alias: TsAliasMeta {
1187            name: "UnusedDependencyOverride",
1188            parent: "UnusedDependencyOverrideFinding",
1189        },
1190    },
1191    IssueTsAliasMeta {
1192        code: "misconfigured-dependency-override",
1193        alias: TsAliasMeta {
1194            name: "MisconfiguredDependencyOverride",
1195            parent: "MisconfiguredDependencyOverrideFinding",
1196        },
1197    },
1198];
1199
1200/// All shared issue-to-result metadata rows.
1201pub const ISSUE_RESULT_META: &[IssueResultMeta] = &[
1202    IssueResultMeta {
1203        code: "unused-file",
1204        sarif_description: "File is not reachable from any entry point",
1205        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.",
1206        meta_docs_path: "explanations/dead-code#unused-files",
1207        meta_name: "Unused Files",
1208        summary_label: "Unused files",
1209        docs_anchor: "unused-files",
1210        result_key: "unused_files",
1211        counts_in_total: true,
1212    },
1213    IssueResultMeta {
1214        code: "unused-export",
1215        sarif_description: "Export is never imported",
1216        meta_description: "Named exports that are never imported by any other module in the project, including direct exports and re-exports through barrel files.",
1217        meta_docs_path: "explanations/dead-code#unused-exports",
1218        meta_name: "Unused Exports",
1219        summary_label: "Unused exports",
1220        docs_anchor: "unused-exports",
1221        result_key: "unused_exports",
1222        counts_in_total: true,
1223    },
1224    IssueResultMeta {
1225        code: "unused-type",
1226        sarif_description: "Type export is never imported",
1227        meta_description: "Type-only exports that are never imported. These do not generate runtime code but add maintenance burden.",
1228        meta_docs_path: "explanations/dead-code#unused-types",
1229        meta_name: "Unused Type Exports",
1230        summary_label: "Unused types",
1231        docs_anchor: "unused-types",
1232        result_key: "unused_types",
1233        counts_in_total: true,
1234    },
1235    IssueResultMeta {
1236        code: "private-type-leak",
1237        sarif_description: "Exported signature references a private type",
1238        meta_description: "Exported values or types whose public TypeScript signature references a same-file type declaration that is not exported.",
1239        meta_docs_path: "explanations/dead-code#private-type-leaks",
1240        meta_name: "Private Type Leaks",
1241        summary_label: "Private type leaks",
1242        docs_anchor: "private-type-leaks",
1243        result_key: "private_type_leaks",
1244        counts_in_total: true,
1245    },
1246    IssueResultMeta {
1247        code: "deprecated-export-in-use",
1248        sarif_description: "Export marked @deprecated is still referenced",
1249        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.",
1250        meta_docs_path: "explanations/dead-code#deprecated-exports-in-use",
1251        meta_name: "Deprecated Exports in Use",
1252        summary_label: "Deprecated exports in use",
1253        docs_anchor: "deprecated-exports-in-use",
1254        result_key: "deprecated_exports_in_use",
1255        counts_in_total: true,
1256    },
1257    IssueResultMeta {
1258        code: "unused-dependency",
1259        sarif_description: "Dependency listed but never imported",
1260        meta_description: "Packages listed in dependencies that are never imported or required by any source file.",
1261        meta_docs_path: "explanations/dead-code#unused-dependencies",
1262        meta_name: "Unused Dependencies",
1263        summary_label: "Unused dependencies",
1264        docs_anchor: "unused-dependencies",
1265        result_key: "unused_dependencies",
1266        counts_in_total: true,
1267    },
1268    IssueResultMeta {
1269        code: "unused-dev-dependency",
1270        sarif_description: "Dev dependency listed but never imported",
1271        meta_description: "Packages listed in devDependencies that are never imported by test files, config files, or scripts.",
1272        meta_docs_path: "explanations/dead-code#unused-devdependencies",
1273        meta_name: "Unused Dev Dependencies",
1274        summary_label: "Unused devDependencies",
1275        docs_anchor: "unused-dependencies",
1276        result_key: "unused_dev_dependencies",
1277        counts_in_total: true,
1278    },
1279    IssueResultMeta {
1280        code: "unused-optional-dependency",
1281        sarif_description: "Optional dependency listed but never imported",
1282        meta_description: "Packages listed in optionalDependencies that are never imported.",
1283        meta_docs_path: "explanations/dead-code#unused-optionaldependencies",
1284        meta_name: "Unused Optional Dependencies",
1285        summary_label: "Unused optionalDependencies",
1286        docs_anchor: "unused-dependencies",
1287        result_key: "unused_optional_dependencies",
1288        counts_in_total: true,
1289    },
1290    IssueResultMeta {
1291        code: "unused-enum-member",
1292        sarif_description: "Enum member is never referenced",
1293        meta_description: "Enum members that are never referenced in the codebase.",
1294        meta_docs_path: "explanations/dead-code#unused-enum-members",
1295        meta_name: "Unused Enum Members",
1296        summary_label: "Unused enum members",
1297        docs_anchor: "unused-enum-members",
1298        result_key: "unused_enum_members",
1299        counts_in_total: true,
1300    },
1301    IssueResultMeta {
1302        code: "unused-class-member",
1303        sarif_description: "Class member is never referenced",
1304        meta_description: "Class methods and properties that are never referenced outside the class.",
1305        meta_docs_path: "explanations/dead-code#unused-class-members",
1306        meta_name: "Unused Class Members",
1307        summary_label: "Unused class members",
1308        docs_anchor: "unused-class-members",
1309        result_key: "unused_class_members",
1310        counts_in_total: true,
1311    },
1312    IssueResultMeta {
1313        code: "unused-store-member",
1314        sarif_description: "Store member is never accessed by any consumer",
1315        meta_description: "Pinia store members declared but never accessed by any consumer project-wide.",
1316        meta_docs_path: "explanations/dead-code#unused-store-members",
1317        meta_name: "Unused Store Members",
1318        summary_label: "Unused store members",
1319        docs_anchor: "unused-store-members",
1320        result_key: "unused_store_members",
1321        counts_in_total: true,
1322    },
1323    IssueResultMeta {
1324        code: "unresolved-import",
1325        sarif_description: "Import could not be resolved",
1326        meta_description: "Import specifiers that could not be resolved to a file on disk.",
1327        meta_docs_path: "explanations/dead-code#unresolved-imports",
1328        meta_name: "Unresolved Imports",
1329        summary_label: "Unresolved imports",
1330        docs_anchor: "unresolved-imports",
1331        result_key: "unresolved_imports",
1332        counts_in_total: true,
1333    },
1334    IssueResultMeta {
1335        code: "unlisted-dependency",
1336        sarif_description: "Dependency used but not in package.json",
1337        meta_description: "Packages imported in source code but not listed in package.json.",
1338        meta_docs_path: "explanations/dead-code#unlisted-dependencies",
1339        meta_name: "Unlisted Dependencies",
1340        summary_label: "Unlisted dependencies",
1341        docs_anchor: "unlisted-dependencies",
1342        result_key: "unlisted_dependencies",
1343        counts_in_total: true,
1344    },
1345    IssueResultMeta {
1346        code: "duplicate-export",
1347        sarif_description: "Export name appears in multiple modules",
1348        meta_description: "The same export name is defined in multiple modules.",
1349        meta_docs_path: "explanations/dead-code#duplicate-exports",
1350        meta_name: "Duplicate Exports",
1351        summary_label: "Duplicate exports",
1352        docs_anchor: "duplicate-exports",
1353        result_key: "duplicate_exports",
1354        counts_in_total: true,
1355    },
1356    IssueResultMeta {
1357        code: "type-only-dependency",
1358        sarif_description: "Production dependency only used via type-only imports",
1359        meta_description: "Production dependencies that are only imported via type-only imports.",
1360        meta_docs_path: "explanations/dead-code#type-only-dependencies",
1361        meta_name: "Type-only Dependencies",
1362        summary_label: "Type-only dependencies",
1363        docs_anchor: "type-only-dependencies",
1364        result_key: "type_only_dependencies",
1365        counts_in_total: true,
1366    },
1367    IssueResultMeta {
1368        code: "test-only-dependency",
1369        sarif_description: "Production dependency only imported by test files",
1370        meta_description: "Production dependencies that are only imported from test files.",
1371        meta_docs_path: "explanations/dead-code#test-only-dependencies",
1372        meta_name: "Test-only Dependencies",
1373        summary_label: "Test-only dependencies",
1374        docs_anchor: "test-only-dependencies",
1375        result_key: "test_only_dependencies",
1376        counts_in_total: true,
1377    },
1378    IssueResultMeta {
1379        code: "dev-dependency-in-production",
1380        sarif_description: "devDependency imported by production code with a runtime import",
1381        meta_description: "devDependencies imported by production source code via a runtime/value import; they should be promoted to dependencies.",
1382        meta_docs_path: "explanations/dead-code#dev-dependencies-in-production",
1383        meta_name: "Dev Dependencies Used in Production",
1384        summary_label: "Dev dependencies used in production",
1385        docs_anchor: "dev-dependencies-in-production",
1386        result_key: "dev_dependencies_in_production",
1387        counts_in_total: true,
1388    },
1389    IssueResultMeta {
1390        code: "circular-dependency",
1391        sarif_description: "Circular dependency chain detected",
1392        meta_description: "A cycle in the module import graph.",
1393        meta_docs_path: "explanations/dead-code#circular-dependencies",
1394        meta_name: "Circular Dependencies",
1395        summary_label: "Circular dependencies",
1396        docs_anchor: "circular-dependencies",
1397        result_key: "circular_dependencies",
1398        counts_in_total: true,
1399    },
1400    IssueResultMeta {
1401        code: "re-export-cycle",
1402        sarif_description: "Two or more barrel files re-export from each other in a loop",
1403        meta_description: "A barrel file re-exports from another barrel that ultimately re-exports back.",
1404        meta_docs_path: "explanations/dead-code#re-export-cycles",
1405        meta_name: "Re-Export Cycles",
1406        summary_label: "Re-export cycles",
1407        docs_anchor: "re-export-cycles",
1408        result_key: "re_export_cycles",
1409        counts_in_total: true,
1410    },
1411    IssueResultMeta {
1412        code: "package-cycle",
1413        sarif_description: "Two or more workspace packages import each other in a loop",
1414        meta_description: "Workspace packages whose resolved imports form a cycle, even when no file-level cycle exists.",
1415        meta_docs_path: "explanations/dead-code#package-cycles",
1416        meta_name: "Package Cycles",
1417        summary_label: "Package cycles",
1418        docs_anchor: "package-cycles",
1419        result_key: "package_cycles",
1420        counts_in_total: true,
1421    },
1422    IssueResultMeta {
1423        code: "boundary-violation",
1424        sarif_description: "Import crosses a configured architecture boundary",
1425        meta_description: "A module imports from a zone that its configured boundary rules do not allow.",
1426        meta_docs_path: "explanations/dead-code#boundary-violations",
1427        meta_name: "Boundary Violations",
1428        summary_label: "Boundary violations",
1429        docs_anchor: "boundary-violations",
1430        result_key: "boundary_violations",
1431        counts_in_total: true,
1432    },
1433    IssueResultMeta {
1434        code: "boundary-coverage",
1435        sarif_description: "Source file matches no configured architecture boundary zone",
1436        meta_description: "A reachable source file is not assigned to any configured boundary zone while boundary coverage is required.",
1437        meta_docs_path: "explanations/dead-code#boundary-violations",
1438        meta_name: "Boundary Coverage",
1439        summary_label: "Boundary coverage",
1440        docs_anchor: "boundary-violations",
1441        result_key: "boundary_coverage_violations",
1442        counts_in_total: true,
1443    },
1444    IssueResultMeta {
1445        code: "boundary-call-violation",
1446        sarif_description: "Zoned file calls a callee its zone forbids",
1447        meta_description: "A file classified into a boundary zone calls a callee matching one of the zone's forbidden call patterns.",
1448        meta_docs_path: "explanations/dead-code#boundary-violations",
1449        meta_name: "Boundary Call Violation",
1450        summary_label: "Boundary calls",
1451        docs_anchor: "boundary-violations",
1452        result_key: "boundary_call_violations",
1453        counts_in_total: true,
1454    },
1455    IssueResultMeta {
1456        code: "policy-violation",
1457        sarif_description: "Usage violates a configured rule-pack policy",
1458        meta_description: "A call, import, export, catalogue-derived effect, or graph-resolved GDP proof producer violates a configured rule pack rule.",
1459        meta_docs_path: "explanations/dead-code#policy-violations",
1460        meta_name: "Policy Violation",
1461        summary_label: "Policy violations",
1462        docs_anchor: "policy-violations",
1463        result_key: "policy_violations",
1464        counts_in_total: true,
1465    },
1466    IssueResultMeta {
1467        code: "invalid-client-export",
1468        sarif_description: "\"use client\" file exports a server-only / route-config name",
1469        meta_description: "A file carrying the use client directive also exports a Next.js server-only or route-segment config name.",
1470        meta_docs_path: "explanations/dead-code#invalid-client-exports",
1471        meta_name: "Invalid client export",
1472        summary_label: "Invalid client exports",
1473        docs_anchor: "invalid-client-exports",
1474        result_key: "invalid_client_exports",
1475        counts_in_total: true,
1476    },
1477    IssueResultMeta {
1478        code: "mixed-client-server-barrel",
1479        sarif_description: "Barrel re-exports both a \"use client\" module and a server-only module",
1480        meta_description: "A barrel file forwards a name from a use client module alongside a name from a server-only module.",
1481        meta_docs_path: "explanations/dead-code#mixed-client-server-barrels",
1482        meta_name: "Mixed client/server barrel",
1483        summary_label: "Mixed client/server barrels",
1484        docs_anchor: "mixed-client-server-barrels",
1485        result_key: "mixed_client_server_barrels",
1486        counts_in_total: true,
1487    },
1488    IssueResultMeta {
1489        code: "misplaced-directive",
1490        sarif_description: "\"use client\" / \"use server\" directive is not in the leading position and is ignored",
1491        meta_description: "A use client or use server directive string appears after a non-directive statement and is ignored.",
1492        meta_docs_path: "explanations/dead-code#misplaced-directives",
1493        meta_name: "Misplaced directive",
1494        summary_label: "Misplaced directives",
1495        docs_anchor: "misplaced-directives",
1496        result_key: "misplaced_directives",
1497        counts_in_total: true,
1498    },
1499    IssueResultMeta {
1500        code: "unprovided-inject",
1501        sarif_description: "inject() / getContext() reads a key that no provide() / setContext() supplies",
1502        meta_description: "A Vue inject or Svelte getContext reads a dependency-injection key that no matching provider supplies.",
1503        meta_docs_path: "explanations/dead-code#unprovided-injects",
1504        meta_name: "Unprovided injects",
1505        summary_label: "Unprovided injects",
1506        docs_anchor: "unprovided-injects",
1507        result_key: "unprovided_injects",
1508        counts_in_total: true,
1509    },
1510    IssueResultMeta {
1511        code: "unrendered-component",
1512        sarif_description: "A Vue / Svelte component is reachable through a barrel but rendered nowhere",
1513        meta_description: "A Vue or Svelte single-file component is reachable through the graph but rendered nowhere in the project.",
1514        meta_docs_path: "explanations/dead-code#unrendered-components",
1515        meta_name: "Unrendered components",
1516        summary_label: "Unrendered components",
1517        docs_anchor: "unrendered-components",
1518        result_key: "unrendered_components",
1519        counts_in_total: true,
1520    },
1521    IssueResultMeta {
1522        code: "unused-component-prop",
1523        sarif_description: "A Vue, Svelte, or React component prop is referenced nowhere in its own component",
1524        meta_description: "A declared Vue, Svelte, React, or Preact component prop is referenced nowhere inside its own component.",
1525        meta_docs_path: "explanations/dead-code#unused-component-props",
1526        meta_name: "Unused component props",
1527        summary_label: "Unused component props",
1528        docs_anchor: "unused-component-props",
1529        result_key: "unused_component_props",
1530        counts_in_total: true,
1531    },
1532    IssueResultMeta {
1533        code: "absent-component-prop",
1534        sarif_description: "Known reachable callers do not supply this used optional input; review defaults and API intent manually.",
1535        meta_description: "Known reachable callers do not supply this used optional input; review defaults and API intent manually.",
1536        meta_docs_path: "explanations/dead-code#absent-component-props",
1537        meta_name: "Absent optional component props",
1538        summary_label: "Absent optional component props",
1539        docs_anchor: "absent-component-props",
1540        result_key: "absent_component_props",
1541        counts_in_total: true,
1542    },
1543    IssueResultMeta {
1544        code: "unused-component-emit",
1545        sarif_description: "A Vue <script setup> defineEmits event is emitted nowhere in its own component",
1546        meta_description: "A Vue script setup defineEmits event is emitted nowhere in its own component.",
1547        meta_docs_path: "explanations/dead-code#unused-component-emits",
1548        meta_name: "Unused component emits",
1549        summary_label: "Unused component emits",
1550        docs_anchor: "unused-component-emits",
1551        result_key: "unused_component_emits",
1552        counts_in_total: true,
1553    },
1554    IssueResultMeta {
1555        code: "unused-component-input",
1556        sarif_description: "An Angular @Input() / signal input() / model() input is read nowhere in its own component",
1557        meta_description: "An Angular input is read nowhere in its own component.",
1558        meta_docs_path: "explanations/dead-code#unused-component-inputs",
1559        meta_name: "Unused component inputs",
1560        summary_label: "Unused component inputs",
1561        docs_anchor: "unused-component-inputs",
1562        result_key: "unused_component_inputs",
1563        counts_in_total: true,
1564    },
1565    IssueResultMeta {
1566        code: "unused-component-output",
1567        sarif_description: "An Angular @Output() / signal output() output is emitted nowhere in its own component",
1568        meta_description: "An Angular output is emitted nowhere in its own component.",
1569        meta_docs_path: "explanations/dead-code#unused-component-outputs",
1570        meta_name: "Unused component outputs",
1571        summary_label: "Unused component outputs",
1572        docs_anchor: "unused-component-outputs",
1573        result_key: "unused_component_outputs",
1574        counts_in_total: true,
1575    },
1576    IssueResultMeta {
1577        code: "unused-svelte-event",
1578        sarif_description: "A Svelte component dispatches a createEventDispatcher event whose name is listened to nowhere in the project",
1579        meta_description: "A Svelte component dispatches a custom event whose name is listened to nowhere in the analyzed project.",
1580        meta_docs_path: "explanations/dead-code#unused-svelte-events",
1581        meta_name: "Unused Svelte events",
1582        summary_label: "Unused Svelte events",
1583        docs_anchor: "unused-svelte-events",
1584        result_key: "unused_svelte_events",
1585        counts_in_total: true,
1586    },
1587    IssueResultMeta {
1588        code: "unused-server-action",
1589        sarif_description: "A Next.js Server Action exported from a \"use server\" file is referenced by no code in the project",
1590        meta_description: "A Next.js Server Action exported from a use server file is referenced by no code in the project.",
1591        meta_docs_path: "explanations/dead-code#unused-server-actions",
1592        meta_name: "Unused server actions",
1593        summary_label: "Unused server actions",
1594        docs_anchor: "unused-server-actions",
1595        result_key: "unused_server_actions",
1596        counts_in_total: true,
1597    },
1598    IssueResultMeta {
1599        code: "unused-load-data-key",
1600        sarif_description: "A SvelteKit load() return-object key is read by no consumer",
1601        meta_description: "A SvelteKit load return-object key is read by no route or project-wide consumer.",
1602        meta_docs_path: "explanations/dead-code#unused-load-data-keys",
1603        meta_name: "Unused load data keys",
1604        summary_label: "Unused load data keys",
1605        docs_anchor: "unused-load-data-keys",
1606        result_key: "unused_load_data_keys",
1607        counts_in_total: true,
1608    },
1609    IssueResultMeta {
1610        code: "unused-effect-layer",
1611        sarif_description: "An Effect layer is composed only into layers that nothing uses",
1612        meta_description: "An Effect layer is composed only into other layers that nothing in the project uses.",
1613        meta_docs_path: "explanations/dead-code#unused-effect-layers",
1614        meta_name: "Unused Effect layers",
1615        summary_label: "Unused Effect layers",
1616        docs_anchor: "unused-effect-layers",
1617        result_key: "unused_effect_layers",
1618        counts_in_total: true,
1619    },
1620    IssueResultMeta {
1621        code: "unused-tagged-error",
1622        sarif_description: "An Effect tagged error class is referenced only as a type or a catchTag string, and no code constructs or fails with it",
1623        meta_description: "An Effect tagged error class is referenced only as a type or a catchTag string. No code constructs or fails with it.",
1624        meta_docs_path: "explanations/dead-code#unused-tagged-errors",
1625        meta_name: "Unused tagged errors",
1626        summary_label: "Unused tagged errors",
1627        docs_anchor: "unused-tagged-errors",
1628        result_key: "unused_tagged_errors",
1629        counts_in_total: true,
1630    },
1631    IssueResultMeta {
1632        code: "unproduced-catch-tag",
1633        sarif_description: "A catchTag handler names a project error tag that no code produces",
1634        meta_description: "A catchTag or catchTags handler names a project error tag that no code in the project produces.",
1635        meta_docs_path: "explanations/dead-code#unproduced-catch-tags",
1636        meta_name: "Unproduced catch tags",
1637        summary_label: "Unproduced catch tags",
1638        docs_anchor: "unproduced-catch-tags",
1639        result_key: "unproduced_catch_tags",
1640        counts_in_total: true,
1641    },
1642    IssueResultMeta {
1643        code: "route-collision",
1644        sarif_description: "Two or more Next.js App Router route files resolve to the same URL",
1645        meta_description: "Two or more Next.js App Router route files resolve to the same URL within one app root.",
1646        meta_docs_path: "explanations/dead-code#route-collisions",
1647        meta_name: "Route collision",
1648        summary_label: "Route collisions",
1649        docs_anchor: "route-collisions",
1650        result_key: "route_collisions",
1651        counts_in_total: true,
1652    },
1653    IssueResultMeta {
1654        code: "dynamic-segment-name-conflict",
1655        sarif_description: "Sibling Next.js dynamic route segments use different slug names at the same position",
1656        meta_description: "Sibling Next.js dynamic route segments use different slug names at the same position.",
1657        meta_docs_path: "explanations/dead-code#dynamic-segment-name-conflicts",
1658        meta_name: "Dynamic segment name conflict",
1659        summary_label: "Dynamic segment conflicts",
1660        docs_anchor: "dynamic-segment-name-conflicts",
1661        result_key: "dynamic_segment_name_conflicts",
1662        counts_in_total: true,
1663    },
1664    IssueResultMeta {
1665        code: "stale-suppression",
1666        sarif_description: "Suppression comment or tag no longer matches any issue",
1667        meta_description: "A fallow suppression comment or tag no longer matches any active issue.",
1668        meta_docs_path: "explanations/dead-code#stale-suppressions",
1669        meta_name: "Stale Suppressions",
1670        summary_label: "Stale suppressions",
1671        docs_anchor: "stale-suppressions",
1672        result_key: "stale_suppressions",
1673        counts_in_total: true,
1674    },
1675    IssueResultMeta {
1676        code: "unused-catalog-entry",
1677        sarif_description: "Catalog entry not referenced by any workspace package",
1678        meta_description: "A package manager catalog entry is not referenced by any workspace package.json.",
1679        meta_docs_path: "explanations/dead-code#unused-catalog-entries",
1680        meta_name: "Unused catalog entry",
1681        summary_label: "Unused catalog entries",
1682        docs_anchor: "unused-catalog-entries",
1683        result_key: "unused_catalog_entries",
1684        counts_in_total: true,
1685    },
1686    IssueResultMeta {
1687        code: "empty-catalog-group",
1688        sarif_description: "Named catalog group has no entries",
1689        meta_description: "A named package manager catalog group has no package entries.",
1690        meta_docs_path: "explanations/dead-code#empty-catalog-groups",
1691        meta_name: "Empty catalog group",
1692        summary_label: "Empty catalog groups",
1693        docs_anchor: "empty-catalog-groups",
1694        result_key: "empty_catalog_groups",
1695        counts_in_total: true,
1696    },
1697    IssueResultMeta {
1698        code: "unresolved-catalog-reference",
1699        sarif_description: "package.json references a catalog that does not declare the package",
1700        meta_description: "A workspace package.json uses a catalog protocol reference that no catalog declares.",
1701        meta_docs_path: "explanations/dead-code#unresolved-catalog-references",
1702        meta_name: "Unresolved catalog reference",
1703        summary_label: "Unresolved catalog references",
1704        docs_anchor: "unresolved-catalog-references",
1705        result_key: "unresolved_catalog_references",
1706        counts_in_total: true,
1707    },
1708    IssueResultMeta {
1709        code: "unused-dependency-override",
1710        sarif_description: "Package-manager override target is not declared or resolved",
1711        meta_description: "A dependency override targets a package not declared by any workspace package and not present in the active readable lockfile.",
1712        meta_docs_path: "explanations/dead-code#unused-dependency-overrides",
1713        meta_name: "Unused dependency override",
1714        summary_label: "Unused dependency overrides",
1715        docs_anchor: "unused-dependency-overrides",
1716        result_key: "unused_dependency_overrides",
1717        counts_in_total: true,
1718    },
1719    IssueResultMeta {
1720        code: "misconfigured-dependency-override",
1721        sarif_description: "Package-manager override has an unparsable key or value",
1722        meta_description: "A dependency override key or value does not parse according to its package manager's grammar.",
1723        meta_docs_path: "explanations/dead-code#misconfigured-dependency-overrides",
1724        meta_name: "Misconfigured dependency override",
1725        summary_label: "Misconfigured dependency overrides",
1726        docs_anchor: "misconfigured-dependency-overrides",
1727        result_key: "misconfigured_dependency_overrides",
1728        counts_in_total: true,
1729    },
1730    IssueResultMeta {
1731        code: "prop-drilling",
1732        sarif_description: "A React/Preact prop is forwarded unchanged through 3+ pass-through components to a distant consumer",
1733        meta_description: "A React or Preact prop is forwarded unchanged through multiple pass-through components to a distant consumer.",
1734        meta_docs_path: "explanations/dead-code#prop-drilling",
1735        meta_name: "Prop drilling",
1736        summary_label: "Prop drilling",
1737        docs_anchor: "prop-drilling",
1738        result_key: "prop_drilling_chains",
1739        counts_in_total: true,
1740    },
1741    IssueResultMeta {
1742        code: "thin-wrapper",
1743        sarif_description: "A React/Preact component whose whole body is a single spread-forwarded child render (a candidate for inlining)",
1744        meta_description: "A React or Preact component is structural indirection around a single spread-forwarded child render.",
1745        meta_docs_path: "explanations/dead-code#thin-wrapper",
1746        meta_name: "Thin wrapper",
1747        summary_label: "Thin wrappers",
1748        docs_anchor: "thin-wrapper",
1749        result_key: "thin_wrappers",
1750        counts_in_total: false,
1751    },
1752    IssueResultMeta {
1753        code: "duplicate-prop-shape",
1754        sarif_description: "Three or more React/Preact components across two or more files declare an identical prop-name set (a missing shared Props type)",
1755        meta_description: "Multiple React or Preact components declare an identical significant prop-name set.",
1756        meta_docs_path: "explanations/dead-code#duplicate-prop-shape",
1757        meta_name: "Duplicate prop shape",
1758        summary_label: "Duplicate prop shapes",
1759        docs_anchor: "duplicate-prop-shape",
1760        result_key: "duplicate_prop_shapes",
1761        counts_in_total: false,
1762    },
1763];
1764
1765/// Canonical names and aliases accepted by `IssueKind::parse`.
1766pub static KNOWN_ISSUE_KIND_NAMES: LazyLock<Vec<&'static str>> =
1767    LazyLock::new(known_issue_kind_names_from_meta);
1768
1769/// CLI filter flags on `fallow dead-code` that scope output to one issue family.
1770pub static DEAD_CODE_FILTER_FLAGS: LazyLock<Vec<&'static str>> =
1771    LazyLock::new(dead_code_filter_flags_from_meta);
1772
1773/// MCP issue selector names mapped to dead-code CLI flags.
1774pub static MCP_ISSUE_TYPE_FLAGS: LazyLock<Vec<(&'static str, &'static str)>> =
1775    LazyLock::new(mcp_issue_type_flags_from_meta);
1776
1777/// Result issue codes emitted by the dead-code CodeClimate formatter.
1778pub static CODECLIMATE_RESULT_CODES: LazyLock<Vec<&'static str>> =
1779    LazyLock::new(codeclimate_result_codes_from_meta);
1780
1781fn known_issue_kind_names_from_meta() -> Vec<&'static str> {
1782    let mut names = Vec::new();
1783    for meta in ISSUE_KIND_META.iter().filter(|meta| meta.kind.is_some()) {
1784        push_unique(&mut names, meta.code);
1785        for alias in meta.aliases {
1786            push_unique(&mut names, *alias);
1787        }
1788    }
1789    names
1790}
1791
1792fn dead_code_filter_flags_from_meta() -> Vec<&'static str> {
1793    let mut flags = Vec::new();
1794    for meta in ISSUE_KIND_META {
1795        if let Some(flag) = meta.filter_flag {
1796            push_unique(&mut flags, flag);
1797        }
1798    }
1799    flags
1800}
1801
1802fn mcp_issue_type_flags_from_meta() -> Vec<(&'static str, &'static str)> {
1803    ISSUE_KIND_META
1804        .iter()
1805        .filter_map(|meta| meta.mcp_pair())
1806        .collect()
1807}
1808
1809fn codeclimate_result_codes_from_meta() -> Vec<&'static str> {
1810    ISSUE_RESULT_META
1811        .iter()
1812        .filter(|meta| meta.counts_in_total)
1813        .map(|meta| meta.code)
1814        .collect()
1815}
1816
1817fn push_unique<T: Copy + PartialEq>(items: &mut Vec<T>, item: T) {
1818    if !items.contains(&item) {
1819        items.push(item);
1820    }
1821}
1822
1823/// Lookup metadata by canonical code.
1824#[must_use]
1825pub fn issue_meta_by_code(code: &str) -> Option<&'static IssueKindMeta> {
1826    ISSUE_KIND_META.iter().find(|meta| meta.code == code)
1827}
1828
1829/// Lookup metadata by canonical code or alias.
1830#[must_use]
1831pub fn issue_meta_for_token(token: &str) -> Option<&'static IssueKindMeta> {
1832    ISSUE_KIND_META
1833        .iter()
1834        .find(|meta| meta.code == token || meta.aliases.contains(&token))
1835}
1836
1837/// Lookup metadata by any shared contract token: canonical code, alias,
1838/// config key, MCP selector, suppression token, or CLI filter flag.
1839#[must_use]
1840pub fn issue_meta_for_contract_token(token: &str) -> Option<&'static IssueKindMeta> {
1841    let normalized = token.trim().trim_start_matches("--").replace('_', "-");
1842    ISSUE_KIND_META
1843        .iter()
1844        .find(|meta| issue_meta_matches_contract_token(meta, &normalized))
1845}
1846
1847/// Whether a metadata row owns the provided shared contract token.
1848#[must_use]
1849pub fn issue_meta_matches_contract_token(meta: &IssueKindMeta, token: &str) -> bool {
1850    let normalized = token.trim().trim_start_matches("--").replace('_', "-");
1851    meta.code == normalized
1852        || meta.aliases.contains(&normalized.as_str())
1853        || meta.config_key == Some(normalized.as_str())
1854        || meta.mcp_issue_type == Some(normalized.as_str())
1855        || meta.suppress_token == Some(normalized.as_str())
1856        || meta.filter_flag.map(|flag| flag.trim_start_matches("--")) == Some(normalized.as_str())
1857}
1858
1859/// Lookup metadata by backing issue kind.
1860#[must_use]
1861pub fn issue_meta_by_kind(kind: IssueKind) -> Option<&'static IssueKindMeta> {
1862    ISSUE_KIND_META.iter().find(|meta| meta.kind == Some(kind))
1863}
1864
1865/// Lookup serialized result metadata by canonical issue code.
1866#[must_use]
1867pub fn issue_result_meta_by_code(code: &str) -> Option<&'static IssueResultMeta> {
1868    ISSUE_RESULT_META.iter().find(|meta| meta.code == code)
1869}
1870
1871/// SARIF rule ids used by CI formatters for a canonical issue code.
1872#[must_use]
1873pub fn issue_sarif_rule_ids(code: &str) -> Vec<String> {
1874    let mut ids = vec![format!("fallow/{code}")];
1875    if code == "stale-suppression" {
1876        ids.push("fallow/missing-suppression-reason".to_string());
1877    }
1878    ids
1879}
1880
1881/// Short SARIF rule description for a rule id emitted by dead-code output.
1882#[must_use]
1883pub fn issue_sarif_rule_description(rule_id: &str) -> Option<&'static str> {
1884    let code = rule_id.strip_prefix("fallow/")?;
1885    if code == "missing-suppression-reason" {
1886        return Some("Suppression comment omits a required reason");
1887    }
1888    issue_result_meta_by_code(code).map(|meta| meta.sarif_description)
1889}
1890
1891/// CodeClimate check names used by CI formatters for a canonical issue code.
1892#[must_use]
1893pub fn issue_codeclimate_check_names(code: &str) -> Vec<String> {
1894    if !CODECLIMATE_RESULT_CODES.contains(&code) {
1895        return Vec::new();
1896    }
1897    issue_sarif_rule_ids(code)
1898}
1899
1900/// Documentation anchor under `/explanations/dead-code` for a canonical issue
1901/// code.
1902#[must_use]
1903pub fn issue_docs_anchor(code: &str) -> Option<&'static str> {
1904    issue_result_meta_by_code(code).map(|meta| meta.docs_anchor)
1905}
1906
1907/// Published TypeScript alias policy for backwards-compatible bare names.
1908#[must_use]
1909pub fn issue_ts_alias(code: &str) -> Option<TsAliasMeta> {
1910    ISSUE_TS_ALIAS_META
1911        .iter()
1912        .find(|meta| meta.code == code)
1913        .map(|meta| meta.alias)
1914}
1915
1916/// Rows exposed by the LSP issue-type capability.
1917pub fn diagnostic_issue_metas() -> impl Iterator<Item = &'static IssueKindMeta> {
1918    ISSUE_KIND_META.iter().filter(|meta| meta.lsp)
1919}
1920
1921/// Rows that map to a serialized `AnalysisResults` array.
1922pub fn result_issue_metas() -> impl Iterator<Item = &'static IssueResultMeta> {
1923    ISSUE_RESULT_META.iter()
1924}
1925
1926/// Rows whose serialized `AnalysisResults` array contributes to `total_issues`.
1927pub fn counted_result_issue_metas() -> impl Iterator<Item = &'static IssueResultMeta> {
1928    result_issue_metas().filter(|meta| meta.counts_in_total)
1929}
1930
1931/// Canonical issue codes `fallow fix` can rewrite automatically. Shared by the
1932/// `fallow schema` `issue_types` rows (`fixable`) and the `fallow://issue-types`
1933/// MCP resource so the two surfaces never disagree on fixability.
1934pub const FIXABLE_ISSUE_CODES: &[&str] = &[
1935    "unused-export",
1936    "unused-enum-member",
1937    "unused-dependency",
1938    "unused-dev-dependency",
1939    "unused-optional-dependency",
1940    "unused-catalog-entry",
1941];
1942
1943/// Whether `fallow fix` can rewrite findings of a canonical issue code.
1944#[must_use]
1945pub fn issue_is_fixable(code: &str) -> bool {
1946    FIXABLE_ISSUE_CODES.contains(&code)
1947}
1948
1949#[cfg(test)]
1950mod tests {
1951    use std::collections::BTreeSet;
1952
1953    use crate::results::TOTAL_ISSUE_RESULT_KEYS;
1954
1955    use super::*;
1956
1957    #[test]
1958    fn fixable_codes_are_canonical_issue_codes() {
1959        for code in FIXABLE_ISSUE_CODES {
1960            assert!(
1961                issue_meta_by_code(code).is_some(),
1962                "fixable code {code} is not a canonical ISSUE_KIND_META code"
1963            );
1964        }
1965        assert!(issue_is_fixable("unused-export"));
1966        assert!(!issue_is_fixable("unused-file"));
1967    }
1968
1969    #[test]
1970    fn known_names_round_trip_through_metadata() {
1971        for name in KNOWN_ISSUE_KIND_NAMES.iter() {
1972            let meta = issue_meta_for_token(name)
1973                .unwrap_or_else(|| panic!("known issue name {name} missing metadata row"));
1974            assert!(
1975                meta.kind.is_some(),
1976                "known issue name {name} maps to non-IssueKind metadata"
1977            );
1978        }
1979    }
1980
1981    #[test]
1982    fn issue_kind_variants_have_metadata() {
1983        for &kind in IssueKind::ALL {
1984            assert!(
1985                issue_meta_by_kind(kind).is_some(),
1986                "IssueKind {kind:?} has no metadata row"
1987            );
1988        }
1989    }
1990
1991    #[test]
1992    fn dead_code_filter_flags_match_metadata() {
1993        let from_constants: BTreeSet<&str> = DEAD_CODE_FILTER_FLAGS.iter().copied().collect();
1994        let from_meta: BTreeSet<&str> = ISSUE_KIND_META
1995            .iter()
1996            .filter_map(|meta| meta.filter_flag)
1997            .collect();
1998        assert_eq!(from_constants, from_meta);
1999    }
2000
2001    #[test]
2002    fn mcp_issue_type_flags_match_metadata() {
2003        let from_constants: BTreeSet<(&str, &str)> = MCP_ISSUE_TYPE_FLAGS.iter().copied().collect();
2004        let from_meta: BTreeSet<(&str, &str)> = ISSUE_KIND_META
2005            .iter()
2006            .filter_map(|meta| meta.mcp_pair())
2007            .collect();
2008        assert_eq!(from_constants, from_meta);
2009    }
2010
2011    #[test]
2012    fn lsp_exposes_only_actual_diagnostic_codes() {
2013        let codes: BTreeSet<&str> = diagnostic_issue_metas().map(|meta| meta.code).collect();
2014        assert!(codes.contains("boundary-violation"));
2015        assert!(!codes.contains("boundary-coverage"));
2016        assert!(!codes.contains("boundary-call-violation"));
2017    }
2018
2019    #[test]
2020    fn issue_codes_are_unique() {
2021        let mut seen = BTreeSet::new();
2022        for meta in ISSUE_KIND_META {
2023            assert!(seen.insert(meta.code), "duplicate issue code {}", meta.code);
2024        }
2025    }
2026
2027    #[test]
2028    fn contract_tokens_resolve_through_metadata() {
2029        for meta in ISSUE_KIND_META {
2030            assert_eq!(
2031                issue_meta_for_contract_token(meta.code).map(|resolved| resolved.code),
2032                Some(meta.code),
2033                "canonical issue code {} should resolve through contract token lookup",
2034                meta.code
2035            );
2036            for token in meta.aliases {
2037                assert!(
2038                    issue_meta_matches_contract_token(meta, token),
2039                    "alias {token} should match {}",
2040                    meta.code
2041                );
2042            }
2043            for token in [
2044                meta.config_key,
2045                meta.mcp_issue_type,
2046                meta.suppress_token,
2047                meta.filter_flag,
2048            ]
2049            .into_iter()
2050            .flatten()
2051            {
2052                assert!(
2053                    issue_meta_for_contract_token(token).is_some(),
2054                    "contract token {token} should resolve through metadata"
2055                );
2056                assert!(
2057                    issue_meta_matches_contract_token(meta, token),
2058                    "contract token {token} should match {}",
2059                    meta.code
2060                );
2061            }
2062        }
2063    }
2064
2065    #[test]
2066    fn sarif_rule_descriptions_cover_result_metadata() {
2067        for meta in result_issue_metas() {
2068            for rule_id in issue_sarif_rule_ids(meta.code) {
2069                assert!(
2070                    issue_sarif_rule_description(&rule_id).is_some(),
2071                    "SARIF rule {rule_id} for {} needs a central description",
2072                    meta.code
2073                );
2074            }
2075        }
2076    }
2077
2078    #[test]
2079    fn result_meta_codes_have_issue_metadata() {
2080        for meta in ISSUE_RESULT_META {
2081            assert!(
2082                issue_meta_by_code(meta.code).is_some(),
2083                "result metadata code {} has no issue metadata row",
2084                meta.code
2085            );
2086        }
2087    }
2088
2089    #[test]
2090    fn result_meta_codes_have_docs_anchors() {
2091        for meta in ISSUE_RESULT_META {
2092            let issue = issue_meta_by_code(meta.code)
2093                .unwrap_or_else(|| panic!("result metadata code {} has no issue row", meta.code));
2094            assert_eq!(
2095                issue.docs_anchor(),
2096                Some(meta.docs_anchor),
2097                "result metadata code {} has mismatched docs anchor",
2098                meta.code
2099            );
2100        }
2101    }
2102
2103    #[test]
2104    fn result_meta_codes_have_summary_labels() {
2105        for meta in ISSUE_RESULT_META {
2106            assert!(
2107                !meta.summary_label.is_empty(),
2108                "result metadata code {} has no summary label",
2109                meta.code
2110            );
2111        }
2112    }
2113
2114    #[test]
2115    fn result_meta_codes_have_meta_names() {
2116        for meta in ISSUE_RESULT_META {
2117            assert!(
2118                !meta.meta_name.is_empty(),
2119                "result metadata code {} has no meta name",
2120                meta.code
2121            );
2122        }
2123    }
2124
2125    #[test]
2126    fn result_meta_codes_have_meta_docs_paths() {
2127        for meta in ISSUE_RESULT_META {
2128            assert!(
2129                meta.meta_docs_path.starts_with("explanations/dead-code#"),
2130                "result metadata code {} has invalid meta docs path",
2131                meta.code
2132            );
2133        }
2134    }
2135
2136    #[test]
2137    fn result_meta_codes_have_meta_descriptions() {
2138        for meta in ISSUE_RESULT_META {
2139            assert!(
2140                !meta.meta_description.is_empty(),
2141                "result metadata code {} has no meta description",
2142                meta.code
2143            );
2144        }
2145    }
2146
2147    #[test]
2148    fn ci_format_ids_are_prefixed_and_known() {
2149        let result_codes: BTreeSet<&str> = result_issue_metas().map(|meta| meta.code).collect();
2150        let codeclimate_codes: BTreeSet<&str> = CODECLIMATE_RESULT_CODES.iter().copied().collect();
2151        assert!(codeclimate_codes.is_subset(&result_codes));
2152
2153        for meta in result_issue_metas() {
2154            let issue = issue_meta_by_code(meta.code)
2155                .unwrap_or_else(|| panic!("result metadata code {} has no issue row", meta.code));
2156            assert!(issue.has_result_contract());
2157            let sarif_ids = issue.sarif_rule_ids();
2158            assert!(sarif_ids.contains(&format!("fallow/{}", meta.code)));
2159            for rule_id in sarif_ids {
2160                assert!(
2161                    rule_id.starts_with("fallow/"),
2162                    "result metadata code {} has unprefixed SARIF rule id {rule_id}",
2163                    meta.code
2164                );
2165            }
2166            for check_name in issue.codeclimate_check_names() {
2167                assert!(
2168                    check_name.starts_with("fallow/"),
2169                    "result metadata code {} has unprefixed CodeClimate check name {check_name}",
2170                    meta.code
2171                );
2172            }
2173        }
2174    }
2175
2176    #[test]
2177    fn ts_alias_policy_is_explicit() {
2178        let aliases: BTreeSet<(&str, &str)> = ISSUE_TS_ALIAS_META
2179            .iter()
2180            .map(|meta| (meta.alias.name, meta.alias.parent))
2181            .collect();
2182
2183        assert_eq!(
2184            BTreeSet::from([
2185                ("BoundaryViolation", "BoundaryViolationFinding"),
2186                ("CircularDependency", "CircularDependencyFinding"),
2187                ("DeprecatedExportInUse", "DeprecatedExportInUseFinding"),
2188                (
2189                    "DevDependencyInProduction",
2190                    "DevDependencyInProductionFinding",
2191                ),
2192                ("DuplicateExport", "DuplicateExportFinding"),
2193                ("EmptyCatalogGroup", "EmptyCatalogGroupFinding"),
2194                (
2195                    "MisconfiguredDependencyOverride",
2196                    "MisconfiguredDependencyOverrideFinding",
2197                ),
2198                ("PrivateTypeLeak", "PrivateTypeLeakFinding"),
2199                ("PackageCycle", "PackageCycleFinding"),
2200                ("ReExportCycle", "ReExportCycleFinding"),
2201                ("TestOnlyDependency", "TestOnlyDependencyFinding"),
2202                ("TypeOnlyDependency", "TypeOnlyDependencyFinding"),
2203                (
2204                    "UnresolvedCatalogReference",
2205                    "UnresolvedCatalogReferenceFinding",
2206                ),
2207                ("UnresolvedImport", "UnresolvedImportFinding"),
2208                ("UnlistedDependency", "UnlistedDependencyFinding"),
2209                ("UnusedCatalogEntry", "UnusedCatalogEntryFinding"),
2210                ("UnusedDependency", "UnusedDependencyFinding"),
2211                ("UnusedDependency", "UnusedDevDependencyFinding"),
2212                ("UnusedDependency", "UnusedOptionalDependencyFinding"),
2213                (
2214                    "UnusedDependencyOverride",
2215                    "UnusedDependencyOverrideFinding",
2216                ),
2217                ("UnusedExport", "UnusedExportFinding"),
2218                ("UnusedFile", "UnusedFileFinding"),
2219                ("UnusedMember", "UnusedClassMemberFinding"),
2220                ("UnusedMember", "UnusedEnumMemberFinding"),
2221                ("UnusedMember", "UnusedStoreMemberFinding"),
2222            ]),
2223            aliases
2224        );
2225    }
2226
2227    #[test]
2228    fn ts_alias_registry_rows_match_result_metadata() {
2229        let result_codes: BTreeSet<&str> = result_issue_metas().map(|meta| meta.code).collect();
2230        let mut seen_codes = BTreeSet::new();
2231        for meta in ISSUE_TS_ALIAS_META {
2232            assert!(
2233                seen_codes.insert(meta.code),
2234                "duplicate TypeScript alias row for {}",
2235                meta.code
2236            );
2237            assert!(
2238                result_codes.contains(meta.code),
2239                "TypeScript alias row {} has no result metadata",
2240                meta.code
2241            );
2242            assert!(
2243                meta.alias.parent.ends_with("Finding"),
2244                "TypeScript alias row {} must point at a generated Finding wrapper",
2245                meta.code
2246            );
2247            assert_eq!(
2248                Some(meta.alias),
2249                issue_meta_by_code(meta.code).and_then(|issue| issue.ts_alias()),
2250                "IssueKindMeta helper must resolve TypeScript alias row {} through the registry",
2251                meta.code
2252            );
2253        }
2254    }
2255
2256    #[test]
2257    fn result_keys_are_unique() {
2258        let mut seen = BTreeSet::new();
2259        for meta in ISSUE_RESULT_META {
2260            assert!(
2261                seen.insert(meta.result_key),
2262                "duplicate result key {}",
2263                meta.result_key
2264            );
2265        }
2266    }
2267
2268    #[test]
2269    fn counted_result_keys_match_total_issue_fields() {
2270        let from_total: BTreeSet<&str> = TOTAL_ISSUE_RESULT_KEYS.iter().copied().collect();
2271        let from_meta: BTreeSet<&str> = counted_result_issue_metas()
2272            .map(|meta| meta.result_key)
2273            .collect();
2274        assert_eq!(from_total, from_meta);
2275    }
2276
2277    #[test]
2278    fn advisory_result_keys_are_explicitly_excluded_from_total() {
2279        let expected = BTreeSet::from(["duplicate_prop_shapes", "thin_wrappers"]);
2280        let from_meta: BTreeSet<&str> = result_issue_metas()
2281            .filter(|meta| !meta.counts_in_total)
2282            .map(|meta| meta.result_key)
2283            .collect();
2284        assert_eq!(expected, from_meta);
2285    }
2286}