mirror of
https://github.com/mgechev/revive.git
synced 2025-11-23 22:04:49 +02:00
print and println built-in functions are not recommended for use-cases other than language boostraping and are not guaranteed to stay in the language. This commit adds a new rule, use-fmt-print, that spots uses of print and println, and recommends using their fmt equivalents.
108 lines
2.4 KiB
Go
108 lines
2.4 KiB
Go
package rule
|
|
|
|
import (
|
|
"fmt"
|
|
"go/ast"
|
|
"strings"
|
|
|
|
"github.com/mgechev/revive/internal/astutils"
|
|
"github.com/mgechev/revive/lint"
|
|
)
|
|
|
|
// UseFmtPrintRule lints calls to print and println.
|
|
type UseFmtPrintRule struct{}
|
|
|
|
// Apply applies the rule to given file.
|
|
func (r *UseFmtPrintRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure {
|
|
redefinesPrint, redefinesPrintln := r.analyzeRedefinitions(file.AST.Decls)
|
|
// Here we could check if both are redefined and if it's the case return nil
|
|
// but the check being false 99.9999999% of the cases we don't
|
|
|
|
var failures []lint.Failure
|
|
onFailure := func(failure lint.Failure) {
|
|
failures = append(failures, failure)
|
|
}
|
|
|
|
w := lintUseFmtPrint{onFailure, redefinesPrint, redefinesPrintln}
|
|
ast.Walk(w, file.AST)
|
|
|
|
return failures
|
|
}
|
|
|
|
// Name returns the rule name.
|
|
func (*UseFmtPrintRule) Name() string {
|
|
return "use-fmt-print"
|
|
}
|
|
|
|
type lintUseFmtPrint struct {
|
|
onFailure func(lint.Failure)
|
|
redefinesPrint bool
|
|
redefinesPrintln bool
|
|
}
|
|
|
|
func (w lintUseFmtPrint) Visit(node ast.Node) ast.Visitor {
|
|
ce, ok := node.(*ast.CallExpr)
|
|
if !ok {
|
|
return w // nothing to do, the node is not a call
|
|
}
|
|
|
|
id, ok := (ce.Fun).(*ast.Ident)
|
|
if !ok {
|
|
return nil
|
|
}
|
|
|
|
name := id.Name
|
|
switch name {
|
|
case "print":
|
|
if w.redefinesPrint {
|
|
return nil // it's a call to user-defined print
|
|
}
|
|
case "println":
|
|
if w.redefinesPrintln {
|
|
return nil // it's a call to user-defined println
|
|
}
|
|
default:
|
|
return nil // nothing to do, the call is not println(...) nor print(...)
|
|
}
|
|
|
|
callArgs := w.callArgsAsStr(ce.Args)
|
|
w.onFailure(lint.Failure{
|
|
Confidence: 1,
|
|
Node: node,
|
|
Category: lint.FailureCategoryBadPractice,
|
|
Failure: fmt.Sprintf(`avoid using built-in function %q, replace it by "fmt.F%s(os.Stderr, %s)"`, name, name, callArgs),
|
|
})
|
|
|
|
return w
|
|
}
|
|
|
|
func (lintUseFmtPrint) callArgsAsStr(args []ast.Expr) string {
|
|
strs := []string{}
|
|
for _, expr := range args {
|
|
strs = append(strs, astutils.GoFmt(expr))
|
|
}
|
|
|
|
return strings.Join(strs, ", ")
|
|
}
|
|
|
|
func (UseFmtPrintRule) analyzeRedefinitions(decls []ast.Decl) (redefinesPrint, redefinesPrintln bool) {
|
|
for _, decl := range decls {
|
|
fnDecl, ok := decl.(*ast.FuncDecl)
|
|
if !ok {
|
|
continue // not a function declaration
|
|
}
|
|
|
|
if fnDecl.Recv != nil {
|
|
continue // it´s a method (not function) declaration
|
|
}
|
|
|
|
switch fnDecl.Name.Name {
|
|
case "print":
|
|
redefinesPrint = true
|
|
case "println":
|
|
redefinesPrintln = true
|
|
}
|
|
}
|
|
return redefinesPrint, redefinesPrintln
|
|
}
|