1use std::path::Path;
4
5use rustc_hash::FxHashSet;
6
7use crate::duplicates::DuplicationReport;
8use crate::module_graph::RetainedModuleGraph;
9
10#[path = "trace_impl.rs"]
11pub(crate) mod trace_impl;
12#[path = "trace_usage_impl.rs"]
13mod trace_usage_impl;
14
15pub use fallow_types::trace_usage::{DependencyUsage, DependencyUsageQuery, SitePageRequest};
16pub use trace_usage_impl::UsageError;
17
18pub use fallow_types::trace::{
19 ClassMemberTrace, CloneTrace, DependencyTrace, ExportReference, ExportTrace, FileTrace,
20 ImpactClosureGap, ImpactClosureTrace, ImportPathHop, ImportPathTrace, PipelineTimings,
21 ReExportChain, TraceProvenance, TraceSource, TracedCloneGroup, TracedExport, TracedReExport,
22};
23pub use trace_impl::{ImportPathEndpoint, SemanticClassMethodResolutionError};
24
25#[must_use]
27pub fn trace_export(
28 graph: &RetainedModuleGraph,
29 root: &Path,
30 file_path: &str,
31 export_name: &str,
32) -> Option<ExportTrace> {
33 trace_impl::trace_export(graph.as_graph(), root, file_path, export_name)
34}
35
36pub fn reconcile_semantic_trace_reachability(
39 graph: &RetainedModuleGraph,
40 root: &Path,
41 target_reachable: bool,
42 trace: &mut fallow_types::semantic::SemanticSymbolTrace,
43) {
44 trace_impl::reconcile_semantic_trace_reachability(
45 graph.as_graph(),
46 root,
47 target_reachable,
48 trace,
49 );
50}
51
52#[must_use]
54pub fn semantic_symbol_for_export(
55 graph: &RetainedModuleGraph,
56 root: &Path,
57 file_path: &str,
58 export_name: &str,
59) -> Option<fallow_types::semantic::SemanticSymbol> {
60 trace_impl::semantic_symbol_for_export(graph.as_graph(), root, file_path, export_name)
61}
62
63#[must_use]
65pub fn semantic_symbol_for_class_member(
66 graph: &RetainedModuleGraph,
67 root: &Path,
68 file_path: &str,
69 member_name: &str,
70) -> Option<fallow_types::semantic::SemanticSymbol> {
71 trace_impl::semantic_symbol_for_class_member(graph.as_graph(), root, file_path, member_name)
72}
73
74pub fn semantic_symbol_for_exact_class_method(
76 graph: &RetainedModuleGraph,
77 root: &Path,
78 file_path: &str,
79 owner_name: &str,
80 member_name: &str,
81) -> Result<fallow_types::semantic::SemanticSymbol, SemanticClassMethodResolutionError> {
82 trace_impl::semantic_symbol_for_exact_class_method(
83 graph.as_graph(),
84 root,
85 file_path,
86 owner_name,
87 member_name,
88 )
89}
90
91#[must_use]
94pub fn trace_class_member(
95 graph: &RetainedModuleGraph,
96 root: &Path,
97 file_path: &str,
98 member_name: &str,
99) -> Option<ClassMemberTrace> {
100 trace_impl::trace_class_member(graph.as_graph(), root, file_path, member_name)
101}
102
103#[must_use]
105pub fn trace_file(graph: &RetainedModuleGraph, root: &Path, file_path: &str) -> Option<FileTrace> {
106 trace_impl::trace_file(graph.as_graph(), root, file_path)
107}
108
109#[must_use]
117#[expect(
118 clippy::implicit_hasher,
119 reason = "fallow standardizes on FxHashSet across the workspace"
120)]
121pub fn trace_dependency(
122 graph: &RetainedModuleGraph,
123 root: &Path,
124 workspace_roots: &[&Path],
125 ignore_patterns: &fallow_config::IgnorePatternSet,
126 package_name: &str,
127 script_used_packages: &FxHashSet<String>,
128) -> DependencyTrace {
129 trace_impl::trace_dependency(
130 graph.as_graph(),
131 root,
132 workspace_roots,
133 ignore_patterns,
134 package_name,
135 script_used_packages,
136 )
137}
138
139pub struct DependencyTraceInputs<'a> {
141 pub graph: &'a RetainedModuleGraph,
143 pub root: &'a Path,
145 pub workspace_roots: &'a [&'a Path],
147 pub ignore_patterns: &'a fallow_config::IgnorePatternSet,
149 pub script_used_packages: &'a FxHashSet<String>,
151 pub provenance: &'a TraceProvenance,
153 pub results: &'a fallow_types::results::AnalysisResults,
156}
157
158#[must_use]
165pub fn build_dependency_trace(
166 inputs: &DependencyTraceInputs<'_>,
167 package_name: &str,
168) -> DependencyTrace {
169 let mut trace = trace_dependency(
170 inputs.graph,
171 inputs.root,
172 inputs.workspace_roots,
173 inputs.ignore_patterns,
174 package_name,
175 inputs.script_used_packages,
176 );
177 trace.sources = inputs.provenance.dependency_sources(package_name);
178 trace.apply_tooling_credit(inputs.provenance.tooling_credit(package_name));
179 trace.apply_unused_declarations(inputs.results, inputs.root);
180 trace
181}
182
183pub fn trace_dependency_with_session(
193 session: &crate::session::AnalysisSession,
194 package_name: &str,
195 query: &DependencyUsageQuery,
196) -> crate::EngineResult<Result<DependencyTrace, UsageError>> {
197 let output = session.analyze_dead_code_with_shared_artifacts(true, true)?;
199 let graph = output.graph.as_ref().ok_or_else(|| {
200 crate::EngineError::new("trace --dependency requires a retained module graph")
201 })?;
202 let modules = output.modules.as_deref().unwrap_or(&[]);
203 let workspace_roots: Vec<&Path> = session
204 .workspaces()
205 .iter()
206 .map(|workspace| workspace.root.as_path())
207 .collect();
208 let mut trace = build_dependency_trace(
209 &DependencyTraceInputs {
210 graph,
211 root: session.root(),
212 workspace_roots: &workspace_roots,
213 ignore_patterns: &session.config().ignore_patterns,
214 script_used_packages: &output.script_used_packages,
215 provenance: &output.trace_provenance,
216 results: &output.results,
217 },
218 package_name,
219 );
220 match trace_usage_impl::dependency_usage(
221 graph.as_graph(),
222 modules,
223 session.root(),
224 package_name,
225 &trace.imported_by,
226 query,
227 ) {
228 Ok(usage) => {
229 trace.usage = Some(usage);
230 Ok(Ok(trace))
231 }
232 Err(err) => Ok(Err(err)),
233 }
234}
235
236#[must_use]
238pub fn trace_clone(
239 report: &DuplicationReport,
240 root: &Path,
241 file_path: &str,
242 line: usize,
243) -> CloneTrace {
244 trace_impl::trace_clone(report, root, file_path, line)
245}
246
247#[must_use]
249pub fn trace_clone_by_fingerprint(
250 report: &DuplicationReport,
251 root: &Path,
252 fingerprint: &str,
253) -> CloneTrace {
254 trace_impl::trace_clone_by_fingerprint(report, root, fingerprint)
255}
256
257#[must_use]
259pub fn trace_impact_closure(
260 graph: &RetainedModuleGraph,
261 root: &Path,
262 file_path: &str,
263) -> Option<ImpactClosureTrace> {
264 trace_impl::trace_impact_closure(graph.as_graph(), root, file_path)
265}
266
267pub fn trace_import_path(
273 graph: &RetainedModuleGraph,
274 root: &Path,
275 from_path: &str,
276 to_path: &str,
277) -> Result<ImportPathTrace, ImportPathEndpoint> {
278 trace_impl::trace_import_path(graph.as_graph(), root, from_path, to_path, false)
279}
280
281pub fn trace_eager_import_path(
289 graph: &RetainedModuleGraph,
290 root: &Path,
291 from_path: &str,
292 to_path: &str,
293) -> Result<ImportPathTrace, ImportPathEndpoint> {
294 trace_impl::trace_import_path(graph.as_graph(), root, from_path, to_path, true)
295}
296
297pub fn trace_import_path_with_session(
306 session: &crate::session::AnalysisSession,
307 from_path: &str,
308 to_path: &str,
309 eager_only: bool,
310) -> crate::EngineResult<Result<ImportPathTrace, ImportPathEndpoint>> {
311 let output = session.analyze_dead_code_with_shared_artifacts(false, true)?;
312 let graph = output
313 .graph
314 .as_ref()
315 .ok_or_else(|| crate::EngineError::new("trace --path requires a retained module graph"))?;
316 Ok(trace_impl::trace_import_path(
317 graph.as_graph(),
318 session.root(),
319 from_path,
320 to_path,
321 eager_only,
322 ))
323}