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 }