github.com/Johnny2210/revive@v1.0.8-0.20210625134200-febf37ccd0f5/rule/errorf.go (about)

     1  package rule
     2  
     3  import (
     4  	"fmt"
     5  	"go/ast"
     6  	"regexp"
     7  	"strings"
     8  
     9  	"github.com/mgechev/revive/lint"
    10  )
    11  
    12  // ErrorfRule lints given else constructs.
    13  type ErrorfRule struct{}
    14  
    15  // Apply applies the rule to given file.
    16  func (r *ErrorfRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure {
    17  	var failures []lint.Failure
    18  
    19  	fileAst := file.AST
    20  	walker := lintErrorf{
    21  		file:    file,
    22  		fileAst: fileAst,
    23  		onFailure: func(failure lint.Failure) {
    24  			failures = append(failures, failure)
    25  		},
    26  	}
    27  
    28  	file.Pkg.TypeCheck()
    29  	ast.Walk(walker, fileAst)
    30  
    31  	return failures
    32  }
    33  
    34  // Name returns the rule name.
    35  func (r *ErrorfRule) Name() string {
    36  	return "errorf"
    37  }
    38  
    39  type lintErrorf struct {
    40  	file      *lint.File
    41  	fileAst   *ast.File
    42  	onFailure func(lint.Failure)
    43  }
    44  
    45  func (w lintErrorf) Visit(n ast.Node) ast.Visitor {
    46  	ce, ok := n.(*ast.CallExpr)
    47  	if !ok || len(ce.Args) != 1 {
    48  		return w
    49  	}
    50  	isErrorsNew := isPkgDot(ce.Fun, "errors", "New")
    51  	var isTestingError bool
    52  	se, ok := ce.Fun.(*ast.SelectorExpr)
    53  	if ok && se.Sel.Name == "Error" {
    54  		if typ := w.file.Pkg.TypeOf(se.X); typ != nil {
    55  			isTestingError = typ.String() == "*testing.T"
    56  		}
    57  	}
    58  	if !isErrorsNew && !isTestingError {
    59  		return w
    60  	}
    61  	arg := ce.Args[0]
    62  	ce, ok = arg.(*ast.CallExpr)
    63  	if !ok || !isPkgDot(ce.Fun, "fmt", "Sprintf") {
    64  		return w
    65  	}
    66  	errorfPrefix := "fmt"
    67  	if isTestingError {
    68  		errorfPrefix = w.file.Render(se.X)
    69  	}
    70  
    71  	failure := lint.Failure{
    72  		Category:   "errors",
    73  		Node:       n,
    74  		Confidence: 1,
    75  		Failure:    fmt.Sprintf("should replace %s(fmt.Sprintf(...)) with %s.Errorf(...)", w.file.Render(se), errorfPrefix),
    76  	}
    77  
    78  	m := srcLineWithMatch(w.file, ce, `^(.*)`+w.file.Render(se)+`\(fmt\.Sprintf\((.*)\)\)(.*)$`)
    79  	if m != nil {
    80  		failure.ReplacementLine = m[1] + errorfPrefix + ".Errorf(" + m[2] + ")" + m[3]
    81  	}
    82  
    83  	w.onFailure(failure)
    84  
    85  	return w
    86  }
    87  
    88  func srcLineWithMatch(file *lint.File, node ast.Node, pattern string) (m []string) {
    89  	line := srcLine(file.Content(), file.ToPosition(node.Pos()))
    90  	line = strings.TrimSuffix(line, "\n")
    91  	rx := regexp.MustCompile(pattern)
    92  	return rx.FindStringSubmatch(line)
    93  }