Add AST analysis tools and go run/test execution tools
This commit is contained in:
129
tool_find_method_receivers.go
Normal file
129
tool_find_method_receivers.go
Normal file
@@ -0,0 +1,129 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/token"
|
||||
)
|
||||
|
||||
type MethodReceiver struct {
|
||||
TypeName string `json:"type_name"`
|
||||
MethodName string `json:"method_name"`
|
||||
ReceiverType string `json:"receiver_type"` // "pointer" or "value"
|
||||
ReceiverName string `json:"receiver_name"`
|
||||
Position Position `json:"position"`
|
||||
}
|
||||
|
||||
type ReceiverAnalysis struct {
|
||||
Methods []MethodReceiver `json:"methods"`
|
||||
Issues []ReceiverIssue `json:"issues"`
|
||||
}
|
||||
|
||||
type ReceiverIssue struct {
|
||||
Type string `json:"type"`
|
||||
Description string `json:"description"`
|
||||
Position Position `json:"position"`
|
||||
}
|
||||
|
||||
func findMethodReceivers(dir string) (*ReceiverAnalysis, error) {
|
||||
analysis := &ReceiverAnalysis{
|
||||
Methods: []MethodReceiver{},
|
||||
Issues: []ReceiverIssue{},
|
||||
}
|
||||
|
||||
typeReceivers := make(map[string]map[string]bool) // type -> receiver type -> exists
|
||||
|
||||
err := walkGoFiles(dir, func(path string, src []byte, file *ast.File, fset *token.FileSet) error {
|
||||
ast.Inspect(file, func(n ast.Node) bool {
|
||||
if funcDecl, ok := n.(*ast.FuncDecl); ok && funcDecl.Recv != nil {
|
||||
if len(funcDecl.Recv.List) > 0 {
|
||||
recv := funcDecl.Recv.List[0]
|
||||
var typeName string
|
||||
var receiverType string
|
||||
var receiverName string
|
||||
|
||||
if len(recv.Names) > 0 {
|
||||
receiverName = recv.Names[0].Name
|
||||
}
|
||||
|
||||
switch t := recv.Type.(type) {
|
||||
case *ast.Ident:
|
||||
typeName = t.Name
|
||||
receiverType = "value"
|
||||
case *ast.StarExpr:
|
||||
if ident, ok := t.X.(*ast.Ident); ok {
|
||||
typeName = ident.Name
|
||||
receiverType = "pointer"
|
||||
}
|
||||
}
|
||||
|
||||
if typeName != "" {
|
||||
pos := fset.Position(funcDecl.Pos())
|
||||
method := MethodReceiver{
|
||||
TypeName: typeName,
|
||||
MethodName: funcDecl.Name.Name,
|
||||
ReceiverType: receiverType,
|
||||
ReceiverName: receiverName,
|
||||
Position: newPosition(pos),
|
||||
}
|
||||
analysis.Methods = append(analysis.Methods, method)
|
||||
|
||||
// Track receiver types per type
|
||||
if typeReceivers[typeName] == nil {
|
||||
typeReceivers[typeName] = make(map[string]bool)
|
||||
}
|
||||
typeReceivers[typeName][receiverType] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Analyze for inconsistencies
|
||||
for typeName, receivers := range typeReceivers {
|
||||
if receivers["pointer"] && receivers["value"] {
|
||||
// Find all methods with this issue
|
||||
for _, method := range analysis.Methods {
|
||||
if method.TypeName == typeName {
|
||||
issue := ReceiverIssue{
|
||||
Type: "mixed_receivers",
|
||||
Description: "Type " + typeName + " has methods with both pointer and value receivers",
|
||||
Position: method.Position,
|
||||
}
|
||||
analysis.Issues = append(analysis.Issues, issue)
|
||||
break // Only report once per type
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check for methods that should use pointer receivers
|
||||
for _, method := range analysis.Methods {
|
||||
if method.ReceiverType == "value" && shouldUsePointerReceiver(method.MethodName) {
|
||||
issue := ReceiverIssue{
|
||||
Type: "should_use_pointer",
|
||||
Description: "Method " + method.MethodName + " on " + method.TypeName + " should probably use a pointer receiver",
|
||||
Position: method.Position,
|
||||
}
|
||||
analysis.Issues = append(analysis.Issues, issue)
|
||||
}
|
||||
}
|
||||
|
||||
return analysis, nil
|
||||
}
|
||||
|
||||
func shouldUsePointerReceiver(methodName string) bool {
|
||||
// Methods that typically modify state should use pointer receivers
|
||||
prefixes := []string{"Set", "Add", "Remove", "Delete", "Update", "Append", "Clear", "Reset"}
|
||||
for _, prefix := range prefixes {
|
||||
if len(methodName) > len(prefix) && methodName[:len(prefix)] == prefix {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
Reference in New Issue
Block a user