1use glob::Pattern as GlobPattern;
6use ryo_analysis::{AnalysisContext, DiscoveredSymbol, Pattern, Visibility as AnalysisVisibility};
7
8use crate::converter::PostFilter;
9use crate::schema::{GenericsMatch, ReceiverKind, Visibility as RyoQlVisibility};
10
11pub struct PostFilterProcessor<'a> {
13 ctx: &'a AnalysisContext,
14}
15
16impl<'a> PostFilterProcessor<'a> {
17 pub fn new(ctx: &'a AnalysisContext) -> Self {
19 Self { ctx }
20 }
21
22 pub fn apply(
26 &self,
27 results: Vec<DiscoveredSymbol>,
28 filters: &[PostFilter],
29 ) -> Vec<DiscoveredSymbol> {
30 if filters.is_empty() {
31 return results;
32 }
33
34 let mut filtered = results;
35
36 for filter in filters {
37 match filter {
38 PostFilter::IsAsync(expected) => {
39 filtered = self.filter_by_async(filtered, *expected);
40 }
41 PostFilter::IsUnsafe(expected) => {
42 filtered = self.filter_by_unsafe(filtered, *expected);
43 }
44 PostFilter::Visibility(vis) => {
45 filtered = self.filter_by_visibility(filtered, vis);
46 }
47 PostFilter::Receiver(receiver) => {
48 filtered = self.filter_by_receiver(filtered, receiver);
49 }
50 PostFilter::Attributes(attrs) => {
51 filtered = self.filter_by_attributes(filtered, attrs);
52 }
53 PostFilter::Generics(generics) => {
54 filtered = self.filter_by_generics(filtered, generics);
55 }
56 PostFilter::PathInclude(pattern) => {
57 filtered = self.filter_by_path_include(filtered, pattern);
58 }
59 PostFilter::PathExclude(pattern) => {
60 filtered = self.filter_by_path_exclude(filtered, pattern);
61 }
62 PostFilter::PatternSearch(_) => {
63 }
65 PostFilter::OnEmpty => {
66 }
68 PostFilter::ReturnType(pattern) => {
69 filtered = self.filter_by_return_type(filtered, pattern);
70 }
71 PostFilter::ParamType(pattern) => {
72 filtered = self.filter_by_param_type(filtered, pattern);
73 }
74 PostFilter::FieldType(pattern) => {
75 filtered = self.filter_by_field_type(filtered, pattern);
76 }
77 PostFilter::Parent(pattern) => {
78 filtered = self.filter_by_parent(filtered, pattern);
79 }
80 PostFilter::SymbolId(ref sid_str) => {
81 filtered = self.filter_by_symbol_id(filtered, sid_str);
82 }
83 PostFilter::BodyMatch(ref body_match) => {
84 filtered = self.filter_by_body(filtered, body_match);
85 }
86 PostFilter::Relations(ref relations) => {
87 filtered = self.filter_by_relations(filtered, relations);
88 }
89 }
90 }
91
92 filtered
93 }
94
95 fn filter_by_return_type(
97 &self,
98 results: Vec<DiscoveredSymbol>,
99 pattern: &str,
100 ) -> Vec<DiscoveredSymbol> {
101 results
102 .into_iter()
103 .filter(|symbol| {
104 if let Some(detail) = self.ctx.detail_store.function(symbol.id) {
105 if let Some(ref ret_type) = detail.return_type {
106 return matches_pattern(ret_type, pattern);
107 }
108 }
109 false
110 })
111 .collect()
112 }
113
114 fn filter_by_param_type(
116 &self,
117 results: Vec<DiscoveredSymbol>,
118 pattern: &str,
119 ) -> Vec<DiscoveredSymbol> {
120 results
121 .into_iter()
122 .filter(|symbol| {
123 if let Some(detail) = self.ctx.detail_store.function(symbol.id) {
124 for param in &detail.params {
125 if matches_pattern(¶m.ty, pattern) {
126 return true;
127 }
128 }
129 }
130 false
131 })
132 .collect()
133 }
134
135 fn filter_by_field_type(
137 &self,
138 results: Vec<DiscoveredSymbol>,
139 pattern: &str,
140 ) -> Vec<DiscoveredSymbol> {
141 results
142 .into_iter()
143 .filter(|symbol| {
144 if let Some(detail) = self.ctx.detail_store.struct_(symbol.id) {
146 for field in &detail.fields {
147 if matches_pattern(&field.ty, pattern) {
148 return true;
149 }
150 }
151 }
152 if let Some(detail) = self.ctx.detail_store.enum_(symbol.id) {
154 for variant in &detail.variants {
155 for field in &variant.fields {
156 if matches_pattern(&field.ty, pattern) {
157 return true;
158 }
159 }
160 }
161 }
162 false
163 })
164 .collect()
165 }
166
167 fn filter_by_async(
169 &self,
170 results: Vec<DiscoveredSymbol>,
171 expected: bool,
172 ) -> Vec<DiscoveredSymbol> {
173 results
174 .into_iter()
175 .filter(|symbol| {
176 if let Some(detail) = self.ctx.detail_store.function(symbol.id) {
177 detail.is_async == expected
178 } else {
179 !expected
181 }
182 })
183 .collect()
184 }
185
186 fn filter_by_unsafe(
188 &self,
189 results: Vec<DiscoveredSymbol>,
190 expected: bool,
191 ) -> Vec<DiscoveredSymbol> {
192 results
193 .into_iter()
194 .filter(|symbol| {
195 if let Some(detail) = self.ctx.detail_store.function(symbol.id) {
197 return detail.is_unsafe == expected;
198 }
199 if let Some(detail) = self.ctx.detail_store.trait_(symbol.id) {
201 return detail.is_unsafe == expected;
202 }
203 if let Some(detail) = self.ctx.detail_store.impl_(symbol.id) {
205 return detail.is_unsafe == expected;
206 }
207 !expected
209 })
210 .collect()
211 }
212
213 fn filter_by_visibility(
215 &self,
216 results: Vec<DiscoveredSymbol>,
217 expected: &RyoQlVisibility,
218 ) -> Vec<DiscoveredSymbol> {
219 results
220 .into_iter()
221 .filter(|symbol| {
222 if let Some(vis) = &symbol.visibility {
223 matches_visibility(vis, expected)
224 } else if let Some(vis) = self.ctx.registry.visibility(symbol.id) {
225 matches_visibility(vis, expected)
226 } else {
227 matches!(expected, RyoQlVisibility::Private)
229 }
230 })
231 .collect()
232 }
233
234 fn filter_by_receiver(
236 &self,
237 results: Vec<DiscoveredSymbol>,
238 expected: &ReceiverKind,
239 ) -> Vec<DiscoveredSymbol> {
240 results
241 .into_iter()
242 .filter(|symbol| {
243 if let Some(detail) = self.ctx.detail_store.function(symbol.id) {
244 let actual = get_receiver_kind(&detail.params);
245 actual == *expected
246 } else {
247 *expected == ReceiverKind::None
249 }
250 })
251 .collect()
252 }
253
254 fn filter_by_attributes(
259 &self,
260 results: Vec<DiscoveredSymbol>,
261 expected_attrs: &[String],
262 ) -> Vec<DiscoveredSymbol> {
263 if expected_attrs.is_empty() {
264 return results;
265 }
266
267 results
268 .into_iter()
269 .filter(|symbol| {
270 let actual_attrs = self.get_attrs(symbol.id);
271 expected_attrs.iter().all(|expected| {
273 actual_attrs.iter().any(|actual| {
274 actual == expected || matches_pattern(actual, expected)
276 })
277 })
278 })
279 .collect()
280 }
281
282 fn get_attrs(&self, id: ryo_analysis::SymbolId) -> Vec<String> {
284 if let Some(detail) = self.ctx.detail_store.function(id) {
285 return detail.attrs.clone();
286 }
287 if let Some(detail) = self.ctx.detail_store.struct_(id) {
288 return detail.attrs.clone();
289 }
290 if let Some(detail) = self.ctx.detail_store.enum_(id) {
291 return detail.attrs.clone();
292 }
293 if let Some(detail) = self.ctx.detail_store.trait_(id) {
294 return detail.attrs.clone();
295 }
296 if let Some(detail) = self.ctx.detail_store.impl_(id) {
297 return detail.attrs.clone();
298 }
299 Vec::new()
300 }
301
302 fn filter_by_generics(
304 &self,
305 results: Vec<DiscoveredSymbol>,
306 expected: &GenericsMatch,
307 ) -> Vec<DiscoveredSymbol> {
308 results
309 .into_iter()
310 .filter(|symbol| {
311 let generics = self.get_generics(symbol.id);
312 matches_generics(&generics, expected)
313 })
314 .collect()
315 }
316
317 fn get_generics(&self, id: ryo_analysis::SymbolId) -> Option<ryo_analysis::GenericInfo> {
319 if let Some(detail) = self.ctx.detail_store.function(id) {
320 return Some(detail.generics.clone());
321 }
322 if let Some(detail) = self.ctx.detail_store.struct_(id) {
323 return Some(detail.generics.clone());
324 }
325 if let Some(detail) = self.ctx.detail_store.enum_(id) {
326 return Some(detail.generics.clone());
327 }
328 if let Some(detail) = self.ctx.detail_store.trait_(id) {
329 return Some(detail.generics.clone());
330 }
331 if let Some(detail) = self.ctx.detail_store.impl_(id) {
332 return Some(detail.generics.clone());
333 }
334 None
335 }
336
337 fn filter_by_path_include(
339 &self,
340 results: Vec<DiscoveredSymbol>,
341 pattern: &str,
342 ) -> Vec<DiscoveredSymbol> {
343 let Ok(glob) = GlobPattern::new(pattern) else {
344 return results; };
346
347 results
348 .into_iter()
349 .filter(|symbol| {
350 if let Some(ref span) = symbol.span {
351 glob.matches(span.file.as_relative().to_string_lossy().as_ref())
352 } else {
353 false }
355 })
356 .collect()
357 }
358
359 fn filter_by_path_exclude(
361 &self,
362 results: Vec<DiscoveredSymbol>,
363 pattern: &str,
364 ) -> Vec<DiscoveredSymbol> {
365 let Ok(glob) = GlobPattern::new(pattern) else {
366 return results; };
368
369 results
370 .into_iter()
371 .filter(|symbol| {
372 if let Some(ref span) = symbol.span {
373 !glob.matches(span.file.as_relative().to_string_lossy().as_ref())
374 } else {
375 true }
377 })
378 .collect()
379 }
380
381 fn filter_by_parent(
386 &self,
387 results: Vec<DiscoveredSymbol>,
388 pattern: &Pattern,
389 ) -> Vec<DiscoveredSymbol> {
390 results
391 .into_iter()
392 .filter(|symbol| {
393 let path_str = symbol.path.to_string();
395 let parts: Vec<&str> = path_str.split("::").collect();
396
397 if parts.len() >= 2 {
399 let parent_name = parts[parts.len() - 2];
400 pattern.matches(parent_name)
401 } else {
402 false
404 }
405 })
406 .collect()
407 }
408
409 fn filter_by_symbol_id(
414 &self,
415 results: Vec<DiscoveredSymbol>,
416 sid_str: &str,
417 ) -> Vec<DiscoveredSymbol> {
418 let Some(target_id) = ryo_analysis::SymbolId::parse(sid_str) else {
419 return vec![];
421 };
422
423 results
424 .into_iter()
425 .filter(|symbol| symbol.id == target_id)
426 .collect()
427 }
428
429 fn filter_by_body(
436 &self,
437 results: Vec<DiscoveredSymbol>,
438 body_match: &ryo_pattern::BodyMatch,
439 ) -> Vec<DiscoveredSymbol> {
440 use ryo_pattern::BodyScanner;
441 use ryo_source::pure::PureItem;
442
443 results
444 .into_iter()
445 .filter(|symbol| {
446 let Some(PureItem::Fn(fn_item)) = self.ctx.ast_registry.get(symbol.id) else {
448 return false;
450 };
451
452 if let Some(ref patterns) = body_match.contains {
454 for pattern in patterns {
455 let scanner = BodyScanner::new(pattern);
456 if scanner.scan_fn(fn_item).is_empty() {
457 return false;
458 }
459 }
460 }
461
462 if let Some(ref patterns) = body_match.not_contains {
464 for pattern in patterns {
465 let scanner = BodyScanner::new(pattern);
466 if !scanner.scan_fn(fn_item).is_empty() {
467 return false;
468 }
469 }
470 }
471
472 if let Some(ref patterns) = body_match.all_of {
474 for pattern in patterns {
475 let scanner = BodyScanner::new(pattern);
476 if scanner.scan_fn(fn_item).is_empty() {
477 return false;
478 }
479 }
480 }
481
482 true
483 })
484 .collect()
485 }
486
487 fn filter_by_relations(
494 &self,
495 results: Vec<DiscoveredSymbol>,
496 relations: &ryo_pattern::Relations,
497 ) -> Vec<DiscoveredSymbol> {
498 results
499 .into_iter()
500 .filter(|symbol| {
501 if let Some(ref conditions) = relations.any {
503 if !conditions
504 .iter()
505 .any(|rel| self.check_relation(symbol.id, rel))
506 {
507 return false;
508 }
509 }
510
511 if let Some(ref conditions) = relations.all {
513 if !conditions
514 .iter()
515 .all(|rel| self.check_relation(symbol.id, rel))
516 {
517 return false;
518 }
519 }
520
521 if let Some(ref conditions) = relations.none {
523 if conditions
524 .iter()
525 .any(|rel| self.check_relation(symbol.id, rel))
526 {
527 return false;
528 }
529 }
530
531 true
532 })
533 .collect()
534 }
535
536 fn check_relation(
538 &self,
539 source_id: ryo_analysis::SymbolId,
540 relation: &ryo_pattern::Relation,
541 ) -> bool {
542 use ryo_pattern::RelationKind;
543
544 let related_ids: Vec<ryo_analysis::SymbolId> = match relation.kind {
545 RelationKind::Calls => self.ctx.code_graph.callees_of(source_id).collect(),
546 RelationKind::CalledBy => self.ctx.code_graph.callers_of(source_id).collect(),
547 RelationKind::TypeReferences => {
548 self.ctx.typeflow_graph.types_used_by(source_id).collect()
549 }
550 RelationKind::TypeReferencedBy => {
551 self.ctx.typeflow_graph.type_users(source_id).collect()
552 }
553 RelationKind::Implements => {
554 let mut result: Vec<ryo_analysis::SymbolId> = self
556 .ctx
557 .code_graph
558 .outgoing_edges(source_id)
559 .filter(|e| e.kind == ryo_analysis::CodeEdgeV2::Implements)
560 .map(|e| e.to)
561 .collect();
562
563 if result.is_empty() {
566 let source_kind = self.ctx.registry.kind(source_id);
567 if matches!(
568 source_kind,
569 Some(ryo_analysis::SymbolKind::Struct)
570 | Some(ryo_analysis::SymbolKind::Enum)
571 ) {
572 if let Some(source_path) = self.ctx.registry.resolve(source_id) {
573 let source_name = source_path.name();
574 for impl_id in self
576 .ctx
577 .registry
578 .iter_by_kind(ryo_analysis::SymbolKind::Impl)
579 {
580 if let Some(impl_path) = self.ctx.registry.resolve(impl_id) {
581 if let Some(last_seg) = impl_path.segment_refs().last() {
582 if let Some(self_ty) = last_seg.impl_self_ty() {
583 let base =
585 self_ty.split('<').next().unwrap_or(self_ty).trim();
586 if base == source_name {
587 for e in self.ctx.code_graph.outgoing_edges(impl_id)
589 {
590 if e.kind
591 == ryo_analysis::CodeEdgeV2::Implements
592 {
593 result.push(e.to);
594 }
595 }
596 }
597 }
598 }
599 }
600 }
601 }
602 }
603 }
604
605 result
606 }
607 RelationKind::ImplementedBy => {
608 let impl_ids: Vec<ryo_analysis::SymbolId> =
610 self.ctx.code_graph.implementors_of(source_id).collect();
611
612 let mut result = impl_ids.clone();
614 for &impl_id in &impl_ids {
615 if let Some(impl_path) = self.ctx.registry.resolve(impl_id) {
616 if let Some(last_seg) = impl_path.segment_refs().last() {
617 if let Some(self_ty) = last_seg.impl_self_ty() {
618 let base = self_ty.split('<').next().unwrap_or(self_ty).trim();
619 if let Some(struct_id) = self.ctx.registry.lookup_by_name(base) {
621 let kind = self.ctx.registry.kind(struct_id);
622 if matches!(
623 kind,
624 Some(ryo_analysis::SymbolKind::Struct)
625 | Some(ryo_analysis::SymbolKind::Enum)
626 ) {
627 result.push(struct_id);
628 }
629 }
630 }
631 }
632 }
633 }
634
635 result
636 }
637 RelationKind::Contains => self.ctx.code_graph.children_of(source_id).collect(),
638 RelationKind::ContainedBy => self
639 .ctx
640 .code_graph
641 .parent_of(source_id)
642 .into_iter()
643 .collect(),
644 };
645
646 related_ids
648 .iter()
649 .any(|&related_id| self.matches_target(related_id, &relation.target))
650 }
651
652 fn matches_target(
654 &self,
655 id: ryo_analysis::SymbolId,
656 target: &ryo_pattern::RelationTarget,
657 ) -> bool {
658 if let Some(ref target_kind) = target.kind {
660 let actual_kind = self
661 .ctx
662 .registry
663 .kind(id)
664 .unwrap_or(ryo_analysis::SymbolKind::Other);
665 if !matches_target_kind(&actual_kind, target_kind) {
666 return false;
667 }
668 }
669
670 if let Some(ref target_match) = target.r#match {
672 let Some(path) = self.ctx.registry.resolve(id) else {
673 return false;
674 };
675 let name = path.name();
676
677 if let Some(ref exact_name) = target_match.name {
678 if name != exact_name {
679 return false;
680 }
681 }
682
683 if let Some(ref pattern) = target_match.pattern {
684 if !matches_pattern(name, pattern) {
685 return false;
686 }
687 }
688
689 if let Some(ref regex_str) = target_match.regex {
690 if let Ok(re) = regex::Regex::new(regex_str) {
691 if !re.is_match(name) {
692 return false;
693 }
694 }
695 }
696 }
697
698 true
699 }
700}
701
702fn matches_target_kind(
708 actual: &ryo_analysis::SymbolKind,
709 expected: &ryo_pattern::TargetKind,
710) -> bool {
711 use ryo_analysis::SymbolKind;
712 use ryo_pattern::TargetKind;
713
714 matches!(
715 (actual, expected),
716 (SymbolKind::Function, TargetKind::Function)
717 | (SymbolKind::Method, TargetKind::Function) | (SymbolKind::Struct, TargetKind::Struct)
719 | (SymbolKind::Enum, TargetKind::Enum)
720 | (SymbolKind::Trait, TargetKind::Trait)
721 | (SymbolKind::Impl, TargetKind::Impl)
722 | (SymbolKind::Mod, TargetKind::Mod)
723 | (SymbolKind::Const, TargetKind::Const)
724 | (SymbolKind::Static, TargetKind::Static)
725 | (SymbolKind::TypeAlias, TargetKind::TypeAlias)
726 )
727}
728
729fn matches_pattern(text: &str, pattern: &str) -> bool {
731 if pattern == "*" {
733 return true;
734 }
735
736 if pattern.starts_with('*') && pattern.ends_with('*') && pattern.len() > 2 {
738 let inner = &pattern[1..pattern.len() - 1];
739 return text.contains(inner);
740 }
741
742 if pattern.ends_with('*') && !pattern.starts_with('*') {
744 let prefix = &pattern[..pattern.len() - 1];
745 return text.starts_with(prefix);
746 }
747
748 if pattern.starts_with('*') && !pattern.ends_with('*') {
750 let suffix = &pattern[1..];
751 return text.ends_with(suffix);
752 }
753
754 if let Some(regex_str) = pattern.strip_prefix("regex:") {
756 if let Ok(re) = regex::Regex::new(regex_str) {
757 return re.is_match(text);
758 }
759 return false;
760 }
761
762 text == pattern
764}
765
766fn matches_visibility(actual: &AnalysisVisibility, expected: &RyoQlVisibility) -> bool {
768 match (actual, expected) {
769 (AnalysisVisibility::Public, RyoQlVisibility::Public) => true,
770 (AnalysisVisibility::Private, RyoQlVisibility::Private) => true,
771 (AnalysisVisibility::Crate, RyoQlVisibility::Crate) => true,
772 (AnalysisVisibility::Super, RyoQlVisibility::Super) => true,
773 (
774 AnalysisVisibility::Restricted(actual_path),
775 RyoQlVisibility::Restricted(expected_path),
776 ) => actual_path.to_string() == *expected_path,
777 _ => false,
778 }
779}
780
781fn get_receiver_kind(params: &[ryo_analysis::ParamInfo]) -> ReceiverKind {
783 params.first().map_or(ReceiverKind::None, |first| {
784 if !first.is_self {
785 return ReceiverKind::None;
786 }
787 if first.ty.starts_with("&mut ") {
789 ReceiverKind::MutRef
790 } else if first.ty.starts_with('&') {
791 ReceiverKind::Ref
792 } else {
793 ReceiverKind::Owned
794 }
795 })
796}
797
798fn matches_generics(actual: &Option<ryo_analysis::GenericInfo>, expected: &GenericsMatch) -> bool {
800 let Some(actual) = actual else {
801 return expected.params.is_none()
803 && expected.bounds.is_none()
804 && expected.lifetimes.is_none();
805 };
806
807 if let Some(ref expected_params) = expected.params {
809 for expected_param in expected_params {
810 if !actual.type_params.contains(expected_param) {
811 return false;
812 }
813 }
814 }
815
816 if let Some(ref expected_lifetimes) = expected.lifetimes {
818 for expected_lt in expected_lifetimes {
819 if !actual.lifetimes.contains(expected_lt) {
820 return false;
821 }
822 }
823 }
824
825 true
829}
830
831#[cfg(test)]
832mod tests {
833 use super::*;
834
835 #[test]
836 fn test_matches_pattern_exact() {
837 assert!(matches_pattern("Result", "Result"));
838 assert!(!matches_pattern("Result", "Option"));
839 }
840
841 #[test]
842 fn test_matches_pattern_wildcard() {
843 assert!(matches_pattern("anything", "*"));
844 }
845
846 #[test]
847 fn test_matches_pattern_contains() {
848 assert!(matches_pattern("MyResult", "*Result*"));
849 assert!(matches_pattern("ResultWrapper", "*Result*"));
850 assert!(!matches_pattern("Option", "*Result*"));
851 }
852
853 #[test]
854 fn test_matches_pattern_prefix() {
855 assert!(matches_pattern("ResultType", "Result*"));
856 assert!(!matches_pattern("MyResult", "Result*"));
857 }
858
859 #[test]
860 fn test_matches_pattern_suffix() {
861 assert!(matches_pattern("MyResult", "*Result"));
862 assert!(!matches_pattern("ResultType", "*Result"));
863 }
864}