diff options
| author | Martin Kagamino Lehoux <martin@lehoux.net> | 2023-10-24 07:34:00 +0200 |
|---|---|---|
| committer | Martin Kagamino Lehoux <martin@lehoux.net> | 2023-10-24 07:34:00 +0200 |
| commit | 435397de49adc9740bc67416166aba4d0be8ae2a (patch) | |
| tree | 1642dccae517840f3256f35e29e8d7d1802f700f /cmd/cvet/check_command.go | |
| parent | 18476c6cba2d5d2bb3ecf3ad136dacbf41d0360e (diff) | |
feat: Add cmds
Diffstat (limited to 'cmd/cvet/check_command.go')
| -rw-r--r-- | cmd/cvet/check_command.go | 104 |
1 files changed, 104 insertions, 0 deletions
diff --git a/cmd/cvet/check_command.go b/cmd/cvet/check_command.go new file mode 100644 index 0000000..47db8fa --- /dev/null +++ b/cmd/cvet/check_command.go @@ -0,0 +1,104 @@ +package main + +import ( + "fmt" + "go/ast" + "go/token" + "strings" + + "github.com/samber/lo" + "golang.org/x/tools/go/analysis" +) + +func isCommandFunc(node *ast.FuncDecl) bool { + return strings.HasSuffix(node.Name.Name, "Command") +} + +func loggerCommandKeyValueArgFinder(pass *analysis.Pass, commandName string) func(arg ast.Expr) bool { + return func(arg ast.Expr) bool { + call, ok := arg.(*ast.CallExpr) + if !ok { + return false + } + firstArg, ok := call.Args[0].(*ast.BasicLit) + if !ok { + return false + } + if firstArg.Kind == token.STRING && firstArg.Value == `"command"` { + secondArg, ok := call.Args[1].(*ast.BasicLit) + if !ok { + pass.Reportf(arg.Pos(), "logger command name must be a literal") + return true + } + if secondArg.Kind != token.STRING { + pass.Reportf(arg.Pos(), "logger command name must be a string literal") + return true + } + + value := secondArg.Value[1 : len(secondArg.Value)-1] + if value != commandName { + pass.Reportf(arg.Pos(), fmt.Sprintf("logger command name must be function name %s, but found %s", commandName, value)) + return true + } + return true + } + + return false + } +} + +func checkStmtHaveNoRawSlog(pass *analysis.Pass, stmts []ast.Stmt) { + for _, stmt := range stmts[1:] { + switch stmt := stmt.(type) { + case *ast.ExprStmt: + if call, ok := stmt.X.(*ast.CallExpr); ok { + if selector, ok := call.Fun.(*ast.SelectorExpr); ok { + if isIdent(selector.X, "slog") { + pass.Reportf(stmt.Pos(), "logger must be used to log") + } + } + } + case *ast.IfStmt: + checkStmtHaveNoRawSlog(pass, stmt.Body.List) + } + } +} + +func checkCommandLogger(pass *analysis.Pass, node *ast.FuncDecl) { + commandName := node.Name.Name + firstStmt, ok := node.Body.List[0].(*ast.AssignStmt) + if !ok || !isIdent(firstStmt.Lhs[0], "logger") { + pass.Reportf(node.Pos(), "command first statement must be a logger creation") + return + } + + call, ok := firstStmt.Rhs[0].(*ast.CallExpr) + if !ok || !isSelector(call.Fun, "slog", "With") { + pass.Reportf(node.Pos(), "logger creation must use slog.With") + return + } + if !lo.ContainsBy(call.Args, loggerCommandKeyValueArgFinder(pass, commandName)) { + pass.Reportf(node.Pos(), "logger creation must contain command arg") + } + + checkStmtHaveNoRawSlog(pass, node.Body.List[1:]) +} + +func checkCommandFunc(pass *analysis.Pass, node *ast.FuncDecl) { + if node.Type.Results.NumFields() != 2 { + pass.Reportf(node.Pos(), "command function must have 2 return value") + } else { + if !isIdent(node.Type.Results.List[0].Type, "int") { + pass.Reportf(node.Pos(), "command function must return an int code as the first return value") + } + if !isIdent(node.Type.Results.List[1].Type, "error") { + pass.Reportf(node.Pos(), "command function must return an error as the second return value") + } + } + if lo.ContainsBy(node.Type.Params.List, func(field *ast.Field) bool { + return isSelector(field.Type, "auth", "User") + }) { + pass.Reportf(node.Pos(), "command function must not have an auth.User parameter") + } + checkCommandLogger(pass, node) +} |
