Skip to main content

code_moniker_core/lang/go/
mod.rs

1use tree_sitter::{Language, Parser, Tree};
2
3use crate::core::code_graph::CodeGraph;
4use crate::core::moniker::Moniker;
5use crate::core::shape::Shape;
6
7use crate::lang::KindSpec;
8
9pub mod build;
10mod kinds;
11mod sdk_pipeline;
12
13#[derive(Clone, Debug, Default)]
14pub struct Presets {}
15
16pub fn parse(source: &str) -> Tree {
17	let mut parser = Parser::new();
18	let language: Language = tree_sitter_go::LANGUAGE.into();
19	parser.set_language(&language).unwrap_or_else(|err| {
20		panic!("failed to load tree-sitter Go grammar: {err}");
21	});
22	parser.parse(source, None).unwrap_or_else(|| {
23		panic!("tree-sitter parse returned None on a non-cancelled call");
24	})
25}
26
27pub fn extract(
28	uri: &str,
29	source: &str,
30	anchor: &Moniker,
31	deep: bool,
32	presets: &Presets,
33) -> CodeGraph {
34	sdk_pipeline::extract(uri, source, anchor, deep, presets)
35}
36
37pub struct Lang;
38
39const DEF_KINDS: &[&str] = &[
40	"type",
41	"struct",
42	"interface",
43	"func",
44	"method",
45	"field",
46	"var",
47	"const",
48];
49
50const DEF_KIND_SPECS: &[KindSpec] = &[
51	KindSpec::new("struct", Shape::Type, 20, "struct"),
52	KindSpec::new("interface", Shape::Type, 21, "interface"),
53	KindSpec::new("type", Shape::Type, 22, "type"),
54	KindSpec::new("field", Shape::Value, 30, "field"),
55	KindSpec::new("func", Shape::Callable, 40, "func"),
56	KindSpec::new("method", Shape::Callable, 41, "method"),
57	KindSpec::new("const", Shape::Value, 60, "const"),
58	KindSpec::new("var", Shape::Value, 61, "var"),
59];
60
61impl crate::lang::LangExtractor for Lang {
62	type Presets = Presets;
63	const LANG_TAG: &'static str = "go";
64	const ALLOWED_KINDS: &'static [&'static str] = DEF_KINDS;
65	const KIND_SPECS: &'static [KindSpec] = DEF_KIND_SPECS;
66	const ALLOWED_VISIBILITIES: &'static [&'static str] = &["public", "module"];
67
68	fn extract(
69		uri: &str,
70		source: &str,
71		anchor: &Moniker,
72		deep: bool,
73		presets: &Self::Presets,
74	) -> CodeGraph {
75		extract(uri, source, anchor, deep, presets)
76	}
77}
78
79#[cfg(test)]
80mod tests {
81	use super::*;
82	use crate::core::moniker::MonikerBuilder;
83	use crate::lang::assert_conformance;
84
85	fn make_anchor() -> Moniker {
86		MonikerBuilder::new().project(b"app").build()
87	}
88
89	fn extract_default(uri: &str, source: &str, anchor: &Moniker, deep: bool) -> CodeGraph {
90		let g = extract(uri, source, anchor, deep, &Presets::default());
91		assert_conformance::<super::Lang>(&g, anchor);
92		g
93	}
94
95	#[test]
96	fn parse_empty_returns_source_file() {
97		let tree = parse("");
98		assert_eq!(tree.root_node().kind(), "source_file");
99	}
100
101	#[test]
102	fn extract_module_uses_path_segments() {
103		let g = extract_default("acme/util/text.go", "package text\n", &make_anchor(), false);
104		let expected = MonikerBuilder::new()
105			.project(b"app")
106			.segment(b"lang", b"go")
107			.segment(b"package", b"acme")
108			.segment(b"package", b"util")
109			.segment(b"module", b"text")
110			.build();
111		assert_eq!(g.root(), &expected);
112	}
113
114	#[test]
115	fn extract_method_when_type_declared_after_method() {
116		let src = "package foo\nfunc (r *Foo) Bar() {}\ntype Foo struct{}\n";
117		let g = extract_default("foo.go", src, &make_anchor(), false);
118		let bar = MonikerBuilder::new()
119			.project(b"app")
120			.segment(b"lang", b"go")
121			.segment(b"module", b"foo")
122			.segment(b"struct", b"Foo")
123			.segment(b"method", b"Bar()")
124			.build();
125		assert!(
126			g.contains(&bar),
127			"method emitted before its type declaration must still be reparented; defs: {:?}",
128			g.def_monikers()
129		);
130		let bar_def = g.defs().find(|d| d.moniker == bar).expect("method def");
131		let defs: Vec<_> = g.defs().collect();
132		let parent = bar_def
133			.parent
134			.and_then(|idx| defs.get(idx).copied())
135			.expect("method parent");
136		let foo = MonikerBuilder::new()
137			.project(b"app")
138			.segment(b"lang", b"go")
139			.segment(b"module", b"foo")
140			.segment(b"struct", b"Foo")
141			.build();
142		assert_eq!(
143			parent.moniker, foo,
144			"method parent metadata must point at receiver type"
145		);
146	}
147
148	#[test]
149	fn extract_simple_call_to_unresolved_callee_uses_name_only() {
150		let src = "package foo\nfunc Run() { Helper(1, 2) }\n";
151		let g = extract_default("foo.go", src, &make_anchor(), false);
152		let r = g
153			.refs()
154			.find(|r| {
155				r.kind == b"calls"
156					&& r.target.as_view().segments().last().unwrap().name == b"Helper"
157			})
158			.expect("calls Helper (name-only, no parens)");
159		assert_eq!(r.confidence, b"name_match".to_vec());
160	}
161
162	#[test]
163	fn extract_composite_literal_unresolved_type_marks_name_match() {
164		let src = "package foo\nfunc Run() { _ = Bar{} }\n";
165		let g = extract_default("foo.go", src, &make_anchor(), false);
166		let r = g
167			.refs()
168			.find(|r| r.kind == b"instantiates")
169			.expect("instantiates ref");
170		assert_eq!(r.confidence, b"name_match".to_vec());
171	}
172
173	#[test]
174	fn extract_shallow_skips_param_and_local_defs() {
175		let src = "package foo\nfunc Run(x int) { y := 1; _ = y }\n";
176		let g = extract_default("foo.go", src, &make_anchor(), false);
177		assert!(
178			g.defs().all(|d| d.kind != b"param" && d.kind != b"local"),
179			"shallow extraction must not emit param/local defs"
180		);
181	}
182
183	#[test]
184	fn extract_deep_emits_param_defs_under_function() {
185		let src = "package foo\nfunc Run(a int, b string) {}\n";
186		let g = extract_default("foo.go", src, &make_anchor(), true);
187		let pa = MonikerBuilder::new()
188			.project(b"app")
189			.segment(b"lang", b"go")
190			.segment(b"module", b"foo")
191			.segment(b"func", b"Run(a:int,b:string)")
192			.segment(b"param", b"a")
193			.build();
194		let pb = MonikerBuilder::new()
195			.project(b"app")
196			.segment(b"lang", b"go")
197			.segment(b"module", b"foo")
198			.segment(b"func", b"Run(a:int,b:string)")
199			.segment(b"param", b"b")
200			.build();
201		assert!(g.contains(&pa));
202		assert!(g.contains(&pb));
203	}
204
205	#[test]
206	fn extract_deep_emits_receiver_param_for_method() {
207		let src = "package foo\ntype Foo struct{}\nfunc (r *Foo) Bar(x int) {}\n";
208		let g = extract_default("foo.go", src, &make_anchor(), true);
209		let recv = MonikerBuilder::new()
210			.project(b"app")
211			.segment(b"lang", b"go")
212			.segment(b"module", b"foo")
213			.segment(b"struct", b"Foo")
214			.segment(b"method", b"Bar(x:int)")
215			.segment(b"param", b"r")
216			.build();
217		assert!(g.contains(&recv));
218	}
219
220	#[test]
221	fn extract_deep_skips_blank_param() {
222		let src = "package foo\nfunc Run(_ int, b string) {}\n";
223		let g = extract_default("foo.go", src, &make_anchor(), true);
224		let params: Vec<&[u8]> = g
225			.defs()
226			.filter(|d| d.kind == b"param")
227			.map(|d| d.moniker.as_view().segments().last().unwrap().name)
228			.collect();
229		assert_eq!(params, vec![&b"b"[..]]);
230	}
231
232	#[test]
233	fn extract_deep_emits_local_def_for_short_var() {
234		let src = "package foo\nfunc Run() { x := 1; _ = x }\n";
235		let g = extract_default("foo.go", src, &make_anchor(), true);
236		let lx = MonikerBuilder::new()
237			.project(b"app")
238			.segment(b"lang", b"go")
239			.segment(b"module", b"foo")
240			.segment(b"func", b"Run()")
241			.segment(b"local", b"x")
242			.build();
243		assert!(g.contains(&lx));
244	}
245
246	#[test]
247	fn extract_deep_emits_local_defs_for_multi_assign() {
248		let src = "package foo\nfunc Run() { x, y := 1, 2; _, _ = x, y }\n";
249		let g = extract_default("foo.go", src, &make_anchor(), true);
250		let names: Vec<&[u8]> = g
251			.defs()
252			.filter(|d| d.kind == b"local")
253			.map(|d| d.moniker.as_view().segments().last().unwrap().name)
254			.collect();
255		assert!(names.contains(&&b"x"[..]));
256		assert!(names.contains(&&b"y"[..]));
257	}
258
259	#[test]
260	fn extract_deep_emits_local_def_for_var_declaration() {
261		let src = "package foo\nfunc Run() { var z int = 5; _ = z }\n";
262		let g = extract_default("foo.go", src, &make_anchor(), true);
263		let lz = MonikerBuilder::new()
264			.project(b"app")
265			.segment(b"lang", b"go")
266			.segment(b"module", b"foo")
267			.segment(b"func", b"Run()")
268			.segment(b"local", b"z")
269			.build();
270		assert!(g.contains(&lz));
271	}
272
273	#[test]
274	fn extract_deep_emits_local_defs_for_range_vars() {
275		let src =
276			"package foo\nfunc Run(m map[string]int) { for k, v := range m { _, _ = k, v } }\n";
277		let g = extract_default("foo.go", src, &make_anchor(), true);
278		let names: Vec<&[u8]> = g
279			.defs()
280			.filter(|d| d.kind == b"local")
281			.map(|d| d.moniker.as_view().segments().last().unwrap().name)
282			.collect();
283		assert!(names.contains(&&b"k"[..]));
284		assert!(names.contains(&&b"v"[..]));
285	}
286
287	#[test]
288	fn extract_top_level_var_does_not_pollute_locals() {
289		let src = "package foo\nvar GlobalCount int\nfunc Run() { GlobalCount = 1 }\n";
290		let g = extract_default("foo.go", src, &make_anchor(), false);
291		let local_names: Vec<&[u8]> = g
292			.defs()
293			.filter(|d| d.kind == b"local")
294			.map(|d| d.moniker.as_view().segments().last().unwrap().name)
295			.collect();
296		assert!(
297			local_names.is_empty(),
298			"a package-level var must not be emitted as a local. found locals: {:?}",
299			local_names
300		);
301		let vars: Vec<&[u8]> = g
302			.defs()
303			.filter(|d| d.kind == b"var")
304			.map(|d| d.moniker.as_view().segments().last().unwrap().name)
305			.collect();
306		assert_eq!(vars, vec![&b"GlobalCount"[..]]);
307	}
308
309	#[test]
310	fn extract_deep_skips_blank_in_short_var() {
311		let src = "package foo\nfunc Run() { _, y := 1, 2; _ = y }\n";
312		let g = extract_default("foo.go", src, &make_anchor(), true);
313		let names: Vec<&[u8]> = g
314			.defs()
315			.filter(|d| d.kind == b"local")
316			.map(|d| d.moniker.as_view().segments().last().unwrap().name)
317			.collect();
318		assert_eq!(names, vec![&b"y"[..]]);
319	}
320}