s1038.go (5466B)
1 package s1038 2 3 import ( 4 "fmt" 5 "go/ast" 6 "go/types" 7 8 "honnef.co/go/tools/analysis/code" 9 "honnef.co/go/tools/analysis/facts/generated" 10 "honnef.co/go/tools/analysis/lint" 11 "honnef.co/go/tools/analysis/report" 12 "honnef.co/go/tools/go/types/typeutil" 13 "honnef.co/go/tools/pattern" 14 15 "golang.org/x/tools/go/analysis" 16 ) 17 18 var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ 19 Analyzer: &analysis.Analyzer{ 20 Name: "S1038", 21 Run: run, 22 Requires: append([]*analysis.Analyzer{generated.Analyzer}, code.RequiredAnalyzers...), 23 }, 24 Doc: &lint.RawDocumentation{ 25 Title: "Unnecessarily complex way of printing formatted string", 26 Text: `Instead of using \'fmt.Print(fmt.Sprintf(...))\', one can use \'fmt.Printf(...)\'.`, 27 Since: "2020.1", 28 MergeIf: lint.MergeIfAny, 29 }, 30 }) 31 32 var Analyzer = SCAnalyzer.Analyzer 33 34 var ( 35 checkPrintSprintQ = pattern.MustParse(` 36 (Or 37 (CallExpr 38 fn@(Or 39 (Symbol "fmt.Print") 40 (Symbol "fmt.Sprint") 41 (Symbol "fmt.Println") 42 (Symbol "fmt.Sprintln")) 43 [(CallExpr (Symbol "fmt.Sprintf") f:_)]) 44 (CallExpr 45 fn@(Or 46 (Symbol "fmt.Fprint") 47 (Symbol "fmt.Fprintln")) 48 [_ (CallExpr (Symbol "fmt.Sprintf") f:_)]))`) 49 50 checkTestingErrorSprintfQ = pattern.MustParse(` 51 (CallExpr 52 sel@(SelectorExpr 53 recv 54 (Ident 55 name@(Or 56 "Error" 57 "Fatal" 58 "Fatalln" 59 "Log" 60 "Panic" 61 "Panicln" 62 "Print" 63 "Println" 64 "Skip"))) 65 [(CallExpr (Symbol "fmt.Sprintf") args)])`) 66 67 checkLogSprintfQ = pattern.MustParse(` 68 (CallExpr 69 (Symbol 70 (Or 71 "log.Fatal" 72 "log.Fatalln" 73 "log.Panic" 74 "log.Panicln" 75 "log.Print" 76 "log.Println")) 77 [(CallExpr (Symbol "fmt.Sprintf") args)])`) 78 79 checkSprintfMapping = map[string]struct { 80 recv string 81 alternative string 82 }{ 83 "(*testing.common).Error": {"(*testing.common)", "Errorf"}, 84 "(testing.TB).Error": {"(testing.TB)", "Errorf"}, 85 "(*testing.common).Fatal": {"(*testing.common)", "Fatalf"}, 86 "(testing.TB).Fatal": {"(testing.TB)", "Fatalf"}, 87 "(*testing.common).Log": {"(*testing.common)", "Logf"}, 88 "(testing.TB).Log": {"(testing.TB)", "Logf"}, 89 "(*testing.common).Skip": {"(*testing.common)", "Skipf"}, 90 "(testing.TB).Skip": {"(testing.TB)", "Skipf"}, 91 "(*log.Logger).Fatal": {"(*log.Logger)", "Fatalf"}, 92 "(*log.Logger).Fatalln": {"(*log.Logger)", "Fatalf"}, 93 "(*log.Logger).Panic": {"(*log.Logger)", "Panicf"}, 94 "(*log.Logger).Panicln": {"(*log.Logger)", "Panicf"}, 95 "(*log.Logger).Print": {"(*log.Logger)", "Printf"}, 96 "(*log.Logger).Println": {"(*log.Logger)", "Printf"}, 97 "log.Fatal": {"", "log.Fatalf"}, 98 "log.Fatalln": {"", "log.Fatalf"}, 99 "log.Panic": {"", "log.Panicf"}, 100 "log.Panicln": {"", "log.Panicf"}, 101 "log.Print": {"", "log.Printf"}, 102 "log.Println": {"", "log.Printf"}, 103 } 104 ) 105 106 func run(pass *analysis.Pass) (any, error) { 107 fmtPrintf := func(node ast.Node) { 108 m, ok := code.Match(pass, checkPrintSprintQ, node) 109 if !ok { 110 return 111 } 112 113 name := m.State["fn"].(*types.Func).Name() 114 var msg string 115 switch name { 116 case "Print", "Fprint", "Sprint": 117 newname := name + "f" 118 msg = fmt.Sprintf("should use fmt.%s instead of fmt.%s(fmt.Sprintf(...))", newname, name) 119 case "Println", "Fprintln", "Sprintln": 120 if _, ok := m.State["f"].(*ast.BasicLit); !ok { 121 // This may be an instance of 122 // fmt.Println(fmt.Sprintf(arg, ...)) where arg is an 123 // externally provided format string and the caller 124 // cannot guarantee that the format string ends with a 125 // newline. 126 return 127 } 128 newname := name[:len(name)-2] + "f" 129 msg = fmt.Sprintf("should use fmt.%s instead of fmt.%s(fmt.Sprintf(...)) (but don't forget the newline)", newname, name) 130 } 131 report.Report(pass, node, msg, 132 report.FilterGenerated()) 133 } 134 135 methSprintf := func(node ast.Node) { 136 m, ok := code.Match(pass, checkTestingErrorSprintfQ, node) 137 if !ok { 138 return 139 } 140 mapped, ok := checkSprintfMapping[code.CallName(pass, node.(*ast.CallExpr))] 141 if !ok { 142 return 143 } 144 145 // Ensure that Errorf/Fatalf refer to the right method 146 recvTV, ok := pass.TypesInfo.Types[m.State["recv"].(ast.Expr)] 147 if !ok { 148 return 149 } 150 obj, _, _ := types.LookupFieldOrMethod(recvTV.Type, recvTV.Addressable(), nil, mapped.alternative) 151 f, ok := obj.(*types.Func) 152 if !ok { 153 return 154 } 155 if typeutil.FuncName(f) != mapped.recv+"."+mapped.alternative { 156 return 157 } 158 159 alt := &ast.SelectorExpr{ 160 X: m.State["recv"].(ast.Expr), 161 Sel: &ast.Ident{Name: mapped.alternative}, 162 } 163 report.Report(pass, node, fmt.Sprintf("should use %s(...) instead of %s(fmt.Sprintf(...))", report.Render(pass, alt), report.Render(pass, m.State["sel"].(*ast.SelectorExpr)))) 164 } 165 166 pkgSprintf := func(node ast.Node) { 167 _, ok := code.Match(pass, checkLogSprintfQ, node) 168 if !ok { 169 return 170 } 171 callName := code.CallName(pass, node.(*ast.CallExpr)) 172 mapped, ok := checkSprintfMapping[callName] 173 if !ok { 174 return 175 } 176 report.Report(pass, node, fmt.Sprintf("should use %s(...) instead of %s(fmt.Sprintf(...))", mapped.alternative, callName)) 177 } 178 179 fn := func(node ast.Node) { 180 fmtPrintf(node) 181 // TODO(dh): add suggested fixes 182 methSprintf(node) 183 pkgSprintf(node) 184 } 185 if !code.CouldMatchAny(pass, checkLogSprintfQ, checkPrintSprintQ, checkTestingErrorSprintfQ) { 186 return nil, nil 187 } 188 code.Preorder(pass, fn, (*ast.CallExpr)(nil)) 189 return nil, nil 190 }