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, security_catalogue_title, security_finding_id,
24 security_rule_id,
25};
26pub use fallow_types::output_dead_code::{
27 BoundaryCallViolationFinding, BoundaryCoverageViolationFinding, BoundaryViolationFinding,
28 CircularDependencyFinding, DuplicateExportFinding, DuplicatePropShapeFinding,
29 DynamicSegmentNameConflictFinding, EmptyCatalogGroupFinding, InvalidClientExportFinding,
30 MisconfiguredDependencyOverrideFinding, MisplacedDirectiveFinding,
31 MixedClientServerBarrelFinding, PolicyViolationFinding, PrivateTypeLeakFinding,
32 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::results::{
44 ActiveSuppression, AnalysisResults, BoundaryCallViolation, BoundaryCoverageViolation,
45 BoundaryViolation, CircularDependency, CircularDependencyEdge, DependencyLocation,
46 DependencyOverrideMisconfigReason, DependencyOverrideSource, DuplicateExport,
47 DuplicateLocation, DuplicatePropShape, DuplicatePropShapeMember, DynamicSegmentNameConflict,
48 EmptyCatalogGroup, EntryPointSummary, ExportUsage, FeatureFlag, FlagConfidence, FlagKind,
49 ImportSite, InvalidClientExport, MisconfiguredDependencyOverride, MisplacedDirective,
50 MixedClientServerBarrel, PolicyRuleKind, PolicyViolation, PolicyViolationSeverity,
51 PrivateTypeLeak, PropDrillHop, PropDrillingChain, ReExportCycle, ReExportCycleKind,
52 ReactComponentIntel, ReactHookSummary, ReactPropDrill, ReactPropIntel, ReferenceLocation,
53 RenderFanInComponent, RenderFanInMetric, RouteCollision, SecurityAttackSurfaceEntry,
54 SecurityCandidate, SecurityCandidateBoundary, SecurityCandidateSink, SecurityDeadCodeContext,
55 SecurityDeadCodeKind, SecurityDefensiveBoundary, SecurityDefensiveControl, SecurityFinding,
56 SecurityFindingKind, SecurityNetworkContext, SecurityReachability, SecurityRuntimeContext,
57 SecurityRuntimeState, SecuritySeverity, SecurityTaintFlow, SecurityUnresolvedCalleeDiagnostic,
58 SecurityZoneCrossing, StaleSuppression, SuppressionOrigin, TaintConfidence, TaintEndpoint,
59 TaintPath, TestOnlyDependency, ThinWrapper, TraceHop, TraceHopRole, TypeOnlyDependency,
60 UnlistedDependency, UnprovidedInject, UnrenderedComponent, UnresolvedCatalogReference,
61 UnresolvedImport, UnusedCatalogEntry, UnusedComponentEmit, UnusedComponentInput,
62 UnusedComponentOutput, UnusedComponentProp, UnusedDependency, UnusedDependencyOverride,
63 UnusedExport, UnusedFile, UnusedLoadDataKey, UnusedMember, UnusedServerAction,
64 UnusedSvelteEvent,
65};
66
67#[derive(Debug)]
69pub struct DeadCodeAnalysis {
70 pub results: AnalysisResults,
72}
73
74#[derive(Debug)]
76pub struct DeadCodeAnalysisWithHashes {
77 pub results: AnalysisResults,
79 pub file_hashes: FxHashMap<PathBuf, u64>,
81}
82
83#[derive(Debug)]
85pub struct DeadCodeAnalysisOutput {
86 pub results: AnalysisResults,
88 pub modules: Option<Vec<ModuleInfo>>,
90 pub files: Option<Vec<DiscoveredFile>>,
92}
93
94#[derive(Debug)]
96pub struct DeadCodeAnalysisArtifacts {
97 pub results: AnalysisResults,
99 pub timings: Option<trace::PipelineTimings>,
101 pub graph: Option<module_graph::RetainedModuleGraph>,
103 pub modules: Option<Vec<ModuleInfo>>,
105 pub files: Option<Vec<DiscoveredFile>>,
107 pub script_used_packages: FxHashSet<String>,
110 pub file_hashes: FxHashMap<PathBuf, u64>,
112}
113
114#[doc(hidden)]
120#[derive(Debug)]
121pub struct SharedDeadCodeAnalysisArtifacts {
122 pub results: AnalysisResults,
123 pub timings: Option<trace::PipelineTimings>,
124 pub graph: Option<module_graph::RetainedModuleGraph>,
125 pub modules: Option<Arc<[ModuleInfo]>>,
126 pub files: Option<Vec<DiscoveredFile>>,
127 pub script_used_packages: FxHashSet<String>,
128 pub file_hashes: FxHashMap<PathBuf, u64>,
129}
130
131impl SharedDeadCodeAnalysisArtifacts {
132 #[must_use]
134 pub fn into_owned(self) -> DeadCodeAnalysisArtifacts {
135 let modules = self.modules.map(|modules| {
136 let mut owned = modules.to_vec();
137 for module in &mut owned {
138 module.release_resolution_payload();
139 }
140 owned
141 });
142 DeadCodeAnalysisArtifacts {
143 results: self.results,
144 timings: self.timings,
145 graph: self.graph,
146 modules,
147 files: self.files,
148 script_used_packages: self.script_used_packages,
149 file_hashes: self.file_hashes,
150 }
151 }
152}
153
154#[derive(Debug)]
156pub struct ProjectAnalysisOutput {
157 pub dead_code: DeadCodeAnalysisOutput,
159 pub duplication: duplicates::DuplicationReport,
161}
162
163#[derive(Debug)]
165pub struct ProjectAnalysisArtifacts {
166 pub dead_code: DeadCodeAnalysisArtifacts,
168 pub duplication: duplicates::DuplicationReport,
170 pub changed_files: Option<FxHashSet<PathBuf>>,
172 pub source_fingerprints: Option<FxHashMap<PathBuf, SourceFingerprint>>,
174}
175
176impl ProjectAnalysisArtifacts {
177 #[must_use]
179 pub fn into_output(self) -> ProjectAnalysisOutput {
180 ProjectAnalysisOutput {
181 dead_code: DeadCodeAnalysisOutput {
182 results: self.dead_code.results,
183 modules: self.dead_code.modules,
184 files: self.dead_code.files,
185 },
186 duplication: self.duplication,
187 }
188 }
189}
190
191#[derive(Debug)]
193pub struct DuplicationAnalysis {
194 pub report: duplicates::DuplicationReport,
195 pub default_ignore_skips: duplicates::DefaultIgnoreSkips,
196}
197
198#[derive(Debug)]
203pub struct HealthAnalysisResult<GroupResolver = ()> {
204 pub report: HealthReport,
206 pub type_aware_meta: Option<fallow_types::envelope::TypeAwareMeta>,
208 pub branching_by_file: crate::health::BranchingByFile,
215 pub grouping: Option<HealthGrouping>,
221 pub group_resolver: Option<GroupResolver>,
224 pub config: ResolvedConfig,
226 pub workspace_diagnostics: Vec<WorkspaceDiagnostic>,
228 pub elapsed: Duration,
230 pub timings: Option<HealthTimings>,
232 pub coverage_gaps_has_findings: bool,
234 pub should_fail_on_coverage_gaps: bool,
237}
238
239impl<GroupResolver> HealthAnalysisResult<GroupResolver> {
240 #[must_use]
243 pub fn without_group_resolver(self) -> HealthAnalysisResult<()> {
244 HealthAnalysisResult {
245 report: self.report,
246 type_aware_meta: self.type_aware_meta,
247 branching_by_file: self.branching_by_file,
248 grouping: self.grouping,
249 group_resolver: None,
250 config: self.config,
251 workspace_diagnostics: self.workspace_diagnostics,
252 elapsed: self.elapsed,
253 timings: self.timings,
254 coverage_gaps_has_findings: self.coverage_gaps_has_findings,
255 should_fail_on_coverage_gaps: self.should_fail_on_coverage_gaps,
256 }
257 }
258}
259
260#[cfg(test)]
261mod tests {
262 use crate::project_config::{ProjectConfigOptions, config_for_project_analysis};
263 use fallow_config::ProductionAnalysis;
264 use fallow_types::output_format::OutputFormat;
265
266 use super::*;
267
268 #[test]
269 fn health_analysis_result_drops_presentation_resolver() {
270 let project = tempfile::tempdir().expect("temp dir");
271 let project_config = config_for_project_analysis(
272 project.path(),
273 None,
274 ProjectConfigOptions {
275 output: OutputFormat::Json,
276 no_cache: true,
277 threads: 1,
278 production_override: None,
279 quiet: true,
280 analysis: ProductionAnalysis::Health,
281 allow_remote_extends: false,
282 },
283 )
284 .expect("project config loads");
285 let result = HealthAnalysisResult {
286 report: HealthReport::default(),
287 branching_by_file: crate::health::BranchingByFile::default(),
288 grouping: None,
289 group_resolver: Some("resolver"),
290 config: project_config.config,
291 workspace_diagnostics: Vec::new(),
292 elapsed: Duration::from_millis(7),
293 timings: None,
294 coverage_gaps_has_findings: true,
295 should_fail_on_coverage_gaps: true,
296 type_aware_meta: None,
297 };
298
299 let neutral = result.without_group_resolver();
300
301 assert!(neutral.group_resolver.is_none());
302 assert_eq!(neutral.elapsed, Duration::from_millis(7));
303 assert!(neutral.coverage_gaps_has_findings);
304 assert!(neutral.should_fail_on_coverage_gaps);
305 }
306
307 #[test]
308 fn engine_result_surface_uses_explicit_reexports() {
309 let source = include_str!("results.rs");
310 let output_dead_code_wildcard = concat!("pub use fallow_types::output_dead_code::", "*");
311 let results_wildcard = concat!("pub use fallow_types::results::", "*");
312
313 assert!(!source.contains(output_dead_code_wildcard));
314 assert!(!source.contains(results_wildcard));
315 }
316}