1use std::collections::BTreeSet;
2
3use code_moniker_core::core::code_graph::{CodeGraph, DefRecord, RefRecord};
4use code_moniker_core::core::kinds::{
5 KIND_COMMENT, KIND_LOCAL, KIND_MODULE, KIND_PARAM, REF_ANNOTATES, REF_CALLS, REF_DI_REGISTER,
6 REF_DI_REQUIRE, REF_EXTENDS, REF_IMPLEMENTS, REF_IMPORTS_MODULE, REF_IMPORTS_SYMBOL,
7 REF_INSTANTIATES, REF_METHOD_CALL, REF_READS, REF_REEXPORTS, REF_USES_TYPE,
8};
9use code_moniker_core::core::moniker::Moniker;
10use code_moniker_core::core::shape::Shape;
11use code_moniker_core::lang::Lang;
12
13#[derive(Clone, Debug)]
14pub enum Predicate {
15 Eq(Moniker),
16 Lt(Moniker),
17 Le(Moniker),
18 Gt(Moniker),
19 Ge(Moniker),
20 AncestorOf(Moniker),
21 DescendantOf(Moniker),
22 Bind(Moniker),
23}
24
25impl Predicate {
26 pub fn matches(&self, m: &Moniker) -> bool {
27 match self {
28 Self::Eq(o) => m == o,
29 Self::Lt(o) => m < o,
30 Self::Le(o) => m <= o,
31 Self::Gt(o) => m > o,
32 Self::Ge(o) => m >= o,
33 Self::AncestorOf(o) => m.is_ancestor_of(o),
34 Self::DescendantOf(o) => o.is_ancestor_of(m),
35 Self::Bind(o) => m.bind_match(o),
36 }
37 }
38}
39
40#[derive(Debug)]
43pub struct RefMatch<'g> {
44 pub record: &'g RefRecord,
45 pub source: &'g Moniker,
46}
47
48#[derive(Debug, Default)]
49pub struct MatchSet<'g> {
50 pub defs: Vec<&'g DefRecord>,
51 pub refs: Vec<RefMatch<'g>>,
52}
53
54pub fn filter<'g>(
55 graph: &'g CodeGraph,
56 predicates: &[Predicate],
57 kinds: &[String],
58 shapes: &[Shape],
59) -> MatchSet<'g> {
60 let kinds_set: Vec<&[u8]> = kinds.iter().map(|s| s.as_bytes()).collect();
61 let kind_ok = |k: &[u8]| -> bool { kinds_set.is_empty() || kinds_set.contains(&k) };
62 let shape_ok = |k: &[u8]| -> bool { shapes.is_empty() || shapes.contains(&Shape::for_kind(k)) };
63 let mut defs: Vec<&DefRecord> = graph
64 .defs()
65 .filter(|d| {
66 kind_ok(&d.kind)
67 && shape_ok(&d.kind)
68 && predicates.iter().all(|p| p.matches(&d.moniker))
69 })
70 .collect();
71 let refs: Vec<&RefRecord> = graph
72 .refs()
73 .filter(|r| {
74 kind_ok(&r.kind) && shape_ok(&r.kind) && predicates.iter().all(|p| p.matches(&r.target))
75 })
76 .collect();
77 defs.sort_by(|a, b| a.moniker.as_bytes().cmp(b.moniker.as_bytes()));
78 let mut keyed: Vec<RefMatch<'g>> = refs
79 .into_iter()
80 .map(|r| RefMatch {
81 record: r,
82 source: &graph.def_at(r.source).moniker,
83 })
84 .collect();
85 keyed.sort_by(|a, b| {
86 (
87 a.source.as_bytes(),
88 a.record.target.as_bytes(),
89 a.record.position,
90 )
91 .cmp(&(
92 b.source.as_bytes(),
93 b.record.target.as_bytes(),
94 b.record.position,
95 ))
96 });
97 MatchSet { defs, refs: keyed }
98}
99
100pub const CROSS_LANG_KINDS: &[&[u8]] = &[
101 KIND_MODULE,
102 KIND_COMMENT,
103 KIND_LOCAL,
104 KIND_PARAM,
105 REF_IMPORTS_SYMBOL,
106 REF_IMPORTS_MODULE,
107 REF_REEXPORTS,
108 REF_DI_REGISTER,
109 REF_DI_REQUIRE,
110 REF_CALLS,
111 REF_METHOD_CALL,
112 REF_READS,
113 REF_USES_TYPE,
114 REF_INSTANTIATES,
115 REF_EXTENDS,
116 REF_IMPLEMENTS,
117 REF_ANNOTATES,
118];
119
120pub fn known_kinds<'a>(langs: impl IntoIterator<Item = &'a Lang>) -> BTreeSet<&'static str> {
121 let mut out: BTreeSet<&'static str> = BTreeSet::new();
122 for k in CROSS_LANG_KINDS {
123 out.insert(std::str::from_utf8(k).expect("kind constants are ASCII"));
124 }
125 for lang in langs {
126 for k in lang.allowed_kinds() {
127 out.insert(*k);
128 }
129 }
130 out
131}
132
133pub fn unknown_kinds(kinds: &[String], known: &BTreeSet<&'static str>) -> Vec<String> {
134 kinds
135 .iter()
136 .filter(|k| !known.contains(k.as_str()))
137 .cloned()
138 .collect()
139}
140
141#[cfg(test)]
142mod tests {
143 use super::*;
144 use code_moniker_core::core::moniker::MonikerBuilder;
145
146 fn m(segments: &[(&[u8], &[u8])]) -> Moniker {
147 let mut b = MonikerBuilder::new();
148 b.project(b"app");
149 for (k, n) in segments {
150 b.segment(k, n);
151 }
152 b.build()
153 }
154
155 fn build_graph() -> CodeGraph {
156 let root = m(&[]);
157 let mut g = CodeGraph::new(root.clone(), b"module");
158 let foo = m(&[(b"class", b"Foo")]);
159 let bar = m(&[(b"class", b"Foo"), (b"method", b"bar")]);
160 let baz = m(&[(b"class", b"Baz")]);
161 g.add_def(foo.clone(), b"class", &root, Some((1, 0)))
162 .unwrap();
163 g.add_def(bar, b"method", &foo, Some((2, 2))).unwrap();
164 g.add_def(baz.clone(), b"class", &root, Some((10, 0)))
165 .unwrap();
166 g.add_ref(&baz, foo, b"EXTENDS", Some((10, 14))).unwrap();
167 g
168 }
169
170 #[test]
171 fn no_predicate_matches_everything() {
172 let g = build_graph();
173 let r = filter(&g, &[], &[], &[]);
174 assert_eq!(r.defs.len(), 4);
175 assert_eq!(r.refs.len(), 1);
176 }
177
178 #[test]
179 fn kind_filter_or_combines() {
180 let g = build_graph();
181 let r = filter(&g, &[], &["method".to_string()], &[]);
182 assert_eq!(r.defs.len(), 1);
183 assert_eq!(r.defs[0].kind, b"method");
184 let r = filter(&g, &[], &["method".to_string(), "module".to_string()], &[]);
185 assert_eq!(r.defs.len(), 2);
186 }
187
188 #[test]
189 fn descendant_of_keeps_only_strict_descendants_and_target() {
190 let g = build_graph();
191 let foo = m(&[(b"class", b"Foo")]);
192 let r = filter(&g, &[Predicate::DescendantOf(foo)], &[], &[]);
193 let names: Vec<&[u8]> = r.defs.iter().map(|d| d.kind.as_slice()).collect();
194 assert!(names.contains(&b"class".as_slice()));
195 assert!(names.contains(&b"method".as_slice()));
196 assert_eq!(r.defs.len(), 2);
197 }
198
199 #[test]
200 fn equality_matches_one_def() {
201 let g = build_graph();
202 let foo = m(&[(b"class", b"Foo")]);
203 let r = filter(&g, &[Predicate::Eq(foo.clone())], &[], &[]);
204 assert_eq!(r.defs.len(), 1);
205 assert_eq!(&r.defs[0].moniker, &foo);
206 assert_eq!(r.refs.len(), 1, "ref to Foo also matches via target");
207 }
208
209 #[test]
210 fn ordering_predicates_use_byte_lex() {
211 let g = build_graph();
212 let baz = m(&[(b"class", b"Baz")]);
213 let r = filter(&g, &[Predicate::Lt(baz.clone())], &[], &[]);
214 assert!(r.defs.iter().all(|d| d.moniker < baz));
215 let r = filter(&g, &[Predicate::Ge(baz.clone())], &[], &[]);
216 assert!(r.defs.iter().all(|d| d.moniker >= baz));
217 }
218
219 #[test]
220 fn ancestor_of_includes_self() {
221 let g = build_graph();
222 let bar = m(&[(b"class", b"Foo"), (b"method", b"bar")]);
223 let r = filter(&g, &[Predicate::AncestorOf(bar)], &[], &[]);
224 let kinds: Vec<&[u8]> = r.defs.iter().map(|d| d.kind.as_slice()).collect();
225 assert!(kinds.contains(&b"module".as_slice()));
226 assert!(kinds.contains(&b"class".as_slice()));
227 assert!(kinds.contains(&b"method".as_slice()));
228 }
229
230 #[test]
231 fn predicate_and_kind_compose() {
232 let g = build_graph();
233 let foo = m(&[(b"class", b"Foo")]);
234 let r = filter(
235 &g,
236 &[Predicate::DescendantOf(foo)],
237 &["method".to_string()],
238 &[],
239 );
240 assert_eq!(r.defs.len(), 1);
241 assert_eq!(r.defs[0].kind, b"method");
242 }
243
244 #[test]
245 fn ref_filtered_by_target_moniker() {
246 let g = build_graph();
247 let foo = m(&[(b"class", b"Foo")]);
248 let r = filter(&g, &[Predicate::Eq(foo)], &[], &[]);
249 assert_eq!(r.refs.len(), 1, "EXTENDS ref targets Foo");
250 }
251
252 #[test]
253 fn shape_filter_picks_callable_across_kinds() {
254 let g = build_graph();
255 let r = filter(&g, &[], &[], &[Shape::Callable]);
256 assert_eq!(r.defs.len(), 1, "only `method` has shape callable");
257 assert_eq!(r.defs[0].kind, b"method");
258 }
259
260 #[test]
261 fn shape_filter_ref_isolates_ref_records() {
262 let g = build_graph();
263 let r = filter(&g, &[], &[], &[Shape::Ref]);
264 assert!(r.defs.is_empty(), "no def has shape `ref`");
265 assert_eq!(r.refs.len(), 1);
266 }
267
268 #[test]
269 fn kind_and_shape_compose_as_and() {
270 let g = build_graph();
271 let r = filter(&g, &[], &["method".to_string()], &[Shape::Type]);
272 assert!(
273 r.defs.is_empty(),
274 "method is callable, not type — empty AND"
275 );
276 }
277
278 #[test]
279 fn shape_for_kind_maps_known_and_unknown() {
280 assert_eq!(Shape::for_kind(b"method"), Shape::Callable);
281 assert_eq!(Shape::for_kind(b"class"), Shape::Type);
282 assert_eq!(Shape::for_kind(b"EXTENDS"), Shape::Ref);
283 }
284
285 #[test]
286 fn known_kinds_for_ts_includes_class_function_and_ref_kinds() {
287 let k = known_kinds(std::iter::once(&Lang::Ts));
288 assert!(k.contains("class"));
289 assert!(k.contains("function"));
290 assert!(k.contains("method"));
291 assert!(k.contains("calls"));
292 assert!(k.contains("imports_module"));
293 assert!(k.contains("module"));
294 assert!(!k.contains("fn"), "fn is Rust-specific, not in ts vocab");
295 }
296
297 #[test]
298 fn known_kinds_union_picks_up_per_lang_specifics() {
299 let langs = [Lang::Ts, Lang::Rs];
300 let k = known_kinds(langs.iter());
301 assert!(k.contains("function"), "TS contributes `function`");
302 assert!(k.contains("fn"), "Rust contributes `fn`");
303 }
304
305 #[test]
306 fn unknown_kinds_flags_typos_and_lang_mismatches() {
307 let langs = [Lang::Ts];
308 let k = known_kinds(langs.iter());
309 let unknown = unknown_kinds(
310 &[
311 "function".to_string(),
312 "fn".to_string(),
313 "clazz".to_string(),
314 ],
315 &k,
316 );
317 assert_eq!(unknown, vec!["fn".to_string(), "clazz".to_string()]);
318 }
319
320 #[test]
321 fn defs_sorted_by_moniker_bytes() {
322 let g = build_graph();
323 let r = filter(&g, &[], &[], &[]);
324 let mut prev: Option<&[u8]> = None;
325 for d in &r.defs {
326 let cur = d.moniker.as_bytes();
327 if let Some(p) = prev {
328 assert!(p <= cur, "defs not sorted: {p:?} then {cur:?}");
329 }
330 prev = Some(cur);
331 }
332 }
333}