Files
gocp/tool_analyze_go_idioms.go
2025-06-27 21:22:36 -07:00

78 lines
2.0 KiB
Go

package main
import (
"go/ast"
"go/token"
"strings"
)
// Go idioms types
type IdiomsInfo struct {
File string `json:"file"`
Violations []IdiomItem `json:"violations,omitempty"`
Suggestions []IdiomItem `json:"suggestions,omitempty"`
}
type IdiomItem struct {
Type string `json:"type"`
Description string `json:"description"`
Suggestion string `json:"suggestion"`
Position Position `json:"position"`
}
func analyzeGoIdioms(dir string) ([]IdiomsInfo, error) {
var idioms []IdiomsInfo
err := walkGoFiles(dir, func(path string, src []byte, file *ast.File, fset *token.FileSet) error {
info := IdiomsInfo{
File: path,
}
ast.Inspect(file, func(n ast.Node) bool {
// Check for proper error handling
if ifStmt, ok := n.(*ast.IfStmt); ok {
if !isErrorCheck(ifStmt) {
// Look for other patterns that might be non-idiomatic
pos := fset.Position(ifStmt.Pos())
info.Suggestions = append(info.Suggestions, IdiomItem{
Type: "error_handling",
Description: "Consider Go error handling patterns",
Suggestion: "Use 'if err != nil' pattern",
Position: newPosition(pos),
})
}
}
// Check for receiver naming
if fn, ok := n.(*ast.FuncDecl); ok && fn.Recv != nil {
for _, recv := range fn.Recv.List {
if len(recv.Names) > 0 {
name := recv.Names[0].Name
if len(name) > 1 && !isValidReceiverName(name) {
pos := fset.Position(recv.Pos())
info.Violations = append(info.Violations, IdiomItem{
Type: "receiver_naming",
Description: "Receiver name should be short abbreviation",
Suggestion: "Use 1-2 character receiver names",
Position: newPosition(pos),
})
}
}
}
}
return true
})
if len(info.Violations) > 0 || len(info.Suggestions) > 0 {
idioms = append(idioms, info)
}
return nil
})
return idioms, err
}
func isValidReceiverName(name string) bool {
return len(name) <= 2 && strings.ToLower(name) == name
}