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