#![allow(
unused_imports,
reason = "private result contract aggregation re-exports types consumed through typed engine modules"
)]
use std::path::PathBuf;
use std::time::Duration;
use fallow_config::ResolvedConfig;
use fallow_output::{HealthGrouping, HealthReport, HealthTimings};
use fallow_types::discover::DiscoveredFile;
use fallow_types::extract::ModuleInfo;
use fallow_types::source_fingerprint::SourceFingerprint;
use fallow_types::workspace::WorkspaceDiagnostic;
use rustc_hash::{FxHashMap, FxHashSet};
use crate::{duplicates, module_graph, trace};
pub use crate::security::{derive_security_severity, security_catalogue_title};
pub use fallow_types::output_dead_code::{
BoundaryCallViolationFinding, BoundaryCoverageViolationFinding, BoundaryViolationFinding,
CircularDependencyFinding, DuplicateExportFinding, DuplicatePropShapeFinding,
DynamicSegmentNameConflictFinding, EmptyCatalogGroupFinding, InvalidClientExportFinding,
MisconfiguredDependencyOverrideFinding, MisplacedDirectiveFinding,
MixedClientServerBarrelFinding, PolicyViolationFinding, PrivateTypeLeakFinding,
PropDrillingChainFinding, ReExportCycleFinding, RouteCollisionFinding,
TestOnlyDependencyFinding, ThinWrapperFinding, TypeOnlyDependencyFinding,
UnlistedDependencyFinding, UnprovidedInjectFinding, UnrenderedComponentFinding,
UnresolvedCatalogReferenceFinding, UnresolvedImportFinding, UnusedCatalogEntryFinding,
UnusedClassMemberFinding, UnusedComponentEmitFinding, UnusedComponentInputFinding,
UnusedComponentOutputFinding, UnusedComponentPropFinding, UnusedDependencyFinding,
UnusedDependencyOverrideFinding, UnusedDevDependencyFinding, UnusedEnumMemberFinding,
UnusedExportFinding, UnusedFileFinding, UnusedLoadDataKeyFinding,
UnusedOptionalDependencyFinding, UnusedServerActionFinding, UnusedStoreMemberFinding,
UnusedSvelteEventFinding, UnusedTypeFinding,
};
pub use fallow_types::results::{
ActiveSuppression, AnalysisResults, BoundaryCallViolation, BoundaryCoverageViolation,
BoundaryViolation, CircularDependency, CircularDependencyEdge, DependencyLocation,
DependencyOverrideMisconfigReason, DependencyOverrideSource, DuplicateExport,
DuplicateLocation, DuplicatePropShape, DuplicatePropShapeMember, DynamicSegmentNameConflict,
EmptyCatalogGroup, EntryPointSummary, ExportUsage, FeatureFlag, FlagConfidence, FlagKind,
ImportSite, InvalidClientExport, MisconfiguredDependencyOverride, MisplacedDirective,
MixedClientServerBarrel, PolicyRuleKind, PolicyViolation, PolicyViolationSeverity,
PrivateTypeLeak, PropDrillHop, PropDrillingChain, ReExportCycle, ReExportCycleKind,
ReactComponentIntel, ReactHookSummary, ReactPropDrill, ReactPropIntel, ReferenceLocation,
RenderFanInComponent, RenderFanInMetric, RouteCollision, SecurityAttackSurfaceEntry,
SecurityCandidate, SecurityCandidateBoundary, SecurityCandidateSink, SecurityDeadCodeContext,
SecurityDeadCodeKind, SecurityDefensiveBoundary, SecurityDefensiveControl, SecurityFinding,
SecurityFindingKind, SecurityNetworkContext, SecurityReachability, SecurityRuntimeContext,
SecurityRuntimeState, SecuritySeverity, SecurityTaintFlow, SecurityUnresolvedCalleeDiagnostic,
SecurityZoneCrossing, StaleSuppression, SuppressionOrigin, TaintConfidence, TaintEndpoint,
TaintPath, TestOnlyDependency, ThinWrapper, TraceHop, TraceHopRole, TypeOnlyDependency,
UnlistedDependency, UnprovidedInject, UnrenderedComponent, UnresolvedCatalogReference,
UnresolvedImport, UnusedCatalogEntry, UnusedComponentEmit, UnusedComponentInput,
UnusedComponentOutput, UnusedComponentProp, UnusedDependency, UnusedDependencyOverride,
UnusedExport, UnusedFile, UnusedLoadDataKey, UnusedMember, UnusedServerAction,
UnusedSvelteEvent,
};
#[derive(Debug)]
pub struct DeadCodeAnalysis {
pub results: AnalysisResults,
}
#[derive(Debug)]
pub struct DeadCodeAnalysisWithHashes {
pub results: AnalysisResults,
pub file_hashes: FxHashMap<PathBuf, u64>,
}
#[derive(Debug)]
pub struct DeadCodeAnalysisOutput {
pub results: AnalysisResults,
pub modules: Option<Vec<ModuleInfo>>,
pub files: Option<Vec<DiscoveredFile>>,
}
#[derive(Debug)]
pub struct DeadCodeAnalysisArtifacts {
pub results: AnalysisResults,
pub timings: Option<trace::PipelineTimings>,
pub graph: Option<module_graph::RetainedModuleGraph>,
pub modules: Option<Vec<ModuleInfo>>,
pub files: Option<Vec<DiscoveredFile>>,
pub script_used_packages: FxHashSet<String>,
pub file_hashes: FxHashMap<PathBuf, u64>,
}
#[derive(Debug)]
pub struct ProjectAnalysisOutput {
pub dead_code: DeadCodeAnalysisOutput,
pub duplication: duplicates::DuplicationReport,
}
#[derive(Debug)]
pub struct ProjectAnalysisArtifacts {
pub dead_code: DeadCodeAnalysisArtifacts,
pub duplication: duplicates::DuplicationReport,
pub changed_files: Option<FxHashSet<PathBuf>>,
pub source_fingerprints: Option<FxHashMap<PathBuf, SourceFingerprint>>,
}
impl ProjectAnalysisArtifacts {
#[must_use]
pub fn into_output(self) -> ProjectAnalysisOutput {
ProjectAnalysisOutput {
dead_code: DeadCodeAnalysisOutput {
results: self.dead_code.results,
modules: self.dead_code.modules,
files: self.dead_code.files,
},
duplication: self.duplication,
}
}
}
#[derive(Debug)]
pub struct DuplicationAnalysis {
pub report: duplicates::DuplicationReport,
pub default_ignore_skips: duplicates::DefaultIgnoreSkips,
}
#[derive(Debug)]
pub struct HealthAnalysisResult<GroupResolver = ()> {
pub report: HealthReport,
pub grouping: Option<HealthGrouping>,
pub group_resolver: Option<GroupResolver>,
pub config: ResolvedConfig,
pub workspace_diagnostics: Vec<WorkspaceDiagnostic>,
pub elapsed: Duration,
pub timings: Option<HealthTimings>,
pub coverage_gaps_has_findings: bool,
pub should_fail_on_coverage_gaps: bool,
}
impl<GroupResolver> HealthAnalysisResult<GroupResolver> {
#[must_use]
pub fn without_group_resolver(self) -> HealthAnalysisResult<()> {
HealthAnalysisResult {
report: self.report,
grouping: self.grouping,
group_resolver: None,
config: self.config,
workspace_diagnostics: self.workspace_diagnostics,
elapsed: self.elapsed,
timings: self.timings,
coverage_gaps_has_findings: self.coverage_gaps_has_findings,
should_fail_on_coverage_gaps: self.should_fail_on_coverage_gaps,
}
}
}
#[cfg(test)]
mod tests {
use crate::project_config::{ProjectConfigOptions, config_for_project_analysis};
use fallow_config::ProductionAnalysis;
use fallow_types::output_format::OutputFormat;
use super::*;
#[test]
fn health_analysis_result_drops_presentation_resolver() {
let project = tempfile::tempdir().expect("temp dir");
let project_config = config_for_project_analysis(
project.path(),
None,
ProjectConfigOptions {
output: OutputFormat::Json,
no_cache: true,
threads: 1,
production_override: None,
quiet: true,
analysis: ProductionAnalysis::Health,
},
)
.expect("project config loads");
let result = HealthAnalysisResult {
report: HealthReport::default(),
grouping: None,
group_resolver: Some("resolver"),
config: project_config.config,
workspace_diagnostics: Vec::new(),
elapsed: Duration::from_millis(7),
timings: None,
coverage_gaps_has_findings: true,
should_fail_on_coverage_gaps: true,
};
let neutral = result.without_group_resolver();
assert!(neutral.group_resolver.is_none());
assert_eq!(neutral.elapsed, Duration::from_millis(7));
assert!(neutral.coverage_gaps_has_findings);
assert!(neutral.should_fail_on_coverage_gaps);
}
#[test]
fn engine_result_surface_uses_explicit_reexports() {
let source = include_str!("results.rs");
let output_dead_code_wildcard = concat!("pub use fallow_types::output_dead_code::", "*");
let results_wildcard = concat!("pub use fallow_types::results::", "*");
assert!(!source.contains(output_dead_code_wildcard));
assert!(!source.contains(results_wildcard));
}
}