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///
106/// `workspace_roots` are the roots of the workspaces, so the peer-dependency
107/// credit runs one closure per workspace, as the unused-dependency check does.
108/// `ignore_patterns` are the configured `ignorePatterns`. A workspace
109/// `package.json` that they match does not turn on an optional peer, as in the
110/// unused-dependency check.
111#[must_use]
112#[expect(
113    clippy::implicit_hasher,
114    reason = "fallow standardizes on FxHashSet across the workspace"
115)]
116pub fn trace_dependency(
117    graph: &RetainedModuleGraph,
118    root: &Path,
119    workspace_roots: &[&Path],
120    ignore_patterns: &fallow_config::IgnorePatternSet,
121    package_name: &str,
122    script_used_packages: &FxHashSet<String>,
123) -> DependencyTrace {
124    trace_impl::trace_dependency(
125        graph.as_graph(),
126        root,
127        workspace_roots,
128        ignore_patterns,
129        package_name,
130        script_used_packages,
131    )
132}
133
134/// Trace duplicate-code groups that contain a source location.
135#[must_use]
136pub fn trace_clone(
137    report: &DuplicationReport,
138    root: &Path,
139    file_path: &str,
140    line: usize,
141) -> CloneTrace {
142    trace_impl::trace_clone(report, root, file_path, line)
143}
144
145/// Trace a duplicate-code group by its stable content fingerprint.
146#[must_use]
147pub fn trace_clone_by_fingerprint(
148    report: &DuplicationReport,
149    root: &Path,
150    fingerprint: &str,
151) -> CloneTrace {
152    trace_impl::trace_clone_by_fingerprint(report, root, fingerprint)
153}
154
155/// Trace the impact closure for a file.
156#[must_use]
157pub fn trace_impact_closure(
158    graph: &RetainedModuleGraph,
159    root: &Path,
160    file_path: &str,
161) -> Option<ImpactClosureTrace> {
162    trace_impl::trace_impact_closure(graph.as_graph(), root, file_path)
163}
164
165/// Trace the shortest import path between two modules.
166///
167/// # Errors
168///
169/// Returns the endpoint that did not resolve to exactly one module in the graph.
170pub fn trace_import_path(
171    graph: &RetainedModuleGraph,
172    root: &Path,
173    from_path: &str,
174    to_path: &str,
175) -> Result<ImportPathTrace, ImportPathEndpoint> {
176    trace_impl::trace_import_path(graph.as_graph(), root, from_path, to_path, false)
177}
178
179/// Trace the shortest import path over eager edges only: static imports that
180/// carry a runtime value. The route explains why `to_path` loads before
181/// `from_path` runs.
182///
183/// # Errors
184///
185/// Returns the endpoint that did not resolve to exactly one module in the graph.
186pub fn trace_eager_import_path(
187    graph: &RetainedModuleGraph,
188    root: &Path,
189    from_path: &str,
190    to_path: &str,
191) -> Result<ImportPathTrace, ImportPathEndpoint> {
192    trace_impl::trace_import_path(graph.as_graph(), root, from_path, to_path, true)
193}
194
195/// Trace the shortest import path through an existing analysis session.
196/// With `eager_only`, the walk follows static value imports only.
197///
198/// The inner `Result` carries the endpoint that did not resolve to a module.
199///
200/// # Errors
201///
202/// Returns an error if parsing or graph construction fails.
203pub fn trace_import_path_with_session(
204    session: &crate::session::AnalysisSession,
205    from_path: &str,
206    to_path: &str,
207    eager_only: bool,
208) -> crate::EngineResult<Result<ImportPathTrace, ImportPathEndpoint>> {
209    let output = session.analyze_dead_code_with_shared_artifacts(false, true)?;
210    let graph = output
211        .graph
212        .as_ref()
213        .ok_or_else(|| crate::EngineError::new("trace --path requires a retained module graph"))?;
214    Ok(trace_impl::trace_import_path(
215        graph.as_graph(),
216        session.root(),
217        from_path,
218        to_path,
219        eager_only,
220    ))
221}