fallow_types/trace.rs
1//! Shared trace output contracts for analysis and integration surfaces.
2
3use std::path::PathBuf;
4
5use serde::Serialize;
6
7use crate::duplicates::{CloneInstance, RefactoringSuggestion};
8use crate::semantic::SemanticNamespace;
9use crate::serde_path;
10use crate::trace_chain::StarExportAmbiguity;
11
12/// Result of tracing an export: why it is considered used or unused.
13#[derive(Debug, Serialize)]
14#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
15pub struct ExportTrace {
16 /// The file containing the export.
17 #[serde(serialize_with = "serde_path::serialize")]
18 pub file: PathBuf,
19 /// The export name being traced.
20 pub export_name: String,
21 /// Namespace whose references are listed for the traced export. The
22 /// preferred lane wins whenever it carries a reference: `value` for a
23 /// value export, `type` for a type-only one. When the preferred lane
24 /// carries none and the other lane resolves to the same declaration, the
25 /// other lane's references are listed and this field names it, so a value
26 /// export whose only credit is a bound `import type` reports `type` with
27 /// `is_used: true`. `is_used` and `direct_references` follow the listed
28 /// lane only, and only reachable reference sources can credit it. Legal
29 /// declaration merges share one declaration group across lanes, including
30 /// an `interface` next to a same-name `class` and a `class` next to a
31 /// same-name `namespace`, so references to either lane credit the merged
32 /// declaration. Distinct same-name declarations outside a merge remain
33 /// separate and keep the preferred lane. `semantic.target.namespace`
34 /// names the lane the declaration itself occupies and can therefore differ
35 /// from this field. Producers always emit the field; the schema permits
36 /// omission by payloads created before namespaces were exposed.
37 #[cfg_attr(feature = "schema", schemars(default))]
38 pub namespace: crate::semantic::SemanticNamespace,
39 /// Whether the file is reachable from an entry point.
40 pub file_reachable: bool,
41 /// Whether the file is an entry point.
42 pub is_entry_point: bool,
43 /// Whether the export is considered used.
44 pub is_used: bool,
45 /// Files that reference this export directly.
46 pub direct_references: Vec<ExportReference>,
47 /// Reachable direct references grouped by namespace. This is additive to
48 /// `namespace` and `direct_references`, whose winning-lane meaning remains
49 /// unchanged for backwards compatibility.
50 #[serde(default, skip_serializing_if = "Vec::is_empty")]
51 pub direct_references_by_namespace: Vec<NamespacedExportReferences>,
52 /// A star-export collision that makes the traced name ambiguous. When
53 /// present, `is_used: false` is an abstention rather than an unused-code
54 /// verdict.
55 #[serde(default, skip_serializing_if = "Option::is_none")]
56 pub star_export_ambiguity: Option<StarExportAmbiguity>,
57 /// Re-export chains that pass through this export.
58 pub re_export_chains: Vec<ReExportChain>,
59 /// Human-readable reason summary.
60 pub reason: String,
61 /// Exact checker-backed references when type-aware tracing is enabled.
62 #[serde(default, skip_serializing_if = "Option::is_none")]
63 pub semantic: Option<crate::semantic::SemanticSymbolTrace>,
64}
65
66/// Result of tracing a class / enum / store MEMBER: the `--trace FILE:NAME`
67/// fallback when `NAME` is not a top-level export but a member declared on one
68/// (issue #1744). The trace runs on the module graph only, so it reports the
69/// OWNING export's reachability and usage (the gating precondition for
70/// member-level crediting) plus a pointer to the right `--unused-*-members`
71/// command, rather than per-member crediting provenance.
72#[derive(Debug, Serialize)]
73#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
74pub struct ClassMemberTrace {
75 /// The file containing the member.
76 #[serde(serialize_with = "serde_path::serialize")]
77 pub file: PathBuf,
78 /// The member name being traced.
79 pub member_name: String,
80 /// The member kind: `class-method`, `class-property`, `enum-member`,
81 /// `store-member`, or `namespace-member`.
82 pub member_kind: String,
83 /// The export that declares this member (the class / enum / store name).
84 pub owner_export: String,
85 /// Namespace whose references credit the owning export, mirroring
86 /// [`ExportTrace::namespace`] for the export this member is declared on.
87 /// `owner_is_used` and `owner_direct_references` describe that lane, so a
88 /// member of a value export credited only by a bound `import type` reports
89 /// `type` here. `semantic.target.namespace` names the lane the checker
90 /// proof covers and can therefore differ. Producers always emit the field;
91 /// the schema permits omission by payloads created before the owner
92 /// namespace was exposed.
93 #[cfg_attr(feature = "schema", schemars(default))]
94 pub owner_namespace: crate::semantic::SemanticNamespace,
95 /// Whether the owning export is considered used.
96 pub owner_is_used: bool,
97 /// Whether the file is reachable from an entry point.
98 pub owner_file_reachable: bool,
99 /// Whether the file is an entry point.
100 pub owner_is_entry_point: bool,
101 /// Files that reference the owning export directly.
102 pub owner_direct_references: Vec<ExportReference>,
103 /// Re-export chains through which the owning export is reachable. Populated
104 /// so a machine consumer can tell "used via a barrel" (empty direct refs but
105 /// non-empty chains) from "genuinely unreferenced".
106 pub owner_re_export_chains: Vec<ReExportChain>,
107 /// Human-readable reason summary plus the follow-up command to inspect the
108 /// member finding.
109 pub reason: String,
110 /// Exact checker-backed member references when type-aware tracing is enabled.
111 #[serde(default, skip_serializing_if = "Option::is_none")]
112 pub semantic: Option<crate::semantic::SemanticSymbolTrace>,
113}
114
115/// A direct reference to an export.
116#[derive(Debug, Clone, Serialize)]
117#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
118pub struct ExportReference {
119 /// File that contains the reference.
120 #[serde(serialize_with = "serde_path::serialize")]
121 pub from_file: PathBuf,
122 /// Reference kind, such as named import, default import, or re-export.
123 pub kind: String,
124}
125
126/// Direct references that credit one namespace of an export binding.
127#[derive(Debug, Serialize)]
128#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
129pub struct NamespacedExportReferences {
130 /// Credited namespace.
131 pub namespace: SemanticNamespace,
132 /// Number of reachable references in this namespace.
133 pub reference_count: usize,
134 /// Reachable references in deterministic graph order.
135 pub references: Vec<ExportReference>,
136}
137
138/// A re-export chain showing how an export is propagated.
139#[derive(Debug, Serialize)]
140#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
141pub struct ReExportChain {
142 /// The barrel file that re-exports this symbol.
143 #[serde(serialize_with = "serde_path::serialize")]
144 pub barrel_file: PathBuf,
145 /// The name it is re-exported as.
146 pub exported_as: String,
147 /// Number of references on the barrel's re-exported symbol.
148 pub reference_count: usize,
149}
150
151/// Result of tracing all edges for a file.
152#[derive(Debug, Serialize)]
153#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
154pub struct FileTrace {
155 /// The traced file.
156 #[serde(serialize_with = "serde_path::serialize")]
157 pub file: PathBuf,
158 /// Whether this file is reachable from entry points.
159 pub is_reachable: bool,
160 /// Whether this file is an entry point.
161 pub is_entry_point: bool,
162 /// Exports declared by this file.
163 pub exports: Vec<TracedExport>,
164 /// Files that this file imports from.
165 #[serde(serialize_with = "serde_path::serialize_vec")]
166 pub imports_from: Vec<PathBuf>,
167 /// Files that import from this file.
168 #[serde(serialize_with = "serde_path::serialize_vec")]
169 pub imported_by: Vec<PathBuf>,
170 /// Re-exports declared by this file.
171 pub re_exports: Vec<TracedReExport>,
172}
173
174/// An export with usage information.
175#[derive(Debug, Serialize)]
176#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
177pub struct TracedExport {
178 /// Export name.
179 pub name: String,
180 /// Whether the export is type-only.
181 pub is_type_only: bool,
182 /// Number of references to this export.
183 pub reference_count: usize,
184 /// Files that reference this export.
185 pub referenced_by: Vec<ExportReference>,
186}
187
188/// A re-export with source information.
189#[derive(Debug, Serialize)]
190#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
191pub struct TracedReExport {
192 /// Source file being re-exported from.
193 #[serde(serialize_with = "serde_path::serialize")]
194 pub source_file: PathBuf,
195 /// Imported symbol name.
196 pub imported_name: String,
197 /// Exported symbol name.
198 pub exported_name: String,
199}
200
201/// Result of tracing a dependency: where it is used.
202#[derive(Debug, Serialize)]
203#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
204pub struct DependencyTrace {
205 /// The dependency name being traced.
206 pub package_name: String,
207 /// Files that import this dependency.
208 #[serde(serialize_with = "serde_path::serialize_vec")]
209 pub imported_by: Vec<PathBuf>,
210 /// Files that import this dependency with type-only imports.
211 #[serde(serialize_with = "serde_path::serialize_vec")]
212 pub type_only_imported_by: Vec<PathBuf>,
213 /// Whether the dependency is invoked from package.json scripts or CI configs.
214 pub used_in_scripts: bool,
215 /// Whether the dependency is used at all.
216 pub is_used: bool,
217 /// Total import count.
218 pub import_count: usize,
219}
220
221/// Pipeline performance timings.
222#[derive(Debug, Clone, Serialize)]
223#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
224pub struct PipelineTimings {
225 /// Time spent discovering files.
226 pub discover_files_ms: f64,
227 /// Number of discovered files.
228 pub file_count: usize,
229 /// Time spent discovering workspaces.
230 pub workspaces_ms: f64,
231 /// Number of discovered workspaces.
232 pub workspace_count: usize,
233 /// Time spent running plugin discovery.
234 pub plugins_ms: f64,
235 /// Time spent analyzing package scripts and CI configuration.
236 pub script_analysis_ms: f64,
237 /// Wall-clock time spent parsing and extracting modules.
238 pub parse_extract_ms: f64,
239 /// Summed parser CPU time across workers.
240 pub parse_cpu_ms: f64,
241 /// Number of extracted modules.
242 pub module_count: usize,
243 /// Number of files loaded from the parse cache.
244 pub cache_hits: usize,
245 /// Number of files parsed without a cache hit.
246 pub cache_misses: usize,
247 /// Time spent updating the parse cache.
248 pub cache_update_ms: f64,
249 /// Time spent categorizing entry points.
250 pub entry_points_ms: f64,
251 /// Number of entry points considered.
252 pub entry_point_count: usize,
253 /// Time spent resolving imports.
254 pub resolve_imports_ms: f64,
255 /// Time spent building the module graph.
256 pub build_graph_ms: f64,
257 /// Time spent running analysis.
258 pub analyze_ms: f64,
259 /// Time spent running duplicate-code analysis, when included.
260 #[serde(default, skip_serializing_if = "Option::is_none")]
261 pub duplication_ms: Option<f64>,
262 /// Total pipeline time.
263 pub total_ms: f64,
264}
265
266/// Result of computing the impact closure for a single file as the seed.
267#[derive(Debug, Serialize)]
268#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
269pub struct ImpactClosureTrace {
270 /// The seed file, root-relative.
271 pub seed: String,
272 /// Root-relative paths transitively affected by the seed.
273 pub affected_not_shown: Vec<String>,
274 /// Coordination gaps between the seed and consumers.
275 pub coordination_gap: Vec<ImpactClosureGap>,
276}
277
278/// One coordination-gap entry in an [`ImpactClosureTrace`].
279#[derive(Debug, Serialize)]
280#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
281pub struct ImpactClosureGap {
282 /// Root-relative path of the consumer module.
283 pub consumer_file: String,
284 /// Exported symbol names the consumer references.
285 pub consumed_symbols: Vec<String>,
286 /// Scope note for the syntactic trace.
287 pub note: String,
288}
289
290/// Result of tracing a clone: all groups containing the code at a source
291/// location or addressed by a stable clone fingerprint.
292#[derive(Debug, Serialize)]
293#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
294pub struct CloneTrace {
295 /// File passed to the trace request, root-relative when a group matches.
296 #[serde(serialize_with = "serde_path::serialize")]
297 pub file: PathBuf,
298 /// 1-based line passed to the trace request or representative group line.
299 pub line: usize,
300 /// The matched clone instance, if one exists.
301 pub matched_instance: Option<CloneInstance>,
302 /// Clone groups matched by the trace request.
303 pub clone_groups: Vec<TracedCloneGroup>,
304}
305
306/// One clone group returned from a clone trace request.
307#[derive(Debug, Serialize)]
308#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
309pub struct TracedCloneGroup {
310 /// Stable content fingerprint, usually `dup:<8hex>` and widened on rare
311 /// report collisions.
312 pub fingerprint: String,
313 /// Number of tokens in the duplicated block.
314 pub token_count: usize,
315 /// Number of lines in the duplicated block.
316 pub line_count: usize,
317 /// Maximum directory-tree or same-file line distance between instances.
318 pub spread: usize,
319 /// Lowest all-pairs similarity for a near-miss clone group.
320 #[serde(default, skip_serializing_if = "Option::is_none")]
321 #[cfg_attr(feature = "schema", schemars(with = "f64"))]
322 pub similarity: Option<f64>,
323 /// Root-relative clone instances in this group.
324 pub instances: Vec<CloneInstance>,
325 /// Group-level refactoring suggestion.
326 pub suggestion: RefactoringSuggestion,
327 /// Best-effort name for the extracted function. Advisory only.
328 #[serde(default, skip_serializing_if = "Option::is_none")]
329 pub suggested_name: Option<String>,
330}