1use code_moniker_core::core::code_graph::{CodeGraph, DefRecord, RefRecord};
2use code_moniker_core::core::moniker::Moniker;
3use code_moniker_core::core::moniker::query::bare_callable_name;
4use code_moniker_core::core::shape::Shape;
5use regex::Regex;
6
7#[derive(Clone, Debug)]
8pub enum Predicate {
9 Eq(Moniker),
10 Lt(Moniker),
11 Le(Moniker),
12 Gt(Moniker),
13 Ge(Moniker),
14 AncestorOf(Moniker),
15 DescendantOf(Moniker),
16 Bind(Moniker),
17}
18
19impl Predicate {
20 pub fn matches(&self, m: &Moniker) -> bool {
21 match self {
22 Self::Eq(o) => m == o,
23 Self::Lt(o) => m < o,
24 Self::Le(o) => m <= o,
25 Self::Gt(o) => m > o,
26 Self::Ge(o) => m >= o,
27 Self::AncestorOf(o) => m.is_ancestor_of(o),
28 Self::DescendantOf(o) => o.is_ancestor_of(m),
29 Self::Bind(o) => m.bind_match(o),
30 }
31 }
32}
33
34#[derive(Debug)]
37pub struct RefMatch<'g> {
38 pub record: &'g RefRecord,
39 pub source: &'g Moniker,
40}
41
42#[derive(Debug, Default)]
43pub struct MatchSet<'g> {
44 pub defs: Vec<&'g DefRecord>,
45 pub refs: Vec<RefMatch<'g>>,
46}
47
48pub fn filter<'g>(
49 graph: &'g CodeGraph,
50 predicates: &[Predicate],
51 kinds: &[String],
52 names: &[Regex],
53 shapes: &[Shape],
54) -> MatchSet<'g> {
55 let kinds_set: Vec<&[u8]> = kinds.iter().map(|s| s.as_bytes()).collect();
56 let kind_ok = |k: &[u8]| -> bool { kinds_set.is_empty() || kinds_set.contains(&k) };
57 let shape_ok = |k: &[u8]| -> bool { shapes.is_empty() || shapes.contains(&Shape::for_kind(k)) };
58 let name_ok = |m: &Moniker| -> bool {
59 names.is_empty() || name_of(m).is_some_and(|name| names.iter().any(|re| re.is_match(&name)))
60 };
61 let mut defs: Vec<&DefRecord> = graph
62 .defs()
63 .filter(|d| {
64 kind_ok(&d.kind)
65 && shape_ok(&d.kind)
66 && name_ok(&d.moniker)
67 && predicates.iter().all(|p| p.matches(&d.moniker))
68 })
69 .collect();
70 let refs: Vec<&RefRecord> = graph
71 .refs()
72 .filter(|r| {
73 kind_ok(&r.kind)
74 && shape_ok(&r.kind)
75 && name_ok(&r.target)
76 && predicates.iter().all(|p| p.matches(&r.target))
77 })
78 .collect();
79 defs.sort_by(|a, b| a.moniker.as_encoded().cmp(b.moniker.as_encoded()));
80 let mut keyed: Vec<RefMatch<'g>> = refs
81 .into_iter()
82 .map(|r| RefMatch {
83 record: r,
84 source: &graph.def_at(r.source).moniker,
85 })
86 .collect();
87 keyed.sort_by(|a, b| {
88 (
89 a.source.as_encoded(),
90 a.record.target.as_encoded(),
91 a.record.position,
92 )
93 .cmp(&(
94 b.source.as_encoded(),
95 b.record.target.as_encoded(),
96 b.record.position,
97 ))
98 });
99 MatchSet { defs, refs: keyed }
100}
101
102pub fn compile_name_filters(names: &[String]) -> anyhow::Result<Vec<Regex>> {
103 names
104 .iter()
105 .map(|name| {
106 Regex::new(name).map_err(|e| anyhow::anyhow!("invalid --name regex `{name}`: {e}"))
107 })
108 .collect()
109}
110
111fn name_of(m: &Moniker) -> Option<String> {
112 let last = m.as_view().segments().last()?;
113 let bare = bare_callable_name(last.name);
114 std::str::from_utf8(bare).ok().map(ToOwned::to_owned)
115}
116
117#[cfg(test)]
118mod tests {
119 use super::*;
120 use code_moniker_core::core::moniker::MonikerBuilder;
121
122 fn m(segments: &[(&[u8], &[u8])]) -> Moniker {
123 let mut b = MonikerBuilder::new();
124 b.project(b"app");
125 for (k, n) in segments {
126 b.segment(k, n);
127 }
128 b.build()
129 }
130
131 fn build_graph() -> CodeGraph {
132 let root = m(&[]);
133 let mut g = CodeGraph::new(root.clone(), b"module");
134 let foo = m(&[(b"class", b"Foo")]);
135 let bar = m(&[(b"class", b"Foo"), (b"method", b"bar")]);
136 let baz = m(&[(b"class", b"Baz")]);
137 g.add_def(foo.clone(), b"class", &root, Some((1, 0)))
138 .unwrap();
139 g.add_def(bar, b"method", &foo, Some((2, 2))).unwrap();
140 g.add_def(baz.clone(), b"class", &root, Some((10, 0)))
141 .unwrap();
142 g.add_ref(&baz, foo, b"EXTENDS", Some((10, 14))).unwrap();
143 g
144 }
145
146 #[test]
147 fn no_predicate_matches_everything() {
148 let g = build_graph();
149 let r = filter(&g, &[], &[], &[], &[]);
150 assert_eq!(r.defs.len(), 4);
151 assert_eq!(r.refs.len(), 1);
152 }
153
154 #[test]
155 fn kind_filter_or_combines() {
156 let g = build_graph();
157 let r = filter(&g, &[], &["method".to_string()], &[], &[]);
158 assert_eq!(r.defs.len(), 1);
159 assert_eq!(r.defs[0].kind, b"method");
160 let r = filter(
161 &g,
162 &[],
163 &["method".to_string(), "module".to_string()],
164 &[],
165 &[],
166 );
167 assert_eq!(r.defs.len(), 2);
168 }
169
170 #[test]
171 fn name_regex_filter_matches_last_segment_name() {
172 let g = build_graph();
173 let names = compile_name_filters(&["^Ba".to_string()]).unwrap();
174 let r = filter(&g, &[], &[], &names, &[]);
175 assert_eq!(r.defs.len(), 1);
176 assert_eq!(name_of(&r.defs[0].moniker).as_deref(), Some("Baz"));
177 }
178
179 #[test]
180 fn descendant_of_keeps_only_strict_descendants_and_target() {
181 let g = build_graph();
182 let foo = m(&[(b"class", b"Foo")]);
183 let r = filter(&g, &[Predicate::DescendantOf(foo)], &[], &[], &[]);
184 let names: Vec<&[u8]> = r.defs.iter().map(|d| d.kind.as_ref()).collect();
185 assert!(names.contains(&b"class".as_slice()));
186 assert!(names.contains(&b"method".as_slice()));
187 assert_eq!(r.defs.len(), 2);
188 }
189
190 #[test]
191 fn equality_matches_one_def() {
192 let g = build_graph();
193 let foo = m(&[(b"class", b"Foo")]);
194 let r = filter(&g, &[Predicate::Eq(foo.clone())], &[], &[], &[]);
195 assert_eq!(r.defs.len(), 1);
196 assert_eq!(&r.defs[0].moniker, &foo);
197 assert_eq!(r.refs.len(), 1, "ref to Foo also matches via target");
198 }
199
200 #[test]
201 fn ordering_predicates_use_byte_lex() {
202 let g = build_graph();
203 let baz = m(&[(b"class", b"Baz")]);
204 let r = filter(&g, &[Predicate::Lt(baz.clone())], &[], &[], &[]);
205 assert!(r.defs.iter().all(|d| d.moniker < baz));
206 let r = filter(&g, &[Predicate::Ge(baz.clone())], &[], &[], &[]);
207 assert!(r.defs.iter().all(|d| d.moniker >= baz));
208 }
209
210 #[test]
211 fn ancestor_of_includes_self() {
212 let g = build_graph();
213 let bar = m(&[(b"class", b"Foo"), (b"method", b"bar")]);
214 let r = filter(&g, &[Predicate::AncestorOf(bar)], &[], &[], &[]);
215 let kinds: Vec<&[u8]> = r.defs.iter().map(|d| d.kind.as_ref()).collect();
216 assert!(kinds.contains(&b"module".as_slice()));
217 assert!(kinds.contains(&b"class".as_slice()));
218 assert!(kinds.contains(&b"method".as_slice()));
219 }
220
221 #[test]
222 fn predicate_and_kind_compose() {
223 let g = build_graph();
224 let foo = m(&[(b"class", b"Foo")]);
225 let r = filter(
226 &g,
227 &[Predicate::DescendantOf(foo)],
228 &["method".to_string()],
229 &[],
230 &[],
231 );
232 assert_eq!(r.defs.len(), 1);
233 assert_eq!(r.defs[0].kind, b"method");
234 }
235
236 #[test]
237 fn ref_filtered_by_target_moniker() {
238 let g = build_graph();
239 let foo = m(&[(b"class", b"Foo")]);
240 let r = filter(&g, &[Predicate::Eq(foo)], &[], &[], &[]);
241 assert_eq!(r.refs.len(), 1, "EXTENDS ref targets Foo");
242 }
243
244 #[test]
245 fn shape_filter_picks_callable_across_kinds() {
246 let g = build_graph();
247 let r = filter(&g, &[], &[], &[], &[Shape::Callable]);
248 assert_eq!(r.defs.len(), 1, "only `method` has shape callable");
249 assert_eq!(r.defs[0].kind, b"method");
250 }
251
252 #[test]
253 fn shape_filter_ref_isolates_ref_records() {
254 let g = build_graph();
255 let r = filter(&g, &[], &[], &[], &[Shape::Ref]);
256 assert!(r.defs.is_empty(), "no def has shape `ref`");
257 assert_eq!(r.refs.len(), 1);
258 }
259
260 #[test]
261 fn kind_and_shape_compose_as_and() {
262 let g = build_graph();
263 let r = filter(&g, &[], &["method".to_string()], &[], &[Shape::Type]);
264 assert!(
265 r.defs.is_empty(),
266 "method is callable, not type — empty AND"
267 );
268 }
269
270 #[test]
271 fn shape_for_kind_maps_known_and_unknown() {
272 assert_eq!(Shape::for_kind(b"method"), Shape::Callable);
273 assert_eq!(Shape::for_kind(b"class"), Shape::Type);
274 assert_eq!(Shape::for_kind(b"EXTENDS"), Shape::Ref);
275 }
276
277 #[test]
278 fn defs_sorted_by_moniker_bytes() {
279 let g = build_graph();
280 let r = filter(&g, &[], &[], &[], &[]);
281 let mut prev: Option<&[u8]> = None;
282 for d in &r.defs {
283 let cur = d.moniker.as_encoded();
284 if let Some(p) = prev {
285 assert!(p <= cur, "defs not sorted: {p:?} then {cur:?}");
286 }
287 prev = Some(cur);
288 }
289 }
290}