github.com/amarpal/go-tools@v0.0.0-20240422043104-40142f59f616/quickfix/qf1012/qf1012.go (about) 1 package qf1012 2 3 import ( 4 "fmt" 5 "go/ast" 6 "go/types" 7 "strings" 8 9 "github.com/amarpal/go-tools/analysis/code" 10 "github.com/amarpal/go-tools/analysis/edit" 11 "github.com/amarpal/go-tools/analysis/lint" 12 "github.com/amarpal/go-tools/analysis/report" 13 "github.com/amarpal/go-tools/knowledge" 14 "github.com/amarpal/go-tools/pattern" 15 16 "golang.org/x/tools/go/analysis" 17 "golang.org/x/tools/go/analysis/passes/inspect" 18 ) 19 20 var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ 21 Analyzer: &analysis.Analyzer{ 22 Name: "QF1012", 23 Run: run, 24 Requires: []*analysis.Analyzer{inspect.Analyzer}, 25 }, 26 Doc: &lint.Documentation{ 27 Title: `Use \'fmt.Fprintf(x, ...)\' instead of \'x.Write(fmt.Sprintf(...))\'`, 28 Since: "2022.1", 29 Severity: lint.SeverityHint, 30 }, 31 }) 32 33 var Analyzer = SCAnalyzer.Analyzer 34 35 var ( 36 checkWriteBytesSprintfQ = pattern.MustParse(` 37 (CallExpr 38 (SelectorExpr recv (Ident "Write")) 39 (CallExpr (ArrayType nil (Ident "byte")) 40 (CallExpr 41 fn@(Or 42 (Symbol "fmt.Sprint") 43 (Symbol "fmt.Sprintf") 44 (Symbol "fmt.Sprintln")) 45 args) 46 ))`) 47 48 checkWriteStringSprintfQ = pattern.MustParse(` 49 (CallExpr 50 (SelectorExpr recv (Ident "WriteString")) 51 (CallExpr 52 fn@(Or 53 (Symbol "fmt.Sprint") 54 (Symbol "fmt.Sprintf") 55 (Symbol "fmt.Sprintln")) 56 args))`) 57 ) 58 59 func run(pass *analysis.Pass) (interface{}, error) { 60 fn := func(node ast.Node) { 61 if m, ok := code.Match(pass, checkWriteBytesSprintfQ, node); ok { 62 recv := m.State["recv"].(ast.Expr) 63 recvT := pass.TypesInfo.TypeOf(recv) 64 if !types.Implements(recvT, knowledge.Interfaces["io.Writer"]) { 65 return 66 } 67 68 name := m.State["fn"].(*types.Func).Name() 69 newName := "F" + strings.TrimPrefix(name, "S") 70 msg := fmt.Sprintf("Use fmt.%s(...) instead of Write([]byte(fmt.%s(...)))", newName, name) 71 72 args := m.State["args"].([]ast.Expr) 73 fix := edit.Fix(msg, edit.ReplaceWithNode(pass.Fset, node, &ast.CallExpr{ 74 Fun: &ast.SelectorExpr{ 75 X: ast.NewIdent("fmt"), 76 Sel: ast.NewIdent(newName), 77 }, 78 Args: append([]ast.Expr{recv}, args...), 79 })) 80 report.Report(pass, node, msg, report.Fixes(fix)) 81 } else if m, ok := code.Match(pass, checkWriteStringSprintfQ, node); ok { 82 recv := m.State["recv"].(ast.Expr) 83 recvT := pass.TypesInfo.TypeOf(recv) 84 if !types.Implements(recvT, knowledge.Interfaces["io.StringWriter"]) { 85 return 86 } 87 // The type needs to implement both StringWriter and Writer. 88 // If it doesn't implement Writer, then we cannot pass it to fmt.Fprint. 89 if !types.Implements(recvT, knowledge.Interfaces["io.Writer"]) { 90 return 91 } 92 93 name := m.State["fn"].(*types.Func).Name() 94 newName := "F" + strings.TrimPrefix(name, "S") 95 msg := fmt.Sprintf("Use fmt.%s(...) instead of WriteString(fmt.%s(...))", newName, name) 96 97 args := m.State["args"].([]ast.Expr) 98 fix := edit.Fix(msg, edit.ReplaceWithNode(pass.Fset, node, &ast.CallExpr{ 99 Fun: &ast.SelectorExpr{ 100 X: ast.NewIdent("fmt"), 101 Sel: ast.NewIdent(newName), 102 }, 103 Args: append([]ast.Expr{recv}, args...), 104 })) 105 report.Report(pass, node, msg, report.Fixes(fix)) 106 } 107 } 108 code.Preorder(pass, fn, (*ast.CallExpr)(nil)) 109 return nil, nil 110 }