fallow_engine/health/
test_reach.rs1use std::path::Path;
14
15use fallow_config::ResolvedConfig;
16use rustc_hash::FxHashMap;
17
18use crate::discover::FileId;
19use crate::module_graph::{RetainedModuleGraph, StaticTestCoverage};
20use crate::session::AnalysisSession;
21
22#[derive(Debug)]
28pub struct TestReachGraph {
29 graph: RetainedModuleGraph,
30 file_map: FxHashMap<FileId, FileId>,
31}
32
33impl TestReachGraph {
34 fn for_production(
38 config: &ResolvedConfig,
39 file_paths: &FxHashMap<FileId, &std::path::PathBuf>,
40 ) -> Option<Self> {
41 if !config.production {
42 return None;
43 }
44 let mut full_config = config.clone();
45 full_config.production = false;
46 let artifacts = AnalysisSession::from_resolved_config(full_config)
47 .and_then(|session| session.analyze_dead_code_retaining_files(false, true))
48 .inspect_err(|error| {
49 tracing::warn!(
50 "cannot build the full-project graph for production test reachability: {error}"
51 );
52 })
53 .ok()?;
54 let graph = artifacts.graph?;
55 let files = artifacts.files?;
56 let ids_by_path: FxHashMap<&Path, FileId> = files
57 .iter()
58 .map(|file| (file.path.as_path(), file.id))
59 .collect();
60 let file_map = file_paths
61 .iter()
62 .filter_map(|(id, path)| {
63 ids_by_path
64 .get(path.as_path())
65 .map(|full_id| (*id, *full_id))
66 })
67 .collect();
68 Some(Self { graph, file_map })
69 }
70
71 pub(crate) fn static_test_coverage(&self) -> StaticTestCoverage<'_> {
72 StaticTestCoverage::mapped(self.graph.as_graph(), &self.file_map)
73 }
74}
75
76pub struct SharedTestReach<'a> {
83 config: &'a ResolvedConfig,
84 file_paths: &'a FxHashMap<FileId, &'a std::path::PathBuf>,
85 graph: std::cell::OnceCell<Option<TestReachGraph>>,
86}
87
88impl<'a> SharedTestReach<'a> {
89 pub const fn new(
90 config: &'a ResolvedConfig,
91 file_paths: &'a FxHashMap<FileId, &'a std::path::PathBuf>,
92 ) -> Self {
93 Self {
94 config,
95 file_paths,
96 graph: std::cell::OnceCell::new(),
97 }
98 }
99
100 pub fn get(&self) -> Option<&TestReachGraph> {
102 self.graph
103 .get_or_init(|| TestReachGraph::for_production(self.config, self.file_paths))
104 .as_ref()
105 }
106}
107
108pub(super) fn build_test_referenced_exports(
113 file_id: FileId,
114 exports: &[fallow_graph::graph::ExportSymbol],
115 test_coverage: StaticTestCoverage<'_>,
116) -> rustc_hash::FxHashSet<String> {
117 let mut set = rustc_hash::FxHashSet::default();
118 for export in exports {
119 if export.is_type_only {
120 continue;
121 }
122 let has_test_ref = test_coverage.covers_any_reference(file_id, export);
123 if has_test_ref {
124 set.insert(export.name.to_string());
125 }
126 }
127 set
128}