Skip to content

Commit 1baa6fc

Browse files
unhappychoiceclaude
andcommitted
feat: add Go language support
This implementation adds comprehensive Go language support to gittype as part of issue #55. Changes include: - Added tree-sitter-go dependency for Go AST parsing - Extended Language enum to include Go - Implemented Go-specific code extraction for: - Functions (func declarations) - Methods (with receiver syntax) - Structs (type declarations with struct_type) - Interfaces (type declarations with interface_type) - Added Go file extension mapping (.go files) - Added Interface chunk type for Go interfaces - Enhanced name extraction to support field_identifier nodes - Added comprehensive test coverage for all Go language features All existing tests continue to pass, ensuring backward compatibility. Resolves #55 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 2ebf018 commit 1baa6fc

File tree

7 files changed

+192
-0
lines changed

7 files changed

+192
-0
lines changed

Cargo.lock

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ tree-sitter-rust = "0.20"
2525
tree-sitter-typescript = "0.20"
2626
tree-sitter-python = "0.20"
2727
tree-sitter-ruby = "0.20"
28+
tree-sitter-go = "0.20"
2829
crossterm = "0.27"
2930
ratatui = "0.26"
3031
rusqlite = { version = "0.29", features = ["bundled"] }

src/extractor/challenge_converter.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,7 @@ impl ChallengeConverter {
346346
super::Language::TypeScript => "typescript".to_string(),
347347
super::Language::Python => "python".to_string(),
348348
super::Language::Ruby => "ruby".to_string(),
349+
super::Language::Go => "go".to_string(),
349350
}
350351
}
351352
}

src/extractor/chunk.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ pub enum ChunkType {
77
Class,
88
Method,
99
Struct,
10+
Interface,
1011
Module,
1112
}
1213

src/extractor/language.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ pub enum Language {
44
TypeScript,
55
Python,
66
Ruby,
7+
Go,
78
}
89

910
impl Language {
@@ -13,6 +14,7 @@ impl Language {
1314
"ts" | "tsx" => Some(Language::TypeScript),
1415
"py" => Some(Language::Python),
1516
"rb" => Some(Language::Ruby),
17+
"go" => Some(Language::Go),
1618
_ => None,
1719
}
1820
}
@@ -23,6 +25,7 @@ impl Language {
2325
Language::TypeScript => "ts",
2426
Language::Python => "py",
2527
Language::Ruby => "rb",
28+
Language::Go => "go",
2629
}
2730
}
2831
}

src/extractor/parser.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ impl Default for ExtractionOptions {
2222
"**/*.tsx".to_string(),
2323
"**/*.py".to_string(),
2424
"**/*.rb".to_string(),
25+
"**/*.go".to_string(),
2526
],
2627
exclude_patterns: vec![
2728
"**/target/**".to_string(),
@@ -83,6 +84,16 @@ impl CodeExtractor {
8384
))
8485
})?;
8586
}
87+
Language::Go => {
88+
parser
89+
.set_language(tree_sitter_go::language())
90+
.map_err(|e| {
91+
GitTypeError::ExtractionFailed(format!(
92+
"Failed to set Go language: {}",
93+
e
94+
))
95+
})?;
96+
}
8697
}
8798
Ok(parser)
8899
}
@@ -269,6 +280,12 @@ impl CodeExtractor {
269280
(class name: (constant) @name) @class
270281
(module name: (constant) @name) @module
271282
",
283+
Language::Go => "
284+
(function_declaration name: (identifier) @name) @function
285+
(method_declaration receiver: _ name: (field_identifier) @name) @method
286+
(type_spec name: (type_identifier) @name type: (struct_type)) @struct
287+
(type_spec name: (type_identifier) @name type: (interface_type)) @interface
288+
",
272289
};
273290

