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}