102 lines
2.9 KiB
Go
102 lines
2.9 KiB
Go
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
|
|
} |