package noliteral import ( "go/ast" "go/token" "strings" "sync" "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, } var ( methodsToSearchOnce = &sync.Once{} methodsToSearch = []string{"Debug", "Info", "Warn", "Error"} customLogs = "reportFlushError, reportError" ) 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 && len(expr.Args) > 0 && isStringValue(expr.Args[0]) { pass.Report(analysis.Diagnostic{ Pos: expr.Pos(), End: 0, Category: LinterName, Message: "Literals are not allowed in the body of the logger", SuggestedFixes: nil, }) return false } return true }) } return nil, nil } func isLogDot(expr ast.Expr) (bool, string) { sel, ok := expr.(*ast.SelectorExpr) if !ok { return false, "" } methodsToSearchOnce.Do(func() { for _, cl := range strings.Split(customLogs, ",") { cl = strings.Trim(cl, " ") if len(cl) > 0 { methodsToSearch = append(methodsToSearch, cl) } } }) for _, method := range methodsToSearch { 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 { return false } if id.Name == ident { return true } return false } func isStringValue(expr ast.Expr) bool { basicLit, ok := expr.(*ast.BasicLit) return ok && basicLit.Kind == token.STRING }