package noliteral import ( "go/ast" "go/token" "golang.org/x/tools/go/analysis" ) const LinterName = "noliteral" var LogsAnalyzer = &analysis.Analyzer{ Name: LinterName, Doc: LinterName + " is a helper tool that ensures logging messages in Go code are structured and not written as simple text.", Run: run, } type Configuration struct { TargetMethods []string `mapstructure:"target-methods"` } var Config = Configuration{ TargetMethods: []string{"Debug", "Info", "Warn", "Error"}, } func run(pass *analysis.Pass) (interface{}, error) { for _, file := range pass.Files { ast.Inspect(file, func(n ast.Node) bool { expr, ok := n.(*ast.CallExpr) if !ok { return true } isLog, _ := isLogDot(expr.Fun) if !isLog { return true } if len(expr.Args) == 0 || !isStringValue(expr.Args[0]) { return true } pass.Report(analysis.Diagnostic{ Pos: expr.Pos(), End: expr.End(), Category: LinterName, Message: "Literals are not allowed in the body of the logger", SuggestedFixes: nil, }) return false }) } return nil, nil } func isLogDot(expr ast.Expr) (bool, string) { sel, ok := expr.(*ast.SelectorExpr) if !ok { return false, "" } for _, method := range Config.TargetMethods { if isIdent(sel.Sel, method) { return true, method } } return false, "" } func isIdent(expr ast.Expr, ident string) bool { id, ok := expr.(*ast.Ident) if ok && id.Name == ident { return true } return false } func isStringValue(expr ast.Expr) bool { basicLit, ok := expr.(*ast.BasicLit) return ok && basicLit.Kind == token.STRING }