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