Reorganize code into individual tool files
This commit is contained in:
102
tool_find_errors.go
Normal file
102
tool_find_errors.go
Normal file
@@ -0,0 +1,102 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Error handling types
|
||||
type ErrorInfo struct {
|
||||
File string `json:"file"`
|
||||
UnhandledErrors []ErrorContext `json:"unhandled_errors,omitempty"`
|
||||
ErrorChecks []ErrorContext `json:"error_checks,omitempty"`
|
||||
ErrorReturns []ErrorContext `json:"error_returns,omitempty"`
|
||||
}
|
||||
|
||||
type ErrorContext struct {
|
||||
Context string `json:"context"`
|
||||
Type string `json:"type"`
|
||||
Position Position `json:"position"`
|
||||
}
|
||||
|
||||
func findErrors(dir string) ([]ErrorInfo, error) {
|
||||
var errors []ErrorInfo
|
||||
|
||||
err := walkGoFiles(dir, func(path string, src []byte, file *ast.File, fset *token.FileSet) error {
|
||||
info := ErrorInfo{
|
||||
File: path,
|
||||
}
|
||||
|
||||
ast.Inspect(file, func(n ast.Node) bool {
|
||||
switch x := n.(type) {
|
||||
// Find function calls that return errors but aren't checked
|
||||
case *ast.ExprStmt:
|
||||
if call, ok := x.X.(*ast.CallExpr); ok {
|
||||
// Check if this function likely returns an error
|
||||
if returnsError(call, file) {
|
||||
pos := fset.Position(call.Pos())
|
||||
context := extractContext(src, pos)
|
||||
info.UnhandledErrors = append(info.UnhandledErrors, ErrorContext{
|
||||
Context: context,
|
||||
Type: "unchecked_call",
|
||||
Position: newPosition(pos),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Find error checks
|
||||
case *ast.IfStmt:
|
||||
if isErrorCheck(x) {
|
||||
pos := fset.Position(x.Pos())
|
||||
context := extractContext(src, pos)
|
||||
info.ErrorChecks = append(info.ErrorChecks, ErrorContext{
|
||||
Context: context,
|
||||
Type: "error_check",
|
||||
Position: newPosition(pos),
|
||||
})
|
||||
}
|
||||
|
||||
// Find error returns
|
||||
case *ast.ReturnStmt:
|
||||
for _, result := range x.Results {
|
||||
if ident, ok := result.(*ast.Ident); ok && (ident.Name == "err" || strings.Contains(ident.Name, "error")) {
|
||||
pos := fset.Position(x.Pos())
|
||||
context := extractContext(src, pos)
|
||||
info.ErrorReturns = append(info.ErrorReturns, ErrorContext{
|
||||
Context: context,
|
||||
Type: "error_return",
|
||||
Position: newPosition(pos),
|
||||
})
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
if len(info.UnhandledErrors) > 0 || len(info.ErrorChecks) > 0 || len(info.ErrorReturns) > 0 {
|
||||
errors = append(errors, info)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
return errors, err
|
||||
}
|
||||
|
||||
func returnsError(call *ast.CallExpr, file *ast.File) bool {
|
||||
// Simple heuristic: check if the function name suggests it returns an error
|
||||
switch fun := call.Fun.(type) {
|
||||
case *ast.Ident:
|
||||
name := fun.Name
|
||||
return strings.HasPrefix(name, "New") || strings.HasPrefix(name, "Create") ||
|
||||
strings.HasPrefix(name, "Open") || strings.HasPrefix(name, "Read") ||
|
||||
strings.HasPrefix(name, "Write") || strings.HasPrefix(name, "Parse")
|
||||
case *ast.SelectorExpr:
|
||||
name := fun.Sel.Name
|
||||
return strings.HasPrefix(name, "New") || strings.HasPrefix(name, "Create") ||
|
||||
strings.HasPrefix(name, "Open") || strings.HasPrefix(name, "Read") ||
|
||||
strings.HasPrefix(name, "Write") || strings.HasPrefix(name, "Parse")
|
||||
}
|
||||
return false
|
||||
}
|
||||
Reference in New Issue
Block a user