Skip to main content

code_moniker_core/lang/
strategy.rs

1// code-moniker: ignore-file[smell-data-clumps-param-names]
2// TODO(smell): keep LangStrategy explicit about node, scope, source, and graph until a typed extraction context exists for all language strategies.
3use tree_sitter::Node;
4
5use crate::core::code_graph::{CodeGraph, Position};
6use crate::core::moniker::Moniker;
7
8pub enum NodeShape<'src> {
9	Symbol(Symbol<'src>),
10	Annotation { kind: &'static [u8] },
11	Skip,
12	Recurse,
13}
14
15pub struct Symbol<'src> {
16	pub moniker: Moniker,
17	pub kind: &'static [u8],
18	pub visibility: &'static [u8],
19	pub signature: Option<Vec<u8>>,
20	pub body: Option<Node<'src>>,
21	pub position: Position,
22	pub annotated_by: Vec<RefSpec>,
23}
24
25pub struct RefSpec {
26	pub kind: &'static [u8],
27	pub target: Moniker,
28	pub confidence: &'static [u8],
29	pub position: Position,
30	pub receiver_hint: &'static [u8],
31	pub alias: &'static [u8],
32}
33
34pub trait LangStrategy {
35	fn classify<'src>(
36		&self,
37		node: Node<'src>,
38		scope: &Moniker,
39		source: &'src [u8],
40		graph: &mut CodeGraph,
41	) -> NodeShape<'src>;
42
43	#[allow(unused_variables)]
44	fn before_body(
45		&self,
46		node: Node<'_>,
47		kind: &[u8],
48		moniker: &Moniker,
49		source: &[u8],
50		graph: &mut CodeGraph,
51	) {
52	}
53
54	#[allow(unused_variables)]
55	fn after_body(&self, kind: &[u8], moniker: &Moniker) {}
56
57	#[allow(unused_variables)]
58	fn on_symbol_emitted(
59		&self,
60		node: Node<'_>,
61		sym_kind: &[u8],
62		sym_moniker: &Moniker,
63		source: &[u8],
64		graph: &mut CodeGraph,
65	) {
66	}
67}
68
69#[cfg(test)]
70mod tests {
71	use super::*;
72	use crate::core::moniker::MonikerBuilder;
73
74	struct FakeStrategy;
75
76	impl LangStrategy for FakeStrategy {
77		fn classify<'src>(
78			&self,
79			node: Node<'src>,
80			_scope: &Moniker,
81			_source: &'src [u8],
82			_graph: &mut CodeGraph,
83		) -> NodeShape<'src> {
84			match node.kind() {
85				"line_comment" => NodeShape::Annotation { kind: b"comment" },
86				_ => NodeShape::Recurse,
87			}
88		}
89	}
90
91	fn anchor() -> Moniker {
92		MonikerBuilder::new().project(b"app").build()
93	}
94
95	#[test]
96	fn fake_strategy_classifies_line_comment_as_annotation() {
97		let s = FakeStrategy;
98		let mut p = tree_sitter::Parser::new();
99		p.set_language(&tree_sitter_rust::LANGUAGE.into()).unwrap();
100		let src = b"// hi\nfn main() {}";
101		let tree = p.parse(src, None).unwrap();
102		let mut cursor = tree.root_node().walk();
103		let scope = anchor();
104		let mut g = CodeGraph::new(scope.clone(), b"module");
105		let mut saw_comment = false;
106		for child in tree.root_node().children(&mut cursor) {
107			if let NodeShape::Annotation { kind } = s.classify(child, &scope, src, &mut g) {
108				assert_eq!(kind, b"comment");
109				saw_comment = true;
110			}
111		}
112		assert!(saw_comment);
113	}
114
115	#[test]
116	fn fake_strategy_recurses_on_unknown_kinds() {
117		let s = FakeStrategy;
118		let mut p = tree_sitter::Parser::new();
119		p.set_language(&tree_sitter_rust::LANGUAGE.into()).unwrap();
120		let src = b"fn main() {}";
121		let tree = p.parse(src, None).unwrap();
122		let mut cursor = tree.root_node().walk();
123		let scope = anchor();
124		let mut g = CodeGraph::new(scope.clone(), b"module");
125		for child in tree.root_node().children(&mut cursor) {
126			assert!(matches!(
127				s.classify(child, &scope, src, &mut g),
128				NodeShape::Recurse
129			));
130		}
131	}
132}