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