src

Go monorepo.
git clone git://code.dwrz.net/src
Log | Files | Refs

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 }