Skip to main content

sqry_core/schema/
unused.rs

1//! Canonical unused scope enumeration.
2//!
3//! Defines scopes for unused symbol detection.
4
5use serde::{Deserialize, Serialize};
6use std::fmt;
7
8/// Scopes for unused symbol detection.
9///
10/// Used by `find_unused` tool to specify which category of
11/// potentially unused symbols to search for.
12///
13/// # Serialization
14///
15/// All variants serialize to lowercase: `"public"`, `"all"`, etc.
16///
17/// # Examples
18///
19/// ```
20/// use sqry_core::schema::UnusedScope;
21///
22/// let scope = UnusedScope::Function;
23/// assert_eq!(scope.as_str(), "function");
24///
25/// let parsed = UnusedScope::parse("private").unwrap();
26/// assert_eq!(parsed, UnusedScope::Private);
27/// ```
28#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
29#[serde(rename_all = "lowercase")]
30#[derive(Default)]
31pub enum UnusedScope {
32    /// Public symbols only.
33    ///
34    /// Finds public/exported symbols that have no external references.
35    /// These are candidates for API cleanup.
36    Public,
37
38    /// Private symbols only.
39    ///
40    /// Finds private/internal symbols that have no references.
41    /// These are likely dead code that can be safely removed.
42    Private,
43
44    /// Functions/methods only.
45    ///
46    /// Finds unreferenced functions and methods across all visibilities.
47    Function,
48
49    /// Structs/classes only.
50    ///
51    /// Finds unreferenced type definitions.
52    Struct,
53
54    /// All symbols (default).
55    ///
56    /// Searches all symbol types and visibilities for unused code.
57    #[default]
58    All,
59}
60
61impl UnusedScope {
62    /// Returns all variants in definition order.
63    #[must_use]
64    pub const fn all() -> &'static [Self] {
65        &[
66            Self::Public,
67            Self::Private,
68            Self::Function,
69            Self::Struct,
70            Self::All,
71        ]
72    }
73
74    /// Returns the canonical string representation.
75    #[must_use]
76    pub const fn as_str(self) -> &'static str {
77        match self {
78            Self::Public => "public",
79            Self::Private => "private",
80            Self::Function => "function",
81            Self::Struct => "struct",
82            Self::All => "all",
83        }
84    }
85
86    /// Parses a string into an `UnusedScope`.
87    ///
88    /// Returns `None` if the string doesn't match any known scope.
89    /// Case-insensitive.
90    #[must_use]
91    pub fn parse(s: &str) -> Option<Self> {
92        match s.to_lowercase().as_str() {
93            "public" | "pub" | "exported" => Some(Self::Public),
94            "private" | "priv" | "internal" => Some(Self::Private),
95            "function" | "func" | "method" => Some(Self::Function),
96            "struct" | "class" | "type" => Some(Self::Struct),
97            "all" | "*" => Some(Self::All),
98            _ => None,
99        }
100    }
101
102    /// Returns `true` if this scope filters by visibility.
103    #[must_use]
104    pub const fn is_visibility_filter(self) -> bool {
105        matches!(self, Self::Public | Self::Private)
106    }
107
108    /// Returns `true` if this scope filters by symbol kind.
109    #[must_use]
110    pub const fn is_kind_filter(self) -> bool {
111        matches!(self, Self::Function | Self::Struct)
112    }
113
114    /// Returns a human-readable description of this scope.
115    #[must_use]
116    pub const fn description(self) -> &'static str {
117        match self {
118            Self::Public => "unused public/exported symbols",
119            Self::Private => "unused private/internal symbols",
120            Self::Function => "unused functions and methods",
121            Self::Struct => "unused structs and classes",
122            Self::All => "all unused symbols",
123        }
124    }
125}
126
127impl fmt::Display for UnusedScope {
128    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
129        f.write_str(self.as_str())
130    }
131}
132
133#[cfg(test)]
134mod tests {
135    use super::*;
136
137    #[test]
138    fn test_as_str() {
139        assert_eq!(UnusedScope::Public.as_str(), "public");
140        assert_eq!(UnusedScope::Private.as_str(), "private");
141        assert_eq!(UnusedScope::Function.as_str(), "function");
142        assert_eq!(UnusedScope::Struct.as_str(), "struct");
143        assert_eq!(UnusedScope::All.as_str(), "all");
144    }
145
146    #[test]
147    fn test_parse() {
148        assert_eq!(UnusedScope::parse("public"), Some(UnusedScope::Public));
149        assert_eq!(UnusedScope::parse("PRIVATE"), Some(UnusedScope::Private));
150        assert_eq!(UnusedScope::parse("func"), Some(UnusedScope::Function));
151        assert_eq!(UnusedScope::parse("class"), Some(UnusedScope::Struct));
152        assert_eq!(UnusedScope::parse("*"), Some(UnusedScope::All));
153        assert_eq!(UnusedScope::parse("unknown"), None);
154    }
155
156    #[test]
157    fn test_display() {
158        assert_eq!(format!("{}", UnusedScope::Public), "public");
159        assert_eq!(format!("{}", UnusedScope::All), "all");
160    }
161
162    #[test]
163    fn test_serde_roundtrip() {
164        for scope in UnusedScope::all() {
165            let json = serde_json::to_string(scope).unwrap();
166            let deserialized: UnusedScope = serde_json::from_str(&json).unwrap();
167            assert_eq!(*scope, deserialized);
168        }
169    }
170
171    #[test]
172    fn test_default() {
173        assert_eq!(UnusedScope::default(), UnusedScope::All);
174    }
175
176    #[test]
177    fn test_classification() {
178        assert!(UnusedScope::Public.is_visibility_filter());
179        assert!(UnusedScope::Private.is_visibility_filter());
180        assert!(!UnusedScope::Function.is_visibility_filter());
181
182        assert!(UnusedScope::Function.is_kind_filter());
183        assert!(UnusedScope::Struct.is_kind_filter());
184        assert!(!UnusedScope::Public.is_kind_filter());
185
186        assert!(!UnusedScope::All.is_visibility_filter());
187        assert!(!UnusedScope::All.is_kind_filter());
188    }
189
190    #[test]
191    fn test_description() {
192        assert!(UnusedScope::Public.description().contains("public"));
193        assert!(UnusedScope::Private.description().contains("private"));
194        assert!(UnusedScope::Function.description().contains("function"));
195        assert!(UnusedScope::Struct.description().contains("struct"));
196        assert!(UnusedScope::All.description().contains("all"));
197    }
198}