Skip to main content

fallow_types/
issue_meta.rs

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