Skip to main content

ryo_analysis/discovery/
query.rs

1//! DiscoveryQuery - Query specification for symbol discovery.
2
3use crate::pattern::CaseOptions;
4use crate::query::UsageContext;
5use crate::Pattern;
6use crate::SymbolKind;
7use serde::{Deserialize, Serialize};
8
9/// Sort order for discovery results.
10#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq)]
11pub enum SortOrder {
12    /// Sort by reference count (most used first) - default
13    #[default]
14    Refs,
15    /// Alphabetical order
16    Alpha,
17    /// Sort by impl count (find root traits/important types)
18    Impls,
19}
20
21// =============================================================================
22// TypeFilter - TypeFlowGraph-based filtering
23// =============================================================================
24
25/// Type-based filter using TypeFlowGraph.
26///
27/// Filters symbols by their type usage contexts (return type, parameter type, etc.).
28#[derive(Debug, Clone, Default, Serialize, Deserialize)]
29pub struct TypeFilter {
30    /// Filter by return type pattern.
31    /// Matches functions that return types matching this pattern.
32    pub return_type: Option<Pattern>,
33
34    /// Filter by parameter type pattern.
35    /// Matches functions that have parameters of types matching this pattern.
36    pub param_type: Option<Pattern>,
37
38    /// Filter by field type pattern.
39    /// Matches structs/enums with fields of types matching this pattern.
40    pub field_type: Option<Pattern>,
41
42    /// Filter by any usage context pattern.
43    /// Matches any symbol that uses types matching this pattern.
44    pub uses_type: Option<Pattern>,
45
46    /// Filter by trait bound pattern.
47    /// Matches symbols with generic parameters bounded by matching traits.
48    pub has_bound: Option<Pattern>,
49}
50
51impl TypeFilter {
52    /// Create a filter for functions returning a specific type.
53    pub fn returns(pattern: impl Into<String>) -> Self {
54        Self {
55            return_type: Some(Pattern::glob(pattern.into())),
56            ..Default::default()
57        }
58    }
59
60    /// Create a filter for functions with a specific parameter type.
61    pub fn has_param(pattern: impl Into<String>) -> Self {
62        Self {
63            param_type: Some(Pattern::glob(pattern.into())),
64            ..Default::default()
65        }
66    }
67
68    /// Create a filter for types with a specific field type.
69    pub fn has_field(pattern: impl Into<String>) -> Self {
70        Self {
71            field_type: Some(Pattern::glob(pattern.into())),
72            ..Default::default()
73        }
74    }
75
76    /// Create a filter for symbols using a specific type.
77    pub fn uses(pattern: impl Into<String>) -> Self {
78        Self {
79            uses_type: Some(Pattern::glob(pattern.into())),
80            ..Default::default()
81        }
82    }
83
84    /// Add trait bound filter.
85    pub fn with_bound(mut self, pattern: impl Into<String>) -> Self {
86        self.has_bound = Some(Pattern::glob(pattern.into()));
87        self
88    }
89
90    /// Check if any filter is set.
91    pub fn is_empty(&self) -> bool {
92        self.return_type.is_none()
93            && self.param_type.is_none()
94            && self.field_type.is_none()
95            && self.uses_type.is_none()
96            && self.has_bound.is_none()
97    }
98
99    /// Get the expected usage contexts based on set filters.
100    pub fn expected_contexts(&self) -> Vec<UsageContext> {
101        let mut contexts = Vec::new();
102        if self.return_type.is_some() {
103            contexts.push(UsageContext::ReturnType);
104        }
105        if self.param_type.is_some() {
106            contexts.push(UsageContext::ParamType);
107        }
108        if self.field_type.is_some() {
109            contexts.push(UsageContext::FieldType);
110            contexts.push(UsageContext::VariantField);
111        }
112        contexts
113    }
114}
115
116/// Query specification for symbol discovery.
117#[derive(Debug, Clone, Serialize, Deserialize)]
118pub struct DiscoveryQuery {
119    /// Name pattern to match.
120    pub pattern: Pattern,
121    /// Case matching options.
122    #[serde(default, skip_serializing_if = "is_default_case_options")]
123    pub case_options: CaseOptions,
124    /// Filter by kinds.
125    pub kinds: Option<Vec<SymbolKind>>,
126    /// Filter by crate.
127    pub in_crate: Option<String>,
128    /// Filter by module path (partial match).
129    pub in_module: Option<String>,
130    /// Include related symbols (callers, implementors, etc.).
131    pub include_relations: bool,
132    /// Maximum depth for relation traversal.
133    pub relation_depth: usize,
134    /// Maximum results to return.
135    pub limit: Option<usize>,
136    /// Sort order for results.
137    pub sort: SortOrder,
138    /// Type-based filter using TypeFlowGraph.
139    pub type_filter: Option<TypeFilter>,
140}
141
142fn is_default_case_options(opts: &CaseOptions) -> bool {
143    opts.is_default()
144}
145
146impl DiscoveryQuery {
147    /// Create a query matching symbol names.
148    pub fn symbol(pattern: impl Into<String>) -> Self {
149        Self {
150            pattern: Pattern::glob(pattern.into()),
151            case_options: CaseOptions::default(),
152            kinds: None,
153            in_crate: None,
154            in_module: None,
155            include_relations: false,
156            relation_depth: 1,
157            limit: None,
158            sort: SortOrder::default(),
159            type_filter: None,
160        }
161    }
162
163    /// Create a query matching symbol names with case options.
164    pub fn symbol_with_options(pattern: impl Into<String>, case_options: CaseOptions) -> Self {
165        Self {
166            pattern: Pattern::glob_with_options(pattern.into(), case_options),
167            case_options,
168            kinds: None,
169            in_crate: None,
170            in_module: None,
171            include_relations: false,
172            relation_depth: 1,
173            limit: None,
174            sort: SortOrder::default(),
175            type_filter: None,
176        }
177    }
178
179    /// Create a query with exact name match.
180    pub fn exact(name: impl Into<String>) -> Self {
181        Self {
182            pattern: Pattern::exact(name.into()),
183            case_options: CaseOptions::default(),
184            kinds: None,
185            in_crate: None,
186            in_module: None,
187            include_relations: false,
188            relation_depth: 1,
189            limit: None,
190            sort: SortOrder::default(),
191            type_filter: None,
192        }
193    }
194
195    /// Create a query with regex pattern.
196    pub fn regex(pattern: impl Into<String>) -> Result<Self, crate::pattern::PatternError> {
197        Ok(Self {
198            pattern: Pattern::regex(pattern.into())?,
199            case_options: CaseOptions::default(),
200            kinds: None,
201            in_crate: None,
202            in_module: None,
203            include_relations: false,
204            relation_depth: 1,
205            limit: None,
206            sort: SortOrder::default(),
207            type_filter: None,
208        })
209    }
210
211    /// Enable case-insensitive matching.
212    ///
213    /// # Examples
214    /// ```ignore
215    /// let query = DiscoveryQuery::symbol("*config").ignore_case();
216    /// // Matches: AppConfig, APPCONFIG, appconfig
217    /// ```
218    pub fn ignore_case(mut self) -> Self {
219        self.case_options.ignore_case = true;
220        // Rebuild pattern with new case options
221        let pattern_str = self.pattern.as_str().to_string();
222        self.pattern = if self.pattern.is_glob() {
223            Pattern::glob_with_options(pattern_str, self.case_options)
224        } else if self.pattern.is_regex() {
225            Pattern::regex_with_options(pattern_str, self.case_options)
226                .unwrap_or_else(|_| self.pattern.clone())
227        } else {
228            Pattern::exact_with_options(pattern_str, self.case_options)
229        };
230        self
231    }
232
233    /// Enable word-separate-insensitive matching.
234    ///
235    /// Ignores differences in casing style (snake_case vs camelCase vs PascalCase).
236    ///
237    /// # Examples
238    /// ```ignore
239    /// let query = DiscoveryQuery::symbol("get_user*").ignore_word_separate();
240    /// // Matches: get_user_name, getUserName, GetUserName
241    /// ```
242    pub fn ignore_word_separate(mut self) -> Self {
243        self.case_options.ignore_word_separate = true;
244        // Rebuild pattern with new case options
245        let pattern_str = self.pattern.as_str().to_string();
246        self.pattern = if self.pattern.is_glob() {
247            Pattern::glob_with_options(pattern_str, self.case_options)
248        } else if self.pattern.is_regex() {
249            // Regex doesn't support word-separate, keep as-is
250            self.pattern.clone()
251        } else {
252            Pattern::exact_with_options(pattern_str, self.case_options)
253        };
254        self
255    }
256
257    /// Filter by specific kinds.
258    pub fn kinds(mut self, kinds: Vec<SymbolKind>) -> Self {
259        self.kinds = Some(kinds);
260        self
261    }
262
263    /// Filter by a single kind.
264    pub fn kind(mut self, kind: SymbolKind) -> Self {
265        self.kinds = Some(vec![kind]);
266        self
267    }
268
269    /// Filter by crate name.
270    pub fn in_crate(mut self, crate_name: impl Into<String>) -> Self {
271        self.in_crate = Some(crate_name.into());
272        self
273    }
274
275    /// Filter by module path.
276    pub fn in_module(mut self, module: impl Into<String>) -> Self {
277        self.in_module = Some(module.into());
278        self
279    }
280
281    /// Include related symbols in results.
282    pub fn with_relations(mut self) -> Self {
283        self.include_relations = true;
284        self
285    }
286
287    /// Set relation traversal depth.
288    pub fn relation_depth(mut self, depth: usize) -> Self {
289        self.relation_depth = depth;
290        self
291    }
292
293    /// Limit the number of results.
294    pub fn limit(mut self, limit: usize) -> Self {
295        self.limit = Some(limit);
296        self
297    }
298
299    /// Set sort order.
300    pub fn sort(mut self, sort: SortOrder) -> Self {
301        self.sort = sort;
302        self
303    }
304
305    /// Add type-based filter.
306    pub fn with_type_filter(mut self, filter: TypeFilter) -> Self {
307        self.type_filter = Some(filter);
308        self
309    }
310
311    /// Filter by return type pattern.
312    pub fn returns(self, pattern: impl Into<String>) -> Self {
313        self.with_type_filter(TypeFilter::returns(pattern))
314    }
315
316    /// Filter by parameter type pattern.
317    pub fn has_param_type(self, pattern: impl Into<String>) -> Self {
318        self.with_type_filter(TypeFilter::has_param(pattern))
319    }
320
321    /// Filter by field type pattern.
322    pub fn has_field_type(self, pattern: impl Into<String>) -> Self {
323        self.with_type_filter(TypeFilter::has_field(pattern))
324    }
325}
326
327impl Default for DiscoveryQuery {
328    fn default() -> Self {
329        Self::symbol("*")
330    }
331}
332
333#[cfg(test)]
334mod tests {
335    use super::*;
336
337    #[test]
338    fn test_symbol_query() {
339        let query = DiscoveryQuery::symbol("*Config");
340        assert!(query.pattern.matches("AppConfig"));
341        assert!(query.kinds.is_none());
342    }
343
344    #[test]
345    fn test_exact_query() {
346        let query = DiscoveryQuery::exact("Config");
347        assert!(query.pattern.matches("Config"));
348        assert!(!query.pattern.matches("AppConfig"));
349    }
350
351    #[test]
352    fn test_query_with_kinds() {
353        let query = DiscoveryQuery::symbol("*").kinds(vec![SymbolKind::Struct, SymbolKind::Enum]);
354        assert_eq!(query.kinds.as_ref().unwrap().len(), 2);
355    }
356
357    #[test]
358    fn test_query_chaining() {
359        let query = DiscoveryQuery::symbol("*Handler")
360            .kind(SymbolKind::Struct)
361            .in_crate("mylib")
362            .in_module("handlers")
363            .with_relations()
364            .limit(10);
365
366        assert!(query.kinds.is_some());
367        assert_eq!(query.in_crate, Some("mylib".to_string()));
368        assert_eq!(query.in_module, Some("handlers".to_string()));
369        assert!(query.include_relations);
370        assert_eq!(query.limit, Some(10));
371    }
372}