diff --git a/packages/core/src/scanner/README.md b/packages/core/src/scanner/README.md index 8cd0258..75cbaef 100644 --- a/packages/core/src/scanner/README.md +++ b/packages/core/src/scanner/README.md @@ -230,6 +230,142 @@ import { MyClass } from './lib'; ] ``` +### Example 3: Scanning Go Files + +**Input:** `server/handler.go` +```go +package server + +// Handler processes incoming requests. +type Handler struct { + name string +} + +// NewHandler creates a new Handler instance. +func NewHandler(name string) *Handler { + return &Handler{name: name} +} + +// Process handles a request and returns a response. +func (h *Handler) Process(req Request) (Response, error) { + // processing logic + return Response{}, nil +} + +// Stack is a generic stack data structure. +type Stack[T any] struct { + items []T +} + +// Push adds an item to the stack. +func (s *Stack[T]) Push(item T) { + s.items = append(s.items, item) +} +``` + +**Output:** +```json +[ + { + "id": "server/handler.go:Handler:4", + "text": "struct Handler\ntype Handler struct\nHandler processes incoming requests.", + "type": "class", + "language": "go", + "metadata": { + "file": "server/handler.go", + "startLine": 4, + "endLine": 6, + "name": "Handler", + "signature": "type Handler struct", + "exported": true, + "docstring": "Handler processes incoming requests." + } + }, + { + "id": "server/handler.go:NewHandler:9", + "text": "function NewHandler\nfunc NewHandler(name string) *Handler\nNewHandler creates a new Handler instance.", + "type": "function", + "language": "go", + "metadata": { + "file": "server/handler.go", + "startLine": 9, + "endLine": 11, + "name": "NewHandler", + "signature": "func NewHandler(name string) *Handler", + "exported": true, + "docstring": "NewHandler creates a new Handler instance." + } + }, + { + "id": "server/handler.go:Handler.Process:14", + "text": "method Handler.Process\nfunc (h *Handler) Process(req Request) (Response, error)\nProcess handles a request and returns a response.", + "type": "method", + "language": "go", + "metadata": { + "file": "server/handler.go", + "startLine": 14, + "endLine": 17, + "name": "Handler.Process", + "signature": "func (h *Handler) Process(req Request) (Response, error)", + "exported": true, + "docstring": "Process handles a request and returns a response.", + "custom": { + "receiver": "Handler", + "receiverPointer": true + } + } + }, + { + "id": "server/handler.go:Stack:20", + "text": "struct Stack\ntype Stack[T any] struct\nStack is a generic stack data structure.", + "type": "class", + "language": "go", + "metadata": { + "file": "server/handler.go", + "startLine": 20, + "endLine": 22, + "name": "Stack", + "signature": "type Stack[T any] struct", + "exported": true, + "docstring": "Stack is a generic stack data structure.", + "custom": { + "isGeneric": true, + "typeParameters": ["T any"] + } + } + }, + { + "id": "server/handler.go:Stack.Push:25", + "text": "method Stack.Push\nfunc (s *Stack[T]) Push(item T)\nPush adds an item to the stack.", + "type": "method", + "language": "go", + "metadata": { + "file": "server/handler.go", + "startLine": 25, + "endLine": 27, + "name": "Stack.Push", + "signature": "func (s *Stack[T]) Push(item T)", + "exported": true, + "docstring": "Push adds an item to the stack.", + "custom": { + "receiver": "Stack", + "receiverPointer": true, + "isGeneric": true + } + } + } +] +``` + +**Go Scanner Features:** +- Functions, methods, structs, interfaces, type aliases +- Doc comments (Go-style `//` comments preceding declarations) +- Receiver method extraction with pointer/value distinction +- Go generics (Go 1.18+) with type parameter tracking +- Exported/unexported detection (capitalization) +- Generated file skipping (`// Code generated` header) +- Test file detection (`*_test.go` → `isTest: true`) + ### Example 3: Full Repository Scan ```typescript @@ -374,7 +510,7 @@ const utilDocs = result.documents.filter( | TypeScript | `TypeScriptScanner` | Functions, classes, methods, interfaces, types, arrow functions, exported constants, JSDoc | ✅ Implemented | | JavaScript | `TypeScriptScanner` | Functions, classes, methods, arrow functions, exported constants, JSDoc | ✅ Implemented (via .ts scanner) | | Markdown | `MarkdownScanner` | Documentation sections, code blocks | ✅ Implemented | -| Go | - | Functions, structs, interfaces | 🔄 Planned (tree-sitter) | +| Go | `GoScanner` | Functions, methods, structs, interfaces, types, constants, generics, doc comments | ✅ Implemented (tree-sitter) | | Python | - | Functions, classes, docstrings | 🔄 Planned (tree-sitter) | | Rust | - | Functions, structs, traits | 🔄 Planned (tree-sitter) | @@ -490,7 +626,9 @@ registry.register(new GoScanner()); - [x] TypeScript scanner with ts-morph - [x] Markdown scanner with remark - [x] Scanner registry and auto-detection -- [ ] Tree-sitter integration for Go, Python, Rust +- [x] Go scanner with tree-sitter (functions, methods, structs, interfaces, generics) +- [ ] Python scanner with tree-sitter +- [ ] Rust scanner with tree-sitter - [ ] Enhanced JavaScript support (JSX, Flow) - [ ] Configuration file support - [ ] Incremental scanning (hash-based) diff --git a/packages/core/src/scanner/__tests__/fixtures/go/edge_cases.go b/packages/core/src/scanner/__tests__/fixtures/go/edge_cases.go new file mode 100644 index 0000000..127f069 --- /dev/null +++ b/packages/core/src/scanner/__tests__/fixtures/go/edge_cases.go @@ -0,0 +1,123 @@ +//go:build linux && amd64 +// +build linux,amd64 + +// Package edgecases tests various Go edge cases for the scanner. +package edgecases + +import ( + "context" + "io" +) + +// init functions should be extracted +func init() { + // Package initialization +} + +// Multiple init functions are allowed +func init() { + // Another init +} + +// Blank identifier for interface compliance check +var _ io.Reader = (*MyReader)(nil) +var _ io.Writer = (*MyWriter)(nil) + +// MyReader implements io.Reader. +type MyReader struct { + data []byte +} + +// Read implements io.Reader. +func (r *MyReader) Read(p []byte) (n int, err error) { + return copy(p, r.data), nil +} + +// MyWriter implements io.Writer. +type MyWriter struct{} + +// Write implements io.Writer. +func (w *MyWriter) Write(p []byte) (n int, err error) { + return len(p), nil +} + +// Embedded struct example +type Base struct { + ID string + Name string +} + +// Extended embeds Base. +type Extended struct { + Base // Embedded + ExtraField int // Additional field +} + +// Multiple variable declarations +var ( + DefaultTimeout = 30 + MaxRetries = 3 + MinWorkers = 1 +) + +// Multiple const declarations +const ( + StatusPending = "pending" + StatusRunning = "running" + StatusComplete = "complete" +) + +// Iota usage +const ( + Sunday = iota + Monday + Tuesday + Wednesday + Thursday + Friday + Saturday +) + +// Function with context (common pattern) +func DoWork(ctx context.Context) error { + select { + case <-ctx.Done(): + return ctx.Err() + default: + return nil + } +} + +// Variadic function +func Sum(numbers ...int) int { + total := 0 + for _, n := range numbers { + total += n + } + return total +} + +// Function returning multiple values +func Divide(a, b int) (int, int, error) { + if b == 0 { + return 0, 0, io.EOF // Using io.EOF as placeholder error + } + return a / b, a % b, nil +} + +// Named return values +func ParseConfig(data []byte) (config *Base, err error) { + config = &Base{} + // parsing logic + return config, nil +} + +// unexportedType should still be detected +type unexportedType struct { + field string +} + +// unexportedFunc should still be detected +func unexportedFunc() string { + return "unexported" +} diff --git a/packages/core/src/scanner/__tests__/go.test.ts b/packages/core/src/scanner/__tests__/go.test.ts index 8115e11..b93d68f 100644 --- a/packages/core/src/scanner/__tests__/go.test.ts +++ b/packages/core/src/scanner/__tests__/go.test.ts @@ -354,4 +354,127 @@ describe('GoScanner', () => { }); }); }); + + describe('edge cases', () => { + let edgeCaseDocuments: Document[]; + + beforeAll(async () => { + edgeCaseDocuments = await scanner.scan(['edge_cases.go'], fixturesDir); + }); + + describe('init functions', () => { + it('should extract init functions', () => { + const initFuncs = edgeCaseDocuments.filter( + (d) => d.metadata.name === 'init' && d.type === 'function' + ); + // Go allows multiple init functions + expect(initFuncs.length).toBeGreaterThanOrEqual(1); + }); + + it('should mark init as unexported', () => { + const initFunc = edgeCaseDocuments.find( + (d) => d.metadata.name === 'init' && d.type === 'function' + ); + expect(initFunc?.metadata.exported).toBe(false); + }); + }); + + describe('embedded structs', () => { + it('should extract struct with embedded field', () => { + const extended = edgeCaseDocuments.find( + (d) => d.metadata.name === 'Extended' && d.type === 'class' + ); + expect(extended).toBeDefined(); + expect(extended?.metadata.snippet).toContain('Base'); + }); + }); + + describe('multiple declarations', () => { + it('should extract multiple const declarations', () => { + const statusConsts = edgeCaseDocuments.filter( + (d) => + d.type === 'variable' && + d.metadata.custom?.isConstant && + d.metadata.name?.startsWith('Status') + ); + expect(statusConsts.length).toBeGreaterThanOrEqual(3); + }); + + it('should extract iota-based constants', () => { + const dayConsts = edgeCaseDocuments.filter( + (d) => + d.type === 'variable' && + d.metadata.custom?.isConstant && + ['Sunday', 'Monday', 'Tuesday'].includes(d.metadata.name || '') + ); + expect(dayConsts.length).toBeGreaterThanOrEqual(1); + }); + }); + + describe('function variations', () => { + it('should extract variadic function', () => { + const sum = edgeCaseDocuments.find( + (d) => d.metadata.name === 'Sum' && d.type === 'function' + ); + expect(sum).toBeDefined(); + expect(sum?.metadata.signature).toContain('...'); + }); + + it('should extract function with context parameter', () => { + const doWork = edgeCaseDocuments.find( + (d) => d.metadata.name === 'DoWork' && d.type === 'function' + ); + expect(doWork).toBeDefined(); + expect(doWork?.metadata.signature).toContain('context.Context'); + }); + + it('should extract function with multiple return values', () => { + const divide = edgeCaseDocuments.find( + (d) => d.metadata.name === 'Divide' && d.type === 'function' + ); + expect(divide).toBeDefined(); + expect(divide?.metadata.signature).toContain('(int, int, error)'); + }); + + it('should extract function with named return values', () => { + const parseConfig = edgeCaseDocuments.find( + (d) => d.metadata.name === 'ParseConfig' && d.type === 'function' + ); + expect(parseConfig).toBeDefined(); + expect(parseConfig?.metadata.signature).toContain('config'); + }); + }); + + describe('unexported items', () => { + it('should extract unexported struct', () => { + const unexported = edgeCaseDocuments.find( + (d) => d.metadata.name === 'unexportedType' && d.type === 'class' + ); + expect(unexported).toBeDefined(); + expect(unexported?.metadata.exported).toBe(false); + }); + + it('should extract unexported function', () => { + const unexported = edgeCaseDocuments.find( + (d) => d.metadata.name === 'unexportedFunc' && d.type === 'function' + ); + expect(unexported).toBeDefined(); + expect(unexported?.metadata.exported).toBe(false); + }); + }); + + describe('interface implementations', () => { + it('should extract types that implement interfaces', () => { + const myReader = edgeCaseDocuments.find( + (d) => d.metadata.name === 'MyReader' && d.type === 'class' + ); + expect(myReader).toBeDefined(); + + const readMethod = edgeCaseDocuments.find( + (d) => d.metadata.name === 'MyReader.Read' && d.type === 'method' + ); + expect(readMethod).toBeDefined(); + }); + }); + }); });