274291
let query = Query::new(tree.language(), query_str).map_err(|e| {
@@ -351,6 +368,7 @@ impl CodeExtractor {
351368
"method" => ChunkType::Method,
352369
"class" | "impl" => ChunkType::Class,
353370
"struct" => ChunkType::Struct,
371+
"interface" => ChunkType::Interface,
354372
"module" => ChunkType::Module,
355373
"arrow_function" => ChunkType::Function,
356374
"function_expression" => ChunkType::Function,
@@ -420,6 +438,7 @@ impl CodeExtractor {
420438
if child.kind() == "identifier"
421439
|| child.kind() == "type_identifier"
422440
|| child.kind() == "property_identifier"
441+
|| child.kind() == "field_identifier"
423442
|| child.kind() == "constant"
424443
{
425444
let start = child.start_byte();
@@ -566,6 +585,7 @@ impl CodeExtractor {
566585
Language::TypeScript => "(comment) @comment",
567586
Language::Python => "(comment) @comment",
568587
Language::Ruby => "(comment) @comment",
588+
Language::Go => "(comment) @comment",
569589
};
570590

571591
let query = match Query::new(tree.language(), comment_query) {
@@ -589,6 +609,7 @@ impl CodeExtractor {
589609
Language::TypeScript => node_kind == "comment",
590610
Language::Python => node_kind == "comment",
591611
Language::Ruby => node_kind == "comment",
612+
Language::Go => node_kind == "comment",
592613
};
593614

594615
if !is_valid_comment {

tests/extractor_unit_tests.rs

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ fn test_language_from_extension() {
2525
assert_eq!(Language::from_extension("tsx"), Some(Language::TypeScript));
2626
assert_eq!(Language::from_extension("py"), Some(Language::Python));
2727
assert_eq!(Language::from_extension("rb"), Some(Language::Ruby));
28+
assert_eq!(Language::from_extension("go"), Some(Language::Go));
2829
assert_eq!(Language::from_extension("unknown"), None);
2930
}
3031

@@ -455,3 +456,156 @@ end
455456
assert!(method_names.contains(&&"login".to_string()));
456457
assert!(method_names.contains(&&"logout".to_string()));
457458
}
459+
460+
#[test]
461+
fn test_go_function_extraction() {
462+
let temp_dir = TempDir::new().unwrap();
463+
let file_path = temp_dir.path().join("test.go");
464+
465+
let go_code = r#"package main
466+
467+
import "fmt"
468+
469+
func main() {
470+
fmt.Println("Hello, world!")
471+
}
472+
473+
func add(a, b int) int {
474+
return a + b
475+
}
476+
477+
func multiply(x int, y int) int {
478+
return x * y
479+
}
480+
"#;
481+
fs::write(&file_path, go_code).unwrap();
482+
483+
let mut extractor = CodeExtractor::new().unwrap();
484+
let chunks = extractor
485+
.extract_chunks(temp_dir.path(), ExtractionOptions::default())
486+
.unwrap();
487+
488+
assert_eq!(chunks.len(), 3);
489+
490+
let function_names: Vec<&String> = chunks.iter().map(|c| &c.name).collect();
491+
assert!(function_names.contains(&&"main".to_string()));
492+
assert!(function_names.contains(&&"add".to_string()));
493+
assert!(function_names.contains(&&"multiply".to_string()));
494+
495+
for chunk in &chunks {
496+
assert!(matches!(chunk.chunk_type, ChunkType::Function));
497+
assert_eq!(chunk.language, Language::Go);
498+
}
499+
}
500+
501+
#[test]
502+
fn test_go_struct_extraction() {
503+
let temp_dir = TempDir::new().unwrap();
504+
let file_path = temp_dir.path().join("test.go");
505+
506+
let go_code = r#"package main
507+
508+
type Person struct {
509+
Name string
510+
Age int
511+
}
512+
513+
type Address struct {
514+
Street string
515+
City string
516+
Zip string
517+
}
518+
519+
func (p Person) GetName() string {
520+
return p.Name
521+
}
522+
523+
func (a *Address) GetFullAddress() string {
524+
return a.Street + ", " + a.City + " " + a.Zip
525+
}
526+
"#;
527+
fs::write(&file_path, go_code).unwrap();
528+
529+
let mut extractor = CodeExtractor::new().unwrap();
530+
let chunks = extractor
531+
.extract_chunks(temp_dir.path(), ExtractionOptions::default())
532+
.unwrap();
533+
534+
assert_eq!(chunks.len(), 4); // 2 structs + 2 methods
535+
536+
// Find struct chunks
537+
let struct_chunks: Vec<_> = chunks
538+
.iter()
539+
.filter(|c| matches!(c.chunk_type, ChunkType::Struct))
540+
.collect();
541+
assert_eq!(struct_chunks.len(), 2);
542+
543+
let struct_names: Vec<&String> = struct_chunks.iter().map(|c| &c.name).collect();
544+
assert!(struct_names.contains(&&"Person".to_string()));
545+
assert!(struct_names.contains(&&"Address".to_string()));
546+
547+
// Find method chunks
548+
let method_chunks: Vec<_> = chunks
549+
.iter()
550+
.filter(|c| matches!(c.chunk_type, ChunkType::Method))
551+
.collect();
552+
assert_eq!(method_chunks.len(), 2);
553+
554+
let method_names: Vec<&String> = method_chunks.iter().map(|c| &c.name).collect();
555+
assert!(method_names.contains(&&"GetName".to_string()));
556+
assert!(method_names.contains(&&"GetFullAddress".to_string()));
557+
}
558+
559+
#[test]
560+
fn test_go_interface_extraction() {
561+
let temp_dir = TempDir::new().unwrap();
562+
let file_path = temp_dir.path().join("test.go");
563+
564+
let go_code = r#"package main
565+
566+
type Writer interface {
567+
Write([]byte) (int, error)
568+
}
569+
570+
type Reader interface {
571+
Read([]byte) (int, error)
572+
}
573+
574+
type ReadWriter interface {
575+
Reader
576+
Writer
577+
}
578+
579+
func process(rw ReadWriter) {
580+
// Implementation here
581+
}
582+
"#;
583+
fs::write(&file_path, go_code).unwrap();
584+
585+
let mut extractor = CodeExtractor::new().unwrap();
586+
let chunks = extractor
587+
.extract_chunks(temp_dir.path(), ExtractionOptions::default())
588+
.unwrap();
589+
590+
assert_eq!(chunks.len(), 4); // 3 interfaces + 1 function
591+
592+
// Find interface chunks
593+
let interface_chunks: Vec<_> = chunks
594+
.iter()
595+
.filter(|c| matches!(c.chunk_type, ChunkType::Interface))
596+
.collect();
597+
assert_eq!(interface_chunks.len(), 3);
598+
599+
let interface_names: Vec<&String> = interface_chunks.iter().map(|c| &c.name).collect();
600+
assert!(interface_names.contains(&&"Writer".to_string()));
601+
assert!(interface_names.contains(&&"Reader".to_string()));
602+
assert!(interface_names.contains(&&"ReadWriter".to_string()));
603+
604+
// Find function chunk
605+
let function_chunks: Vec<_> = chunks
606+
.iter()
607+
.filter(|c| matches!(c.chunk_type, ChunkType::Function))
608+
.collect();
609+
assert_eq!(function_chunks.len(), 1);
610+
assert_eq!(function_chunks[0].name, "process");
611+
}

0 commit comments

Comments
 (0)