Skip to main content

backhopper_xref/
result.rs

1// Copyright (C) 2026 Michael S. Klishin and Contributors
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3// See LICENSE-APACHE and LICENSE-MIT for details.
4
5//! Typed result data for each predefined analysis.
6//!
7//! Every result type carries the original call-site locations so consumers
8//! can format human-readable output without re-walking source. The
9//! `Display` impls live in `display.rs`.
10
11use std::collections::BTreeSet;
12
13use backhopper_core::{ApplicationName, BehaviourName, Mfa, ModuleName};
14use backhopper_xref_graph::{
15    CallKind, DeprecationTier, FunctionRef, FunctionSig, Loc, UnresolvedCallSite,
16};
17use serde::{Deserialize, Serialize};
18
19pub trait AnalysisResult: serde::Serialize {
20    fn is_clean(&self) -> bool;
21}
22
23#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
24pub struct UndefinedFunctionCall {
25    pub caller: Mfa,
26    pub callee: FunctionRef,
27    pub kind: CallKind,
28    pub locations: Vec<Loc>,
29}
30
31#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
32pub struct UndefinedFunctionCalls {
33    pub entries: Vec<UndefinedFunctionCall>,
34}
35
36impl AnalysisResult for UndefinedFunctionCalls {
37    fn is_clean(&self) -> bool {
38        self.entries.is_empty()
39    }
40}
41
42#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
43pub struct UnusedLocal {
44    pub mfa: Mfa,
45    pub def_loc: Option<Loc>,
46}
47
48#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
49pub struct LocalsNotUsed {
50    pub entries: Vec<UnusedLocal>,
51}
52
53impl AnalysisResult for LocalsNotUsed {
54    fn is_clean(&self) -> bool {
55        self.entries.is_empty()
56    }
57}
58
59#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
60pub struct UnusedExport {
61    pub mfa: Mfa,
62    pub def_loc: Option<Loc>,
63    pub satisfies_callback: bool,
64    pub is_on_load: bool,
65}
66
67#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
68pub struct ExportsNotUsed {
69    pub entries: Vec<UnusedExport>,
70}
71
72impl AnalysisResult for ExportsNotUsed {
73    fn is_clean(&self) -> bool {
74        self.entries.is_empty()
75    }
76}
77
78#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
79pub struct DeprecatedCall {
80    pub caller: Mfa,
81    pub callee: Mfa,
82    pub tier: DeprecationTier,
83    pub message: Option<String>,
84    pub locations: Vec<Loc>,
85}
86
87#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
88pub struct DeprecatedFunctionCalls {
89    pub entries: Vec<DeprecatedCall>,
90}
91
92impl AnalysisResult for DeprecatedFunctionCalls {
93    fn is_clean(&self) -> bool {
94        self.entries.is_empty()
95    }
96}
97
98#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
99pub struct CallerEntry {
100    pub caller: Mfa,
101    pub kind: CallKind,
102    pub locations: Vec<Loc>,
103}
104
105#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
106pub struct CallersOf {
107    pub target: Mfa,
108    pub entries: Vec<CallerEntry>,
109}
110
111impl AnalysisResult for CallersOf {
112    fn is_clean(&self) -> bool {
113        self.entries.is_empty()
114    }
115}
116
117#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
118pub struct CalleeEntry {
119    pub callee: FunctionRef,
120    pub kind: CallKind,
121    pub locations: Vec<Loc>,
122}
123
124#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
125pub struct CalleesOf {
126    pub source: Mfa,
127    pub entries: Vec<CalleeEntry>,
128}
129
130impl AnalysisResult for CalleesOf {
131    fn is_clean(&self) -> bool {
132        self.entries.is_empty()
133    }
134}
135
136#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
137pub struct ModuleCallers {
138    pub target: ModuleName,
139    pub entries: BTreeSet<ModuleName>,
140}
141
142impl AnalysisResult for ModuleCallers {
143    fn is_clean(&self) -> bool {
144        self.entries.is_empty()
145    }
146}
147
148#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
149pub struct ModuleDependencies {
150    pub source: ModuleName,
151    pub entries: BTreeSet<ModuleName>,
152}
153
154impl AnalysisResult for ModuleDependencies {
155    fn is_clean(&self) -> bool {
156        self.entries.is_empty()
157    }
158}
159
160#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
161pub struct AppDependencies {
162    pub source: ApplicationName,
163    pub entries: BTreeSet<ApplicationName>,
164}
165
166impl AnalysisResult for AppDependencies {
167    fn is_clean(&self) -> bool {
168        self.entries.is_empty()
169    }
170}
171
172#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
173pub struct NonConformance {
174    pub implementer: ModuleName,
175    pub behaviour: BehaviourName,
176    pub missing: BTreeSet<FunctionSig>,
177}
178
179impl AnalysisResult for NonConformance {
180    fn is_clean(&self) -> bool {
181        self.missing.is_empty()
182    }
183}
184
185#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
186pub struct UnresolvedCalls {
187    pub entries: Vec<UnresolvedCallSite>,
188}
189
190impl AnalysisResult for UnresolvedCalls {
191    fn is_clean(&self) -> bool {
192        self.entries.is_empty()
193    }
194}