go.rs
1 use crate::languages::LanguageSupport; 2 use crate::types::EnvSourceKind; 3 use std::sync::OnceLock; 4 use tracing::error; 5 use tree_sitter::{Language, Node, Query}; 6 7 pub struct Go; 8 9 static REFERENCE_QUERY: OnceLock<Query> = OnceLock::new(); 10 static BINDING_QUERY: OnceLock<Query> = OnceLock::new(); 11 static IMPORT_QUERY: OnceLock<Query> = OnceLock::new(); 12 static COMPLETION_QUERY: OnceLock<Query> = OnceLock::new(); 13 static REASSIGNMENT_QUERY: OnceLock<Query> = OnceLock::new(); 14 static IDENTIFIER_QUERY: OnceLock<Query> = OnceLock::new(); 15 static EXPORT_QUERY: OnceLock<Query> = OnceLock::new(); 16 17 static ASSIGNMENT_QUERY: OnceLock<Query> = OnceLock::new(); 18 static DESTRUCTURE_QUERY: OnceLock<Query> = OnceLock::new(); 19 static SCOPE_QUERY: OnceLock<Query> = OnceLock::new(); 20 21 /// Compiles a tree-sitter query and fails fast on errors to surface invalid language query definitions early. 22 fn compile_query(grammar: &Language, source: &str, query_name: &str) -> Query { 23 match Query::new(grammar, source) { 24 Ok(query) => query, 25 Err(e) => { 26 error!( 27 language = "go", 28 query = query_name, 29 error = %e, 30 "Failed to compile query, failing fast" 31 ); 32 panic!("Failed to compile query '{}': {}", query_name, e) 33 } 34 } 35 } 36 37 impl LanguageSupport for Go { 38 fn id(&self) -> &'static str { 39 "go" 40 } 41 42 fn is_standard_env_object(&self, name: &str) -> bool { 43 name == "os" 44 } 45 46 fn extensions(&self) -> &'static [&'static str] { 47 &["go"] 48 } 49 50 fn language_ids(&self) -> &'static [&'static str] { 51 &["go"] 52 } 53 54 fn grammar(&self) -> Language { 55 tree_sitter_go::LANGUAGE.into() 56 } 57 58 fn reference_query(&self) -> &Query { 59 REFERENCE_QUERY.get_or_init(|| { 60 compile_query( 61 &self.grammar(), 62 include_str!("../../queries/go/references.scm"), 63 "references", 64 ) 65 }) 66 } 67 68 fn binding_query(&self) -> Option<&Query> { 69 Some(BINDING_QUERY.get_or_init(|| { 70 compile_query( 71 &self.grammar(), 72 include_str!("../../queries/go/bindings.scm"), 73 "bindings", 74 ) 75 })) 76 } 77 78 fn import_query(&self) -> Option<&Query> { 79 Some(IMPORT_QUERY.get_or_init(|| { 80 compile_query( 81 &self.grammar(), 82 include_str!("../../queries/go/imports.scm"), 83 "imports", 84 ) 85 })) 86 } 87 88 fn completion_query(&self) -> Option<&Query> { 89 Some(COMPLETION_QUERY.get_or_init(|| { 90 compile_query( 91 &self.grammar(), 92 include_str!("../../queries/go/completion.scm"), 93 "completion", 94 ) 95 })) 96 } 97 98 fn reassignment_query(&self) -> Option<&Query> { 99 Some(REASSIGNMENT_QUERY.get_or_init(|| { 100 compile_query( 101 &self.grammar(), 102 include_str!("../../queries/go/reassignments.scm"), 103 "reassignments", 104 ) 105 })) 106 } 107 108 fn identifier_query(&self) -> Option<&Query> { 109 Some(IDENTIFIER_QUERY.get_or_init(|| { 110 compile_query( 111 &self.grammar(), 112 include_str!("../../queries/go/identifiers.scm"), 113 "identifiers", 114 ) 115 })) 116 } 117 118 fn export_query(&self) -> Option<&Query> { 119 Some(EXPORT_QUERY.get_or_init(|| { 120 compile_query( 121 &self.grammar(), 122 include_str!("../../queries/go/exports.scm"), 123 "exports", 124 ) 125 })) 126 } 127 128 fn assignment_query(&self) -> Option<&Query> { 129 Some(ASSIGNMENT_QUERY.get_or_init(|| { 130 compile_query( 131 &self.grammar(), 132 include_str!("../../queries/go/assignments.scm"), 133 "assignments", 134 ) 135 })) 136 } 137 138 fn destructure_query(&self) -> Option<&Query> { 139 Some(DESTRUCTURE_QUERY.get_or_init(|| { 140 compile_query( 141 &self.grammar(), 142 include_str!("../../queries/go/destructures.scm"), 143 "destructures", 144 ) 145 })) 146 } 147 148 fn scope_query(&self) -> Option<&Query> { 149 Some(SCOPE_QUERY.get_or_init(|| { 150 compile_query( 151 &self.grammar(), 152 include_str!("../../queries/go/scopes.scm"), 153 "scopes", 154 ) 155 })) 156 } 157 158 fn is_env_source_node(&self, node: Node, source: &[u8]) -> Option<EnvSourceKind> { 159 if node.kind() == "identifier" { 160 let text = node.utf8_text(source).ok()?; 161 if text == "os" { 162 return Some(EnvSourceKind::Object { 163 canonical_name: "os".into(), 164 }); 165 } 166 } 167 168 None 169 } 170 171 fn known_env_modules(&self) -> &'static [&'static str] { 172 &["os"] 173 } 174 175 fn completion_trigger_characters(&self) -> &'static [&'static str] { 176 &["(\"", "('"] 177 } 178 179 fn is_scope_node(&self, node: tree_sitter::Node) -> bool { 180 matches!( 181 node.kind(), 182 "function_declaration" 183 | "method_declaration" 184 | "func_literal" 185 | "block" 186 | "for_statement" 187 | "if_statement" 188 | "switch_statement" 189 | "select_statement" 190 ) 191 } 192 193 fn extract_var_name( 194 &self, 195 node: tree_sitter::Node, 196 source: &[u8], 197 ) -> Option<compact_str::CompactString> { 198 use compact_str::CompactString; 199 node.utf8_text(source) 200 .ok() 201 .map(|s| CompactString::from(self.strip_quotes(s))) 202 } 203 204 fn strip_quotes<'a>(&self, text: &'a str) -> &'a str { 205 text.trim_matches(|c| c == '"' || c == '\'' || c == '`') 206 } 207 208 fn extract_property_access( 209 &self, 210 tree: &tree_sitter::Tree, 211 content: &str, 212 byte_offset: usize, 213 ) -> Option<(compact_str::CompactString, compact_str::CompactString)> { 214 let node = tree 215 .root_node() 216 .descendant_for_byte_range(byte_offset, byte_offset)?; 217 218 let selector = if node.kind() == "selector_expression" { 219 node 220 } else if let Some(parent) = node.parent() { 221 if parent.kind() == "selector_expression" { 222 parent 223 } else { 224 return None; 225 } 226 } else { 227 return None; 228 }; 229 230 let operand_node = selector.child_by_field_name("operand")?; 231 let field_node = selector.child_by_field_name("field")?; 232 233 if operand_node.kind() != "identifier" { 234 return None; 235 } 236 237 let object_name = operand_node.utf8_text(content.as_bytes()).ok()?; 238 let property_name = field_node.utf8_text(content.as_bytes()).ok()?; 239 240 Some((object_name.into(), property_name.into())) 241 } 242 } 243 244 #[cfg(test)] 245 mod tests { 246 use super::*; 247 248 fn get_go() -> Go { 249 Go 250 } 251 252 #[test] 253 fn test_id() { 254 assert_eq!(get_go().id(), "go"); 255 } 256 257 #[test] 258 fn test_extensions() { 259 let exts = get_go().extensions(); 260 assert!(exts.contains(&"go")); 261 } 262 263 #[test] 264 fn test_language_ids() { 265 let ids = get_go().language_ids(); 266 assert!(ids.contains(&"go")); 267 } 268 269 #[test] 270 fn test_is_standard_env_object() { 271 let go = get_go(); 272 assert!(go.is_standard_env_object("os")); 273 assert!(!go.is_standard_env_object("process")); 274 assert!(!go.is_standard_env_object("something.else")); 275 } 276 277 #[test] 278 fn test_known_env_modules() { 279 let modules = get_go().known_env_modules(); 280 assert!(modules.contains(&"os")); 281 } 282 283 #[test] 284 fn test_grammar_compiles() { 285 let go = get_go(); 286 let _grammar = go.grammar(); 287 } 288 289 #[test] 290 fn test_reference_query_compiles() { 291 let go = get_go(); 292 let _query = go.reference_query(); 293 } 294 295 #[test] 296 fn test_binding_query_compiles() { 297 let go = get_go(); 298 assert!(go.binding_query().is_some()); 299 } 300 301 #[test] 302 fn test_import_query_compiles() { 303 let go = get_go(); 304 assert!(go.import_query().is_some()); 305 } 306 307 #[test] 308 fn test_completion_query_compiles() { 309 let go = get_go(); 310 assert!(go.completion_query().is_some()); 311 } 312 313 #[test] 314 fn test_reassignment_query_compiles() { 315 let go = get_go(); 316 assert!(go.reassignment_query().is_some()); 317 } 318 319 #[test] 320 fn test_identifier_query_compiles() { 321 let go = get_go(); 322 assert!(go.identifier_query().is_some()); 323 } 324 325 #[test] 326 fn test_export_query_compiles() { 327 let go = get_go(); 328 assert!(go.export_query().is_some()); 329 } 330 331 #[test] 332 fn test_assignment_query_compiles() { 333 let go = get_go(); 334 assert!(go.assignment_query().is_some()); 335 } 336 337 #[test] 338 fn test_scope_query_compiles() { 339 let go = get_go(); 340 assert!(go.scope_query().is_some()); 341 } 342 343 #[test] 344 fn test_destructure_query_compiles() { 345 let go = get_go(); 346 assert!(go.destructure_query().is_some()); 347 } 348 349 #[test] 350 fn test_strip_quotes() { 351 let go = get_go(); 352 assert_eq!(go.strip_quotes("\"hello\""), "hello"); 353 assert_eq!(go.strip_quotes("'a'"), "a"); 354 assert_eq!(go.strip_quotes("`raw`"), "raw"); 355 assert_eq!(go.strip_quotes("noquotes"), "noquotes"); 356 } 357 358 #[test] 359 fn test_is_env_source_node_os() { 360 let go = get_go(); 361 let mut parser = tree_sitter::Parser::new(); 362 parser.set_language(&go.grammar()).unwrap(); 363 364 let code = "package main\nimport \"os\"\nfunc main() { os.Getenv(\"VAR\") }"; 365 let tree = parser.parse(code, None).unwrap(); 366 let root = tree.root_node(); 367 368 fn walk_tree(cursor: &mut tree_sitter::TreeCursor, go: &Go, code: &str) -> bool { 369 loop { 370 let node = cursor.node(); 371 if node.kind() == "identifier" { 372 if let Some(kind) = go.is_env_source_node(node, code.as_bytes()) { 373 if let EnvSourceKind::Object { canonical_name } = kind { 374 if canonical_name == "os" { 375 return true; 376 } 377 } 378 } 379 } 380 381 if cursor.goto_first_child() { 382 if walk_tree(cursor, go, code) { 383 return true; 384 } 385 cursor.goto_parent(); 386 } 387 388 if !cursor.goto_next_sibling() { 389 break; 390 } 391 } 392 false 393 } 394 395 let mut cursor = root.walk(); 396 let found = walk_tree(&mut cursor, &go, code); 397 assert!(found, "Should detect os as env source"); 398 } 399 400 #[test] 401 fn test_extract_property_access() { 402 let go = get_go(); 403 let mut parser = tree_sitter::Parser::new(); 404 parser.set_language(&go.grammar()).unwrap(); 405 406 let code = "package main\nfunc main() { env.DATABASE_URL }"; 407 let tree = parser.parse(code, None).unwrap(); 408 409 let offset = code.find("DATABASE_URL").unwrap(); 410 let result = go.extract_property_access(&tree, code, offset); 411 assert!(result.is_some()); 412 let (obj, prop) = result.unwrap(); 413 assert_eq!(obj.as_str(), "env"); 414 assert_eq!(prop.as_str(), "DATABASE_URL"); 415 } 416 417 #[test] 418 fn test_is_scope_node() { 419 let go = get_go(); 420 let mut parser = tree_sitter::Parser::new(); 421 parser.set_language(&go.grammar()).unwrap(); 422 423 let code = "package main\nfunc test() {}"; 424 let tree = parser.parse(code, None).unwrap(); 425 let root = tree.root_node(); 426 427 fn find_node_of_kind<'a>( 428 node: tree_sitter::Node<'a>, 429 kind: &str, 430 ) -> Option<tree_sitter::Node<'a>> { 431 if node.kind() == kind { 432 return Some(node); 433 } 434 for i in 0..node.child_count() { 435 if let Some(child) = node.child(i) { 436 if let Some(found) = find_node_of_kind(child, kind) { 437 return Some(found); 438 } 439 } 440 } 441 None 442 } 443 444 if let Some(func) = find_node_of_kind(root, "function_declaration") { 445 assert!(go.is_scope_node(func)); 446 } 447 } 448 449 #[test] 450 fn test_extract_var_name() { 451 let go = get_go(); 452 let mut parser = tree_sitter::Parser::new(); 453 parser.set_language(&go.grammar()).unwrap(); 454 455 let code = "package main\nconst VAR = \"value\""; 456 let tree = parser.parse(code, None).unwrap(); 457 let root = tree.root_node(); 458 459 fn find_node_of_kind<'a>( 460 node: tree_sitter::Node<'a>, 461 kind: &str, 462 ) -> Option<tree_sitter::Node<'a>> { 463 if node.kind() == kind { 464 return Some(node); 465 } 466 for i in 0..node.child_count() { 467 if let Some(child) = node.child(i) { 468 if let Some(found) = find_node_of_kind(child, kind) { 469 return Some(found); 470 } 471 } 472 } 473 None 474 } 475 476 if let Some(str_lit) = find_node_of_kind(root, "interpreted_string_literal") { 477 let name = go.extract_var_name(str_lit, code.as_bytes()); 478 assert!(name.is_some()); 479 assert_eq!(name.unwrap().as_str(), "value"); 480 } 481 } 482 }