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, 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::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, PackageCycle, PackageCycleEdge, PolicyRuleKind, PolicyViolation,
51 PolicyViolationSeverity, PrivateTypeLeak, PropDrillHop, PropDrillingChain, ReExportCycle,
52 ReExportCycleKind, ReactComponentIntel, ReactHookSummary, ReactPropDrill, ReactPropIntel,
53 ReferenceLocation, RenderFanInComponent, RenderFanInMetric, RouteCollision,
54 SecurityAttackSurfaceEntry, SecurityCandidate, SecurityCandidateBoundary,
55 SecurityCandidateSink, SecurityDeadCodeContext, SecurityDeadCodeKind,
56 SecurityDefensiveBoundary, SecurityDefensiveControl, SecurityFinding, SecurityFindingKind,
57 SecurityNetworkContext, SecurityReachability, SecurityRuntimeContext, SecurityRuntimeState,
58 SecuritySeverity, SecurityTaintFlow, SecurityUnresolvedCalleeDiagnostic, SecurityZoneCrossing,
59 StaleSuppression, SuppressionOrigin, TaintConfidence, TaintEndpoint, TaintPath,
60 TestOnlyDependency, ThinWrapper, TraceHop, TraceHopRole, TypeOnlyDependency,
61 UnlistedDependency, UnprovidedInject, UnrenderedComponent, UnresolvedCatalogReference,
62 UnresolvedImport, UnusedCatalogEntry, UnusedComponentEmit, UnusedComponentInput,
63 UnusedComponentOutput, UnusedComponentProp, UnusedDependency, UnusedDependencyOverride,
64 UnusedExport, UnusedFile, UnusedLoadDataKey, UnusedMember, UnusedServerAction,
65 UnusedSvelteEvent,
66};
67
68#[derive(Debug)]
70pub struct DeadCodeAnalysis {
71 pub results: AnalysisResults,
73}
74
75#[derive(Debug)]
77pub struct DeadCodeAnalysisWithHashes {
78 pub results: AnalysisResults,
80 pub file_hashes: FxHashMap<PathBuf, u64>,
82}
83
84#[derive(Debug)]
86pub struct DeadCodeAnalysisOutput {
87 pub results: AnalysisResults,
89 pub modules: Option<Vec<ModuleInfo>>,
91 pub files: Option<Vec<DiscoveredFile>>,
93}
94
95#[derive(Debug)]
97pub struct DeadCodeAnalysisArtifacts {
98 pub results: AnalysisResults,
100 pub timings: Option<trace::PipelineTimings>,
102 pub graph: Option<module_graph::RetainedModuleGraph>,
104 pub modules: Option<Vec<ModuleInfo>>,
106 pub files: Option<Vec<DiscoveredFile>>,
108 pub script_used_packages: FxHashSet<String>,
111 pub trace_provenance: trace::TraceProvenance,
114 pub file_hashes: FxHashMap<PathBuf, u64>,
116}
117
118#[doc(hidden)]
124#[derive(Debug)]
125pub struct SharedDeadCodeAnalysisArtifacts {
126 pub results: AnalysisResults,
127 pub timings: Option<trace::PipelineTimings>,
128 pub graph: Option<module_graph::RetainedModuleGraph>,
129 pub modules: Option<Arc<[ModuleInfo]>>,
130 pub files: Option<Vec<DiscoveredFile>>,
131 pub script_used_packages: FxHashSet<String>,
132 pub trace_provenance: trace::TraceProvenance,
133 pub file_hashes: FxHashMap<PathBuf, u64>,
134}
135
136impl SharedDeadCodeAnalysisArtifacts {
137 #[must_use]
139 pub fn into_owned(self) -> DeadCodeAnalysisArtifacts {
140 let modules = self.modules.map(|modules| {
141 let mut owned = modules.to_vec();
142 for module in &mut owned {
143 module.release_resolution_payload();
144 }
145 owned
146 });
147 DeadCodeAnalysisArtifacts {
148 results: self.results,
149 timings: self.timings,
150 graph: self.graph,
151 modules,
152 files: self.files,
153 script_used_packages: self.script_used_packages,
154 trace_provenance: self.trace_provenance,
155 file_hashes: self.file_hashes,
156 }
157 }
158}
159
160#[derive(Debug)]
162pub struct ProjectAnalysisOutput {
163 pub dead_code: DeadCodeAnalysisOutput,
165 pub duplication: duplicates::DuplicationReport,
167}
168
169#[derive(Debug)]
171pub struct ProjectAnalysisArtifacts {
172 pub dead_code: DeadCodeAnalysisArtifacts,
174 pub duplication: duplicates::DuplicationReport,
176 pub changed_files: Option<FxHashSet<PathBuf>>,
178 pub source_fingerprints: Option<FxHashMap<PathBuf, SourceFingerprint>>,
180}
181
182impl ProjectAnalysisArtifacts {
183 #[must_use]
185 pub fn into_output(self) -> ProjectAnalysisOutput {
186 ProjectAnalysisOutput {
187 dead_code: DeadCodeAnalysisOutput {
188 results: self.dead_code.results,
189 modules: self.dead_code.modules,
190 files: self.dead_code.files,
191 },
192 duplication: self.duplication,
193 }
194 }
195}
196
197#[derive(Debug)]
199pub struct DuplicationAnalysis {
200 pub report: duplicates::DuplicationReport,
201 pub default_ignore_skips: duplicates::DefaultIgnoreSkips,
202}
203
204#[derive(Debug)]
209pub struct HealthAnalysisResult<GroupResolver = ()> {
210 pub report: HealthReport,
212 pub type_aware_meta: Option<fallow_types::envelope::TypeAwareMeta>,
214 pub branching_by_file: crate::health::BranchingByFile,
221 pub grouping: Option<HealthGrouping>,
227 pub group_resolver: Option<GroupResolver>,
230 pub config: ResolvedConfig,
232 pub workspace_diagnostics: Vec<WorkspaceDiagnostic>,
234 pub elapsed: Duration,
236 pub timings: Option<HealthTimings>,
238 pub coverage_gaps_has_findings: bool,
240 pub should_fail_on_coverage_gaps: bool,
243 pub changed_files_analyzed: Option<Vec<std::path::PathBuf>>,
248}
249
250impl<GroupResolver> HealthAnalysisResult<GroupResolver> {
251 #[must_use]
254 pub fn without_group_resolver(self) -> HealthAnalysisResult<()> {
255 HealthAnalysisResult {
256 report: self.report,
257 type_aware_meta: self.type_aware_meta,
258 branching_by_file: self.branching_by_file,
259 grouping: self.grouping,
260 group_resolver: None,
261 config: self.config,
262 workspace_diagnostics: self.workspace_diagnostics,
263 elapsed: self.elapsed,
264 timings: self.timings,
265 coverage_gaps_has_findings: self.coverage_gaps_has_findings,
266 should_fail_on_coverage_gaps: self.should_fail_on_coverage_gaps,
267 changed_files_analyzed: self.changed_files_analyzed,
268 }
269 }
270}
271
272#[cfg(test)]
273mod tests {
274 use crate::project_config::{ProjectConfigOptions, config_for_project_analysis};
275 use fallow_config::ProductionAnalysis;
276 use fallow_types::output_format::OutputFormat;
277
278 use super::*;
279
280 #[test]
281 fn health_analysis_result_drops_presentation_resolver() {
282 let project = tempfile::tempdir().expect("temp dir");
283 let project_config = config_for_project_analysis(
284 project.path(),
285 None,
286 ProjectConfigOptions {
287 output: OutputFormat::Json,
288 no_cache: true,
289 threads: 1,
290 production_override: None,
291 quiet: true,
292 analysis: ProductionAnalysis::Health,
293 allow_remote_extends: false,
294 },
295 )
296 .expect("project config loads");
297 let result = HealthAnalysisResult {
298 report: HealthReport::default(),
299 branching_by_file: crate::health::BranchingByFile::default(),
300 grouping: None,
301 group_resolver: Some("resolver"),
302 config: project_config.config,
303 workspace_diagnostics: Vec::new(),
304 elapsed: Duration::from_millis(7),
305 timings: None,
306 coverage_gaps_has_findings: true,
307 should_fail_on_coverage_gaps: true,
308 type_aware_meta: None,
309 changed_files_analyzed: None,
310 };
311
312 let neutral = result.without_group_resolver();
313
314 assert!(neutral.group_resolver.is_none());
315 assert_eq!(neutral.elapsed, Duration::from_millis(7));
316 assert!(neutral.coverage_gaps_has_findings);
317 assert!(neutral.should_fail_on_coverage_gaps);
318 }
319
320 #[test]
321 fn engine_result_surface_uses_explicit_reexports() {
322 let source = include_str!("results.rs");
323 let output_dead_code_wildcard = concat!("pub use fallow_types::output_dead_code::", "*");
324 let results_wildcard = concat!("pub use fallow_types::results::", "*");
325
326 assert!(!source.contains(output_dead_code_wildcard));
327 assert!(!source.contains(results_wildcard));
328 }
329}