1use std::path::PathBuf;
4use std::time::Duration;
5
6use fallow_config::ResolvedConfig;
7use fallow_output::{HealthGrouping, HealthReport, HealthTimings};
8use fallow_types::discover::DiscoveredFile;
9use fallow_types::extract::ModuleInfo;
10use fallow_types::workspace::WorkspaceDiagnostic;
11use rustc_hash::{FxHashMap, FxHashSet};
12
13use crate::{duplicates, module_graph, trace};
14
15pub use fallow_types::output_dead_code::{
16 BoundaryCallViolationFinding, BoundaryCoverageViolationFinding, BoundaryViolationFinding,
17 CircularDependencyFinding, DuplicateExportFinding, DuplicatePropShapeFinding,
18 DynamicSegmentNameConflictFinding, EmptyCatalogGroupFinding, InvalidClientExportFinding,
19 MisconfiguredDependencyOverrideFinding, MisplacedDirectiveFinding,
20 MixedClientServerBarrelFinding, PolicyViolationFinding, PrivateTypeLeakFinding,
21 PropDrillingChainFinding, ReExportCycleFinding, RouteCollisionFinding,
22 TestOnlyDependencyFinding, ThinWrapperFinding, TypeOnlyDependencyFinding,
23 UnlistedDependencyFinding, UnprovidedInjectFinding, UnrenderedComponentFinding,
24 UnresolvedCatalogReferenceFinding, UnresolvedImportFinding, UnusedCatalogEntryFinding,
25 UnusedClassMemberFinding, UnusedComponentEmitFinding, UnusedComponentInputFinding,
26 UnusedComponentOutputFinding, UnusedComponentPropFinding, UnusedDependencyFinding,
27 UnusedDependencyOverrideFinding, UnusedDevDependencyFinding, UnusedEnumMemberFinding,
28 UnusedExportFinding, UnusedFileFinding, UnusedLoadDataKeyFinding,
29 UnusedOptionalDependencyFinding, UnusedServerActionFinding, UnusedStoreMemberFinding,
30 UnusedSvelteEventFinding, UnusedTypeFinding,
31};
32pub use fallow_types::results::{
33 ActiveSuppression, AnalysisResults, BoundaryCallViolation, BoundaryCoverageViolation,
34 BoundaryViolation, CircularDependency, CircularDependencyEdge, DependencyLocation,
35 DependencyOverrideMisconfigReason, DependencyOverrideSource, DuplicateExport,
36 DuplicateLocation, DuplicatePropShape, DuplicatePropShapeMember, DynamicSegmentNameConflict,
37 EmptyCatalogGroup, EntryPointSummary, ExportUsage, FeatureFlag, FlagConfidence, FlagKind,
38 ImportSite, InvalidClientExport, MisconfiguredDependencyOverride, MisplacedDirective,
39 MixedClientServerBarrel, PolicyRuleKind, PolicyViolation, PolicyViolationSeverity,
40 PrivateTypeLeak, PropDrillHop, PropDrillingChain, ReExportCycle, ReExportCycleKind,
41 ReactComponentIntel, ReactHookSummary, ReactPropDrill, ReactPropIntel, ReferenceLocation,
42 RenderFanInComponent, RenderFanInMetric, RouteCollision, SecurityAttackSurfaceEntry,
43 SecurityCandidate, SecurityCandidateBoundary, SecurityCandidateSink, SecurityDeadCodeContext,
44 SecurityDeadCodeKind, SecurityDefensiveBoundary, SecurityDefensiveControl, SecurityFinding,
45 SecurityFindingKind, SecurityNetworkContext, SecurityReachability, SecurityRuntimeContext,
46 SecurityRuntimeState, SecuritySeverity, SecurityTaintFlow, SecurityUnresolvedCalleeDiagnostic,
47 SecurityZoneCrossing, StaleSuppression, SuppressionOrigin, TaintConfidence, TaintEndpoint,
48 TaintPath, TestOnlyDependency, ThinWrapper, TraceHop, TraceHopRole, TypeOnlyDependency,
49 UnlistedDependency, UnprovidedInject, UnrenderedComponent, UnresolvedCatalogReference,
50 UnresolvedImport, UnusedCatalogEntry, UnusedComponentEmit, UnusedComponentInput,
51 UnusedComponentOutput, UnusedComponentProp, UnusedDependency, UnusedDependencyOverride,
52 UnusedExport, UnusedFile, UnusedLoadDataKey, UnusedMember, UnusedServerAction,
53 UnusedSvelteEvent,
54};
55
56#[derive(Debug)]
58pub struct DeadCodeAnalysis {
59 pub results: AnalysisResults,
60}
61
62#[derive(Debug)]
64pub struct DeadCodeAnalysisWithHashes {
65 pub results: AnalysisResults,
66 pub file_hashes: FxHashMap<PathBuf, u64>,
67}
68
69#[derive(Debug)]
71pub struct DeadCodeAnalysisOutput {
72 pub results: AnalysisResults,
73 pub modules: Option<Vec<ModuleInfo>>,
74 pub files: Option<Vec<DiscoveredFile>>,
75}
76
77#[derive(Debug)]
79pub struct DeadCodeAnalysisArtifacts {
80 pub results: AnalysisResults,
81 pub timings: Option<trace::PipelineTimings>,
82 pub graph: Option<module_graph::RetainedModuleGraph>,
83 pub modules: Option<Vec<ModuleInfo>>,
84 pub files: Option<Vec<DiscoveredFile>>,
85 pub script_used_packages: FxHashSet<String>,
86 pub file_hashes: FxHashMap<PathBuf, u64>,
87}
88
89#[derive(Debug)]
91pub struct ProjectAnalysisOutput {
92 pub dead_code: DeadCodeAnalysisOutput,
93 pub duplication: duplicates::DuplicationReport,
94}
95
96#[derive(Debug)]
98pub struct DuplicationAnalysis {
99 pub report: duplicates::DuplicationReport,
100 pub default_ignore_skips: duplicates::DefaultIgnoreSkips,
101}
102
103#[derive(Debug)]
108pub struct HealthAnalysisResult<GroupResolver = ()> {
109 pub report: HealthReport,
110 pub grouping: Option<HealthGrouping>,
116 pub group_resolver: Option<GroupResolver>,
119 pub config: ResolvedConfig,
120 pub workspace_diagnostics: Vec<WorkspaceDiagnostic>,
121 pub elapsed: Duration,
122 pub timings: Option<HealthTimings>,
123 pub coverage_gaps_has_findings: bool,
124 pub should_fail_on_coverage_gaps: bool,
125}
126
127impl<GroupResolver> HealthAnalysisResult<GroupResolver> {
128 #[must_use]
131 pub fn without_group_resolver(self) -> HealthAnalysisResult<()> {
132 HealthAnalysisResult {
133 report: self.report,
134 grouping: self.grouping,
135 group_resolver: None,
136 config: self.config,
137 workspace_diagnostics: self.workspace_diagnostics,
138 elapsed: self.elapsed,
139 timings: self.timings,
140 coverage_gaps_has_findings: self.coverage_gaps_has_findings,
141 should_fail_on_coverage_gaps: self.should_fail_on_coverage_gaps,
142 }
143 }
144}
145
146#[cfg(test)]
147mod tests {
148 use fallow_config::ProductionAnalysis;
149 use fallow_types::output_format::OutputFormat;
150
151 use super::*;
152
153 #[test]
154 fn health_analysis_result_drops_presentation_resolver() {
155 let project = tempfile::tempdir().expect("temp dir");
156 let project_config = crate::config_for_project_analysis(
157 project.path(),
158 None,
159 crate::ProjectConfigOptions {
160 output: OutputFormat::Json,
161 no_cache: true,
162 threads: 1,
163 production_override: None,
164 quiet: true,
165 analysis: ProductionAnalysis::Health,
166 },
167 )
168 .expect("project config loads");
169 let result = HealthAnalysisResult {
170 report: HealthReport::default(),
171 grouping: None,
172 group_resolver: Some("resolver"),
173 config: project_config.config,
174 workspace_diagnostics: Vec::new(),
175 elapsed: Duration::from_millis(7),
176 timings: None,
177 coverage_gaps_has_findings: true,
178 should_fail_on_coverage_gaps: true,
179 };
180
181 let neutral = result.without_group_resolver();
182
183 assert!(neutral.group_resolver.is_none());
184 assert_eq!(neutral.elapsed, Duration::from_millis(7));
185 assert!(neutral.coverage_gaps_has_findings);
186 assert!(neutral.should_fail_on_coverage_gaps);
187 }
188
189 #[test]
190 fn engine_result_surface_uses_explicit_reexports() {
191 let source = include_str!("results.rs");
192 let output_dead_code_wildcard = concat!("pub use fallow_types::output_dead_code::", "*");
193 let results_wildcard = concat!("pub use fallow_types::results::", "*");
194
195 assert!(!source.contains(output_dead_code_wildcard));
196 assert!(!source.contains(results_wildcard));
197 }
198}