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