1use std::path::Path;
4
5use fallow_types::trace::PeerClosureSeeds;
6use rustc_hash::FxHashSet;
7
8use crate::duplicates::DuplicationReport;
9use crate::module_graph::RetainedModuleGraph;
10
11#[path = "trace_impl.rs"]
12pub(crate) mod trace_impl;
13#[path = "trace_usage_impl.rs"]
14mod trace_usage_impl;
15
16pub use fallow_types::trace_usage::{DependencyUsage, DependencyUsageQuery, SitePageRequest};
17pub use trace_usage_impl::UsageError;
18
19pub use fallow_types::trace::{
20 ClassMemberTrace, CloneTrace, DependencyTrace, ExportReference, ExportTrace, FileTrace,
21 ImpactClosureGap, ImpactClosureTrace, ImportPathHop, ImportPathTrace, PipelineTimings,
22 ReExportChain, TraceProvenance, TraceSource, TracedCloneGroup, TracedExport, TracedReExport,
23};
24pub use trace_impl::{ImportPathEndpoint, SemanticClassMethodResolutionError};
25
26#[must_use]
28pub fn trace_export(
29 graph: &RetainedModuleGraph,
30 root: &Path,
31 file_path: &str,
32 export_name: &str,
33) -> Option<ExportTrace> {
34 trace_impl::trace_export(graph.as_graph(), root, file_path, export_name)
35}
36
37pub fn reconcile_semantic_trace_reachability(
40 graph: &RetainedModuleGraph,
41 root: &Path,
42 target_reachable: bool,
43 trace: &mut fallow_types::semantic::SemanticSymbolTrace,
44) {
45 trace_impl::reconcile_semantic_trace_reachability(
46 graph.as_graph(),
47 root,
48 target_reachable,
49 trace,
50 );
51}
52
53#[must_use]
55pub fn semantic_symbol_for_export(
56 graph: &RetainedModuleGraph,
57 root: &Path,
58 file_path: &str,
59 export_name: &str,
60) -> Option<fallow_types::semantic::SemanticSymbol> {
61 trace_impl::semantic_symbol_for_export(graph.as_graph(), root, file_path, export_name)
62}
63
64#[must_use]
66pub fn semantic_symbol_for_class_member(
67 graph: &RetainedModuleGraph,
68 root: &Path,
69 file_path: &str,
70 member_name: &str,
71) -> Option<fallow_types::semantic::SemanticSymbol> {
72 trace_impl::semantic_symbol_for_class_member(graph.as_graph(), root, file_path, member_name)
73}
74
75pub fn semantic_symbol_for_exact_class_method(
77 graph: &RetainedModuleGraph,
78 root: &Path,
79 file_path: &str,
80 owner_name: &str,
81 member_name: &str,
82) -> Result<fallow_types::semantic::SemanticSymbol, SemanticClassMethodResolutionError> {
83 trace_impl::semantic_symbol_for_exact_class_method(
84 graph.as_graph(),
85 root,
86 file_path,
87 owner_name,
88 member_name,
89 )
90}
91
92#[must_use]
95pub fn trace_class_member(
96 graph: &RetainedModuleGraph,
97 root: &Path,
98 file_path: &str,
99 member_name: &str,
100) -> Option<ClassMemberTrace> {
101 trace_impl::trace_class_member(graph.as_graph(), root, file_path, member_name)
102}
103
104#[must_use]
106pub fn trace_file(graph: &RetainedModuleGraph, root: &Path, file_path: &str) -> Option<FileTrace> {
107 trace_impl::trace_file(graph.as_graph(), root, file_path)
108}
109
110#[must_use]
118#[expect(
119 clippy::implicit_hasher,
120 reason = "fallow standardizes on FxHashSet across the workspace"
121)]
122pub fn trace_dependency(
123 graph: &RetainedModuleGraph,
124 root: &Path,
125 workspace_roots: &[&Path],
126 ignore_patterns: &fallow_config::IgnorePatternSet,
127 package_name: &str,
128 script_used_packages: &FxHashSet<String>,
129) -> DependencyTrace {
130 trace_impl::trace_dependency(
131 graph.as_graph(),
132 root,
133 workspace_roots,
134 ignore_patterns,
135 package_name,
136 &trace_impl::DependencyCredit {
137 script_used_packages,
138 peer_seeds: &PeerClosureSeeds::from_shared(script_used_packages.iter().cloned()),
139 },
140 )
141}
142
143pub struct DependencyTraceInputs<'a> {
145 pub graph: &'a RetainedModuleGraph,
147 pub root: &'a Path,
149 pub workspace_roots: &'a [&'a Path],
151 pub ignore_patterns: &'a fallow_config::IgnorePatternSet,
153 pub script_used_packages: &'a FxHashSet<String>,
155 pub provenance: &'a TraceProvenance,
157 pub results: &'a fallow_types::results::AnalysisResults,
160}
161
162#[must_use]
169pub fn build_dependency_trace(
170 inputs: &DependencyTraceInputs<'_>,
171 package_name: &str,
172) -> DependencyTrace {
173 let mut trace = trace_impl::trace_dependency(
174 inputs.graph.as_graph(),
175 inputs.root,
176 inputs.workspace_roots,
177 inputs.ignore_patterns,
178 package_name,
179 &trace_impl::DependencyCredit {
180 script_used_packages: inputs.script_used_packages,
181 peer_seeds: inputs.provenance.peer_seeds(),
182 },
183 );
184 trace.sources = inputs.provenance.dependency_sources(package_name);
185 trace.apply_tooling_credit(inputs.provenance.tooling_credit(package_name));
186 trace.apply_unused_declarations(inputs.results, inputs.root);
187 trace
188}
189
190pub fn trace_dependency_with_session(
200 session: &crate::session::AnalysisSession,
201 package_name: &str,
202 query: &DependencyUsageQuery,
203) -> crate::EngineResult<Result<DependencyTrace, UsageError>> {
204 let output = session.analyze_dead_code_with_shared_artifacts(true, true)?;
206 let graph = output.graph.as_ref().ok_or_else(|| {
207 crate::EngineError::new("trace --dependency requires a retained module graph")
208 })?;
209 let modules = output.modules.as_deref().unwrap_or(&[]);
210 let workspace_roots: Vec<&Path> = session
211 .workspaces()
212 .iter()
213 .map(|workspace| workspace.root.as_path())
214 .collect();
215 let mut trace = build_dependency_trace(
216 &DependencyTraceInputs {
217 graph,
218 root: session.root(),
219 workspace_roots: &workspace_roots,
220 ignore_patterns: &session.config().ignore_patterns,
221 script_used_packages: &output.script_used_packages,
222 provenance: &output.trace_provenance,
223 results: &output.results,
224 },
225 package_name,
226 );
227 match trace_usage_impl::dependency_usage(
228 graph.as_graph(),
229 modules,
230 session.root(),
231 package_name,
232 &trace.imported_by,
233 query,
234 ) {
235 Ok(usage) => {
236 trace.usage = Some(usage);
237 Ok(Ok(trace))
238 }
239 Err(err) => Ok(Err(err)),
240 }
241}
242
243#[must_use]
245pub fn trace_clone(
246 report: &DuplicationReport,
247 root: &Path,
248 file_path: &str,
249 line: usize,
250) -> CloneTrace {
251 trace_impl::trace_clone(report, root, file_path, line)
252}
253
254#[must_use]
256pub fn trace_clone_by_fingerprint(
257 report: &DuplicationReport,
258 root: &Path,
259 fingerprint: &str,
260) -> CloneTrace {
261 trace_impl::trace_clone_by_fingerprint(report, root, fingerprint)
262}
263
264#[must_use]
266pub fn trace_impact_closure(
267 graph: &RetainedModuleGraph,
268 root: &Path,
269 file_path: &str,
270) -> Option<ImpactClosureTrace> {
271 trace_impl::trace_impact_closure(graph.as_graph(), root, file_path)
272}
273
274#[must_use]
279pub fn trace_impact_closure_without_search(
280 graph: &RetainedModuleGraph,
281 root: &Path,
282 file_path: &str,
283) -> Option<ImpactClosureTrace> {
284 trace_impl::trace_impact_closure_without_search(graph.as_graph(), root, file_path)
285}
286
287pub fn trace_import_path(
293 graph: &RetainedModuleGraph,
294 root: &Path,
295 from_path: &str,
296 to_path: &str,
297) -> Result<ImportPathTrace, ImportPathEndpoint> {
298 trace_impl::trace_import_path(graph.as_graph(), root, from_path, to_path, false)
299}
300
301pub fn trace_eager_import_path(
309 graph: &RetainedModuleGraph,
310 root: &Path,
311 from_path: &str,
312 to_path: &str,
313) -> Result<ImportPathTrace, ImportPathEndpoint> {
314 trace_impl::trace_import_path(graph.as_graph(), root, from_path, to_path, true)
315}
316
317pub fn trace_import_path_with_session(
326 session: &crate::session::AnalysisSession,
327 from_path: &str,
328 to_path: &str,
329 eager_only: bool,
330) -> crate::EngineResult<Result<ImportPathTrace, ImportPathEndpoint>> {
331 let output = session.analyze_dead_code_with_shared_artifacts(false, true)?;
332 let graph = output
333 .graph
334 .as_ref()
335 .ok_or_else(|| crate::EngineError::new("trace --path requires a retained module graph"))?;
336 Ok(trace_impl::trace_import_path(
337 graph.as_graph(),
338 session.root(),
339 from_path,
340 to_path,
341 eager_only,
342 ))
343}