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::sync::Arc;
10use std::time::Duration;
11
12use fallow_config::ResolvedConfig;
13use fallow_output::{HealthGrouping, HealthReport, HealthTimings};
14use fallow_types::discover::DiscoveredFile;
15use fallow_types::extract::ModuleInfo;
16use fallow_types::source_fingerprint::SourceFingerprint;
17use fallow_types::workspace::WorkspaceDiagnostic;
18use rustc_hash::{FxHashMap, FxHashSet};
19
20use crate::{duplicates, module_graph, trace};
21
22pub use crate::security::{
23 derive_security_severity, enable_security_rules, resolve_security_finding_severity,
24 security_catalogue_title, security_rule_id, security_rules_can_error,
25};
26pub use fallow_types::output_dead_code::{
27 AbsentComponentPropFinding, BoundaryCallViolationFinding, BoundaryCoverageViolationFinding,
28 BoundaryViolationFinding, CircularDependencyFinding, DuplicateExportFinding,
29 DuplicatePropShapeFinding, DynamicSegmentNameConflictFinding, EmptyCatalogGroupFinding,
30 InvalidClientExportFinding, MisconfiguredDependencyOverrideFinding, MisplacedDirectiveFinding,
31 MixedClientServerBarrelFinding, PackageCycleFinding, PolicyViolationFinding,
32 PrivateTypeLeakFinding, PropDrillingChainFinding, ReExportCycleFinding, RouteCollisionFinding,
33 TestOnlyDependencyFinding, ThinWrapperFinding, TypeOnlyDependencyFinding,
34 UnlistedDependencyFinding, UnprovidedInjectFinding, UnrenderedComponentFinding,
35 UnresolvedCatalogReferenceFinding, UnresolvedImportFinding, UnusedCatalogEntryFinding,
36 UnusedClassMemberFinding, UnusedComponentEmitFinding, UnusedComponentInputFinding,
37 UnusedComponentOutputFinding, UnusedComponentPropFinding, UnusedDependencyFinding,
38 UnusedDependencyOverrideFinding, UnusedDevDependencyFinding, UnusedEnumMemberFinding,
39 UnusedExportFinding, UnusedFileFinding, UnusedLoadDataKeyFinding,
40 UnusedOptionalDependencyFinding, UnusedServerActionFinding, UnusedStoreMemberFinding,
41 UnusedSvelteEventFinding, UnusedTypeFinding,
42};
43pub use fallow_types::output_dead_code::{
44 UnproducedCatchTagFinding, UnusedEffectLayerFinding, UnusedTaggedErrorFinding,
45};
46pub use fallow_types::results::{
47 AbsentComponentProp, ActiveSuppression, AnalysisResults, BoundaryCallViolation,
48 BoundaryCoverageViolation, BoundaryViolation, CircularDependency, CircularDependencyEdge,
49 ComponentPropCallSite, DependencyLocation, DependencyOverrideMisconfigReason,
50 DependencyOverrideSource, DuplicateExport, DuplicateLocation, DuplicatePropShape,
51 DuplicatePropShapeMember, DynamicSegmentNameConflict, EmptyCatalogGroup, EntryPointSummary,
52 ExportUsage, FeatureFlag, FlagConfidence, FlagKind, ImportSite, InvalidClientExport,
53 MisconfiguredDependencyOverride, MisplacedDirective, MixedClientServerBarrel, PackageCycle,
54 PackageCycleEdge, PolicyRuleKind, PolicyViolation, PolicyViolationSeverity, PrivateTypeLeak,
55 PropDrillHop, PropDrillingChain, ReExportCycle, ReExportCycleKind, ReactComponentIntel,
56 ReactHookSummary, ReactPropDrill, ReactPropIntel, ReferenceLocation, RenderFanInComponent,
57 RenderFanInMetric, RouteCollision, SecurityAttackSurfaceEntry, SecurityCandidate,
58 SecurityCandidateBoundary, SecurityCandidateSink, SecurityDeadCodeContext,
59 SecurityDeadCodeKind, SecurityDefensiveBoundary, SecurityDefensiveControl, SecurityFinding,
60 SecurityFindingKind, SecurityNetworkContext, SecurityReachability, SecurityRuntimeContext,
61 SecurityRuntimeState, SecuritySeverity, SecurityTaintFlow, SecurityUnresolvedCalleeDiagnostic,
62 SecurityZoneCrossing, StaleSuppression, SuppressionOrigin, TaintConfidence, TaintEndpoint,
63 TaintPath, TestOnlyDependency, ThinWrapper, TraceHop, TraceHopRole, TypeOnlyDependency,
64 UnlistedDependency, UnprovidedInject, UnrenderedComponent, UnresolvedCatalogReference,
65 UnresolvedImport, UnusedCatalogEntry, UnusedComponentEmit, UnusedComponentInput,
66 UnusedComponentOutput, UnusedComponentProp, UnusedDependency, UnusedDependencyOverride,
67 UnusedExport, UnusedFile, UnusedLoadDataKey, UnusedMember, UnusedServerAction,
68 UnusedSvelteEvent,
69};
70pub use fallow_types::results::{
71 EffectCatchSite, EffectLayerKind, UnproducedCatchTag, UnusedEffectLayer, UnusedTaggedError,
72};
73
74#[derive(Debug)]
76pub struct DeadCodeAnalysis {
77 pub results: AnalysisResults,
79}
80
81#[derive(Debug)]
83pub struct DeadCodeAnalysisWithHashes {
84 pub results: AnalysisResults,
86 pub file_hashes: FxHashMap<PathBuf, u64>,
88}
89
90#[derive(Debug)]
92pub struct DeadCodeAnalysisOutput {
93 pub results: AnalysisResults,
95 pub modules: Option<Vec<ModuleInfo>>,
97 pub files: Option<Vec<DiscoveredFile>>,
99}
100
101#[derive(Debug)]
103pub struct DeadCodeAnalysisArtifacts {
104 pub results: AnalysisResults,
106 pub timings: Option<trace::PipelineTimings>,
108 pub graph: Option<module_graph::RetainedModuleGraph>,
110 pub modules: Option<Vec<ModuleInfo>>,
112 pub files: Option<Vec<DiscoveredFile>>,
114 pub script_used_packages: FxHashSet<String>,
117 pub trace_provenance: trace::TraceProvenance,
120 pub file_hashes: FxHashMap<PathBuf, u64>,
122}
123
124#[doc(hidden)]
130#[derive(Debug)]
131pub struct SharedDeadCodeAnalysisArtifacts {
132 pub results: AnalysisResults,
133 pub timings: Option<trace::PipelineTimings>,
134 pub graph: Option<module_graph::RetainedModuleGraph>,
135 pub modules: Option<Arc<[ModuleInfo]>>,
136 pub files: Option<Vec<DiscoveredFile>>,
137 pub script_used_packages: FxHashSet<String>,
138 pub trace_provenance: trace::TraceProvenance,
139 pub file_hashes: FxHashMap<PathBuf, u64>,
140}
141
142impl SharedDeadCodeAnalysisArtifacts {
143 #[must_use]
145 pub fn into_owned(self) -> DeadCodeAnalysisArtifacts {
146 let modules = self.modules.map(|modules| {
147 let mut owned = modules.to_vec();
148 for module in &mut owned {
149 module.release_resolution_payload();
150 }
151 owned
152 });
153 DeadCodeAnalysisArtifacts {
154 results: self.results,
155 timings: self.timings,
156 graph: self.graph,
157 modules,
158 files: self.files,
159 script_used_packages: self.script_used_packages,
160 trace_provenance: self.trace_provenance,
161 file_hashes: self.file_hashes,
162 }
163 }
164}
165
166#[derive(Debug)]
168pub struct ProjectAnalysisOutput {
169 pub dead_code: DeadCodeAnalysisOutput,
171 pub duplication: duplicates::DuplicationReport,
173}
174
175#[derive(Debug)]
177pub struct ProjectAnalysisArtifacts {
178 pub dead_code: DeadCodeAnalysisArtifacts,
180 pub duplication: duplicates::DuplicationReport,
182 pub changed_files: Option<FxHashSet<PathBuf>>,
184 pub source_fingerprints: Option<FxHashMap<PathBuf, SourceFingerprint>>,
186}
187
188impl ProjectAnalysisArtifacts {
189 #[must_use]
191 pub fn into_output(self) -> ProjectAnalysisOutput {
192 ProjectAnalysisOutput {
193 dead_code: DeadCodeAnalysisOutput {
194 results: self.dead_code.results,
195 modules: self.dead_code.modules,
196 files: self.dead_code.files,
197 },
198 duplication: self.duplication,
199 }
200 }
201}
202
203#[derive(Debug)]
205pub struct DuplicationAnalysis {
206 pub report: duplicates::DuplicationReport,
207 pub default_ignore_skips: duplicates::DefaultIgnoreSkips,
208}
209
210#[derive(Debug)]
215pub struct HealthAnalysisResult<GroupResolver = ()> {
216 pub report: HealthReport,
218 pub type_aware_meta: Option<fallow_types::envelope::TypeAwareMeta>,
220 pub branching_by_file: crate::health::BranchingByFile,
227 pub grouping: Option<HealthGrouping>,
233 pub group_resolver: Option<GroupResolver>,
236 pub config: ResolvedConfig,
238 pub workspace_diagnostics: Vec<WorkspaceDiagnostic>,
240 pub elapsed: Duration,
242 pub timings: Option<HealthTimings>,
244 pub coverage_gaps_has_findings: bool,
246 pub should_fail_on_coverage_gaps: bool,
249 pub changed_files_analyzed: Option<Vec<std::path::PathBuf>>,
254}
255
256impl<GroupResolver> HealthAnalysisResult<GroupResolver> {
257 #[must_use]
260 pub fn without_group_resolver(self) -> HealthAnalysisResult<()> {
261 HealthAnalysisResult {
262 report: self.report,
263 type_aware_meta: self.type_aware_meta,
264 branching_by_file: self.branching_by_file,
265 grouping: self.grouping,
266 group_resolver: None,
267 config: self.config,
268 workspace_diagnostics: self.workspace_diagnostics,
269 elapsed: self.elapsed,
270 timings: self.timings,
271 coverage_gaps_has_findings: self.coverage_gaps_has_findings,
272 should_fail_on_coverage_gaps: self.should_fail_on_coverage_gaps,
273 changed_files_analyzed: self.changed_files_analyzed,
274 }
275 }
276}
277
278#[cfg(test)]
279mod tests {
280 use crate::project_config::{ProjectConfigOptions, config_for_project_analysis};
281 use fallow_config::ProductionAnalysis;
282 use fallow_types::output_format::OutputFormat;
283
284 use super::*;
285
286 #[test]
287 fn health_analysis_result_drops_presentation_resolver() {
288 let project = tempfile::tempdir().expect("temp dir");
289 let project_config = config_for_project_analysis(
290 project.path(),
291 None,
292 ProjectConfigOptions {
293 output: OutputFormat::Json,
294 no_cache: true,
295 threads: 1,
296 production_override: None,
297 quiet: true,
298 analysis: ProductionAnalysis::Health,
299 allow_remote_extends: false,
300 },
301 )
302 .expect("project config loads");
303 let result = HealthAnalysisResult {
304 report: HealthReport::default(),
305 branching_by_file: crate::health::BranchingByFile::default(),
306 grouping: None,
307 group_resolver: Some("resolver"),
308 config: project_config.config,
309 workspace_diagnostics: Vec::new(),
310 elapsed: Duration::from_millis(7),
311 timings: None,
312 coverage_gaps_has_findings: true,
313 should_fail_on_coverage_gaps: true,
314 type_aware_meta: None,
315 changed_files_analyzed: None,
316 };
317
318 let neutral = result.without_group_resolver();
319
320 assert!(neutral.group_resolver.is_none());
321 assert_eq!(neutral.elapsed, Duration::from_millis(7));
322 assert!(neutral.coverage_gaps_has_findings);
323 assert!(neutral.should_fail_on_coverage_gaps);
324 }
325
326 #[test]
327 fn engine_result_surface_uses_explicit_reexports() {
328 let source = include_str!("results.rs");
329 let output_dead_code_wildcard = concat!("pub use fallow_types::output_dead_code::", "*");
330 let results_wildcard = concat!("pub use fallow_types::results::", "*");
331
332 assert!(!source.contains(output_dead_code_wildcard));
333 assert!(!source.contains(results_wildcard));
334 }
335}