Skip to main content

fallow_engine/
trace.rs

1//! Read-only trace helpers exposed through the engine boundary.
2
3use 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
13pub use fallow_types::trace::{
14    ClassMemberTrace, CloneTrace, DependencyTrace, ExportReference, ExportTrace, FileTrace,
15    ImpactClosureGap, ImpactClosureTrace, ImportPathHop, ImportPathTrace, PipelineTimings,
16    ReExportChain, TraceProvenance, TraceSource, TracedCloneGroup, TracedExport, TracedReExport,
17};
18pub use trace_impl::{ImportPathEndpoint, SemanticClassMethodResolutionError};
19
20/// Trace why an export is considered used or unused.
21#[must_use]
22pub fn trace_export(
23    graph: &RetainedModuleGraph,
24    root: &Path,
25    file_path: &str,
26    export_name: &str,
27) -> Option<ExportTrace> {
28    trace_impl::trace_export(graph.as_graph(), root, file_path, export_name)
29}
30
31/// Reconcile checker-backed trace evidence with graph reachability while
32/// retaining non-crediting evidence for inspection.
33pub fn reconcile_semantic_trace_reachability(
34    graph: &RetainedModuleGraph,
35    root: &Path,
36    target_reachable: bool,
37    trace: &mut fallow_types::semantic::SemanticSymbolTrace,
38) {
39    trace_impl::reconcile_semantic_trace_reachability(
40        graph.as_graph(),
41        root,
42        target_reachable,
43        trace,
44    );
45}
46
47/// Resolve the source identity for an exact semantic export query.
48#[must_use]
49pub fn semantic_symbol_for_export(
50    graph: &RetainedModuleGraph,
51    root: &Path,
52    file_path: &str,
53    export_name: &str,
54) -> Option<fallow_types::semantic::SemanticSymbol> {
55    trace_impl::semantic_symbol_for_export(graph.as_graph(), root, file_path, export_name)
56}
57
58/// Resolve the source identity for an exact semantic class-member query.
59#[must_use]
60pub fn semantic_symbol_for_class_member(
61    graph: &RetainedModuleGraph,
62    root: &Path,
63    file_path: &str,
64    member_name: &str,
65) -> Option<fallow_types::semantic::SemanticSymbol> {
66    trace_impl::semantic_symbol_for_class_member(graph.as_graph(), root, file_path, member_name)
67}
68
69/// Resolve one exact exported class method for semantic impact analysis.
70pub fn semantic_symbol_for_exact_class_method(
71    graph: &RetainedModuleGraph,
72    root: &Path,
73    file_path: &str,
74    owner_name: &str,
75    member_name: &str,
76) -> Result<fallow_types::semantic::SemanticSymbol, SemanticClassMethodResolutionError> {
77    trace_impl::semantic_symbol_for_exact_class_method(
78        graph.as_graph(),
79        root,
80        file_path,
81        owner_name,
82        member_name,
83    )
84}
85
86/// Trace a class / enum / store member (the `--trace FILE:MEMBER` fallback when
87/// `MEMBER` is not a top-level export). See issue #1744.
88#[must_use]
89pub fn trace_class_member(
90    graph: &RetainedModuleGraph,
91    root: &Path,
92    file_path: &str,
93    member_name: &str,
94) -> Option<ClassMemberTrace> {
95    trace_impl::trace_class_member(graph.as_graph(), root, file_path, member_name)
96}
97
98/// Trace all graph edges for a file.
99#[must_use]
100pub fn trace_file(graph: &RetainedModuleGraph, root: &Path, file_path: &str) -> Option<FileTrace> {
101    trace_impl::trace_file(graph.as_graph(), root, file_path)
102}
103
104/// Trace where a dependency is used.
105#[must_use]
106#[expect(
107    clippy::implicit_hasher,
108    reason = "fallow standardizes on FxHashSet across the workspace"
109)]
110pub fn trace_dependency(
111    graph: &RetainedModuleGraph,
112    root: &Path,
113    package_name: &str,
114    script_used_packages: &FxHashSet<String>,
115) -> DependencyTrace {
116    trace_impl::trace_dependency(graph.as_graph(), root, package_name, script_used_packages)
117}
118
119/// Trace duplicate-code groups that contain a source location.
120#[must_use]
121pub fn trace_clone(
122    report: &DuplicationReport,
123    root: &Path,
124    file_path: &str,
125    line: usize,
126) -> CloneTrace {
127    trace_impl::trace_clone(report, root, file_path, line)
128}
129
130/// Trace a duplicate-code group by its stable content fingerprint.
131#[must_use]
132pub fn trace_clone_by_fingerprint(
133    report: &DuplicationReport,
134    root: &Path,
135    fingerprint: &str,
136) -> CloneTrace {
137    trace_impl::trace_clone_by_fingerprint(report, root, fingerprint)
138}
139
140/// Trace the impact closure for a file.
141#[must_use]
142pub fn trace_impact_closure(
143    graph: &RetainedModuleGraph,
144    root: &Path,
145    file_path: &str,
146) -> Option<ImpactClosureTrace> {
147    trace_impl::trace_impact_closure(graph.as_graph(), root, file_path)
148}
149
150/// Trace the shortest import path between two modules.
151///
152/// # Errors
153///
154/// Returns the endpoint that did not resolve to exactly one module in the graph.
155pub fn trace_import_path(
156    graph: &RetainedModuleGraph,
157    root: &Path,
158    from_path: &str,
159    to_path: &str,
160) -> Result<ImportPathTrace, ImportPathEndpoint> {
161    trace_impl::trace_import_path(graph.as_graph(), root, from_path, to_path, false)
162}
163
164/// Trace the shortest import path over eager edges only: static imports that
165/// carry a runtime value. The route explains why `to_path` loads before
166/// `from_path` runs.
167///
168/// # Errors
169///
170/// Returns the endpoint that did not resolve to exactly one module in the graph.
171pub fn trace_eager_import_path(
172    graph: &RetainedModuleGraph,
173    root: &Path,
174    from_path: &str,
175    to_path: &str,
176) -> Result<ImportPathTrace, ImportPathEndpoint> {
177    trace_impl::trace_import_path(graph.as_graph(), root, from_path, to_path, true)
178}
179
180/// Trace the shortest import path through an existing analysis session.
181/// With `eager_only`, the walk follows static value imports only.
182///
183/// The inner `Result` carries the endpoint that did not resolve to a module.
184///
185/// # Errors
186///
187/// Returns an error if parsing or graph construction fails.
188pub fn trace_import_path_with_session(
189    session: &crate::session::AnalysisSession,
190    from_path: &str,
191    to_path: &str,
192    eager_only: bool,
193) -> crate::EngineResult<Result<ImportPathTrace, ImportPathEndpoint>> {
194    let output = session.analyze_dead_code_with_shared_artifacts(false, true)?;
195    let graph = output
196        .graph
197        .as_ref()
198        .ok_or_else(|| crate::EngineError::new("trace --path requires a retained module graph"))?;
199    Ok(trace_impl::trace_import_path(
200        graph.as_graph(),
201        session.root(),
202        from_path,
203        to_path,
204        eager_only,
205    ))
206}