s1039.go raw

   1  package s1039
   2  
   3  import (
   4  	"fmt"
   5  	"go/ast"
   6  	"go/types"
   7  	"strings"
   8  
   9  	"honnef.co/go/tools/analysis/code"
  10  	"honnef.co/go/tools/analysis/edit"
  11  	"honnef.co/go/tools/analysis/facts/generated"
  12  	"honnef.co/go/tools/analysis/lint"
  13  	"honnef.co/go/tools/analysis/report"
  14  	"honnef.co/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:     "S1039",
  23  		Run:      run,
  24  		Requires: []*analysis.Analyzer{inspect.Analyzer, generated.Analyzer},
  25  	},
  26  	Doc: &lint.RawDocumentation{
  27  		Title: `Unnecessary use of \'fmt.Sprint\'`,
  28  		Text: `
  29  Calling \'fmt.Sprint\' with a single string argument is unnecessary
  30  and identical to using the string directly.`,
  31  		Since: "2020.1",
  32  		// MergeIfAll because s might not be a string under all build tags.
  33  		// you shouldn't write code like that…
  34  		MergeIf: lint.MergeIfAll,
  35  	},
  36  })
  37  
  38  var Analyzer = SCAnalyzer.Analyzer
  39  
  40  var checkSprintLiteralQ = pattern.MustParse(`
  41  	(CallExpr
  42  		fn@(Or
  43  			(Symbol "fmt.Sprint")
  44  			(Symbol "fmt.Sprintf"))
  45  		[lit@(BasicLit "STRING" _)])`)
  46  
  47  func run(pass *analysis.Pass) (interface{}, error) {
  48  	// We only flag calls with string literals, not expressions of
  49  	// type string, because some people use fmt.Sprint(s) as a pattern
  50  	// for copying strings, which may be useful when extracting a small
  51  	// substring from a large string.
  52  	fn := func(node ast.Node) {
  53  		m, ok := code.Match(pass, checkSprintLiteralQ, node)
  54  		if !ok {
  55  			return
  56  		}
  57  		callee := m.State["fn"].(*types.Func)
  58  		lit := m.State["lit"].(*ast.BasicLit)
  59  		if callee.Name() == "Sprintf" {
  60  			if strings.ContainsRune(lit.Value, '%') {
  61  				// This might be a format string
  62  				return
  63  			}
  64  		}
  65  		report.Report(pass, node, fmt.Sprintf("unnecessary use of fmt.%s", callee.Name()),
  66  			report.FilterGenerated(),
  67  			report.Fixes(edit.Fix("Replace with string literal", edit.ReplaceWithNode(pass.Fset, node, lit))))
  68  	}
  69  	code.Preorder(pass, fn, (*ast.CallExpr)(nil))
  70  	return nil, nil
  71  }
  72