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::{ExtractionContext, KindSpec, LangExtractor, ParsedDocument};
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	<Lang as LangExtractor>::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 parse(_uri: &str, source: &str) -> ParsedDocument {
69		ParsedDocument::new(parse(source))
70	}
71
72	fn file_root(uri: &str, anchor: &Moniker) -> Option<Moniker> {
73		Some(sdk_pipeline::compute_module_moniker(anchor, uri))
74	}
75
76	fn extract_parsed(
77		context: ExtractionContext<'_, Self::Presets>,
78		document: &ParsedDocument,
79	) -> CodeGraph {
80		sdk_pipeline::extract(
81			context.uri,
82			context.source,
83			document,
84			context.anchor,
85			context.deep,
86			context.presets,
87		)
88	}
89}
90
91#[cfg(test)]
92mod tests {
93	use super::*;
94	use crate::core::moniker::MonikerBuilder;
95	use crate::lang::assert_conformance;
96
97	fn make_anchor() -> Moniker {
98		MonikerBuilder::new().project(b"app").build()
99	}
100
101	fn extract_default(uri: &str, source: &str, anchor: &Moniker, deep: bool) -> CodeGraph {
102		let g = extract(uri, source, anchor, deep, &Presets::default());
103		assert_conformance::<super::Lang>(&g, anchor);
104		g
105	}
106
107	#[test]
108	fn parse_empty_returns_source_file() {
109		let tree = parse("");
110		assert_eq!(tree.root_node().kind(), "source_file");
111	}
112
113	#[test]
114	fn extract_module_uses_path_segments() {
115		let g = extract_default("acme/util/text.go", "package text\n", &make_anchor(), false);
116		let expected = MonikerBuilder::new()
117			.project(b"app")
118			.segment(b"lang", b"go")
119			.segment(b"package", b"acme")
120			.segment(b"package", b"util")
121			.segment(b"module", b"text")
122			.build();
123		assert_eq!(g.root(), &expected);
124	}
125
126	#[test]
127	fn extract_method_when_type_declared_after_method() {
128		let src = "package foo\nfunc (r *Foo) Bar() {}\ntype Foo struct{}\n";
129		let g = extract_default("foo.go", src, &make_anchor(), false);
130		let bar = MonikerBuilder::new()
131			.project(b"app")
132			.segment(b"lang", b"go")
133			.segment(b"module", b"foo")
134			.segment(b"struct", b"Foo")
135			.segment(b"method", b"Bar()")
136			.build();
137		assert!(
138			g.contains(&bar),
139			"method emitted before its type declaration must still be reparented; defs: {:?}",
140			g.def_monikers()
141		);
142		let bar_def = g.defs().find(|d| d.moniker == bar).expect("method def");
143		let defs: Vec<_> = g.defs().collect();
144		let parent = bar_def
145			.parent
146			.and_then(|idx| defs.get(idx).copied())
147			.expect("method parent");
148		let foo = MonikerBuilder::new()
149			.project(b"app")
150			.segment(b"lang", b"go")
151			.segment(b"module", b"foo")
152			.segment(b"struct", b"Foo")
153			.build();
154		assert_eq!(
155			parent.moniker, foo,
156			"method parent metadata must point at receiver type"
157		);
158	}
159
160	#[test]
161	fn extract_simple_call_to_unresolved_callee_uses_name_only() {
162		let src = "package foo\nfunc Run() { Helper(1, 2) }\n";
163		let g = extract_default("foo.go", src, &make_anchor(), false);
164		let r = g
165			.refs()
166			.find(|r| {
167				r.kind == b"calls"
168					&& r.target.as_view().segments().last().unwrap().name == b"Helper"
169			})
170			.expect("calls Helper (name-only, no parens)");
171		assert_eq!(r.confidence, b"name_match".to_vec());
172	}
173
174	#[test]
175	fn extract_composite_literal_unresolved_type_marks_name_match() {
176		let src = "package foo\nfunc Run() { _ = Bar{} }\n";
177		let g = extract_default("foo.go", src, &make_anchor(), false);
178		let r = g
179			.refs()
180			.find(|r| r.kind == b"instantiates")
181			.expect("instantiates ref");
182		assert_eq!(r.confidence, b"name_match".to_vec());
183	}
184
185	#[test]
186	fn extract_shallow_skips_param_and_local_defs() {
187		let src = "package foo\nfunc Run(x int) { y := 1; _ = y }\n";
188		let g = extract_default("foo.go", src, &make_anchor(), false);
189		assert!(
190			g.defs().all(|d| d.kind != b"param" && d.kind != b"local"),
191			"shallow extraction must not emit param/local defs"
192		);
193	}
194
195	#[test]
196	fn extract_deep_emits_param_defs_under_function() {
197		let src = "package foo\nfunc Run(a int, b string) {}\n";
198		let g = extract_default("foo.go", src, &make_anchor(), true);
199		let pa = MonikerBuilder::new()
200			.project(b"app")
201			.segment(b"lang", b"go")
202			.segment(b"module", b"foo")
203			.segment(b"func", b"Run(a:int,b:string)")
204			.segment(b"param", b"a")
205			.build();
206		let pb = MonikerBuilder::new()
207			.project(b"app")
208			.segment(b"lang", b"go")
209			.segment(b"module", b"foo")
210			.segment(b"func", b"Run(a:int,b:string)")
211			.segment(b"param", b"b")
212			.build();
213		assert!(g.contains(&pa));
214		assert!(g.contains(&pb));
215	}
216
217	#[test]
218	fn extract_deep_emits_receiver_param_for_method() {
219		let src = "package foo\ntype Foo struct{}\nfunc (r *Foo) Bar(x int) {}\n";
220		let g = extract_default("foo.go", src, &make_anchor(), true);
221		let recv = MonikerBuilder::new()
222			.project(b"app")
223			.segment(b"lang", b"go")
224			.segment(b"module", b"foo")
225			.segment(b"struct", b"Foo")
226			.segment(b"method", b"Bar(x:int)")
227			.segment(b"param", b"r")
228			.build();
229		assert!(g.contains(&recv));
230	}
231
232	#[test]
233	fn extract_deep_skips_blank_param() {
234		let src = "package foo\nfunc Run(_ int, b string) {}\n";
235		let g = extract_default("foo.go", src, &make_anchor(), true);
236		let params: Vec<&[u8]> = g
237			.defs()
238			.filter(|d| d.kind == b"param")
239			.map(|d| d.moniker.as_view().segments().last().unwrap().name)
240			.collect();
241		assert_eq!(params, vec![&b"b"[..]]);
242	}
243
244	#[test]
245	fn extract_deep_emits_local_def_for_short_var() {
246		let src = "package foo\nfunc Run() { x := 1; _ = x }\n";
247		let g = extract_default("foo.go", src, &make_anchor(), true);
248		let lx = MonikerBuilder::new()
249			.project(b"app")
250			.segment(b"lang", b"go")
251			.segment(b"module", b"foo")
252			.segment(b"func", b"Run()")
253			.segment(b"local", b"x")
254			.build();
255		assert!(g.contains(&lx));
256	}
257
258	#[test]
259	fn extract_deep_emits_local_defs_for_multi_assign() {
260		let src = "package foo\nfunc Run() { x, y := 1, 2; _, _ = x, y }\n";
261		let g = extract_default("foo.go", src, &make_anchor(), true);
262		let names: Vec<&[u8]> = g
263			.defs()
264			.filter(|d| d.kind == b"local")
265			.map(|d| d.moniker.as_view().segments().last().unwrap().name)
266			.collect();
267		assert!(names.contains(&&b"x"[..]));
268		assert!(names.contains(&&b"y"[..]));
269	}
270
271	#[test]
272	fn extract_deep_emits_local_def_for_var_declaration() {
273		let src = "package foo\nfunc Run() { var z int = 5; _ = z }\n";
274		let g = extract_default("foo.go", src, &make_anchor(), true);
275		let lz = MonikerBuilder::new()
276			.project(b"app")
277			.segment(b"lang", b"go")
278			.segment(b"module", b"foo")
279			.segment(b"func", b"Run()")
280			.segment(b"local", b"z")
281			.build();
282		assert!(g.contains(&lz));
283	}
284
285	#[test]
286	fn extract_deep_emits_local_defs_for_range_vars() {
287		let src =
288			"package foo\nfunc Run(m map[string]int) { for k, v := range m { _, _ = k, v } }\n";
289		let g = extract_default("foo.go", src, &make_anchor(), true);
290		let names: Vec<&[u8]> = g
291			.defs()
292			.filter(|d| d.kind == b"local")
293			.map(|d| d.moniker.as_view().segments().last().unwrap().name)
294			.collect();
295		assert!(names.contains(&&b"k"[..]));
296		assert!(names.contains(&&b"v"[..]));
297	}
298
299	#[test]
300	fn extract_top_level_var_does_not_pollute_locals() {
301		let src = "package foo\nvar GlobalCount int\nfunc Run() { GlobalCount = 1 }\n";
302		let g = extract_default("foo.go", src, &make_anchor(), false);
303		let local_names: Vec<&[u8]> = g
304			.defs()
305			.filter(|d| d.kind == b"local")
306			.map(|d| d.moniker.as_view().segments().last().unwrap().name)
307			.collect();
308		assert!(
309			local_names.is_empty(),
310			"a package-level var must not be emitted as a local. found locals: {:?}",
311			local_names
312		);
313		let vars: Vec<&[u8]> = g
314			.defs()
315			.filter(|d| d.kind == b"var")
316			.map(|d| d.moniker.as_view().segments().last().unwrap().name)
317			.collect();
318		assert_eq!(vars, vec![&b"GlobalCount"[..]]);
319	}
320
321	#[test]
322	fn extract_deep_skips_blank_in_short_var() {
323		let src = "package foo\nfunc Run() { _, y := 1, 2; _ = y }\n";
324		let g = extract_default("foo.go", src, &make_anchor(), true);
325		let names: Vec<&[u8]> = g
326			.defs()
327			.filter(|d| d.kind == b"local")
328			.map(|d| d.moniker.as_view().segments().last().unwrap().name)
329			.collect();
330		assert_eq!(names, vec![&b"y"[..]]);
331	}
332}