qf1010.go raw

   1  package qf1010
   2  
   3  import (
   4  	"go/ast"
   5  	"go/types"
   6  
   7  	"honnef.co/go/tools/analysis/code"
   8  	"honnef.co/go/tools/analysis/edit"
   9  	"honnef.co/go/tools/analysis/lint"
  10  	"honnef.co/go/tools/analysis/report"
  11  	"honnef.co/go/tools/knowledge"
  12  	"honnef.co/go/tools/pattern"
  13  
  14  	"golang.org/x/tools/go/analysis"
  15  	"golang.org/x/tools/go/analysis/passes/inspect"
  16  )
  17  
  18  var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
  19  	Analyzer: &analysis.Analyzer{
  20  		Name:     "QF1010",
  21  		Run:      run,
  22  		Requires: []*analysis.Analyzer{inspect.Analyzer},
  23  	},
  24  	Doc: &lint.RawDocumentation{
  25  		Title:    "Convert slice of bytes to string when printing it",
  26  		Since:    "2021.1",
  27  		Severity: lint.SeverityHint,
  28  	},
  29  })
  30  
  31  var Analyzer = SCAnalyzer.Analyzer
  32  
  33  var byteSlicePrintingQ = pattern.MustParse(`
  34  	(Or
  35  		(CallExpr
  36  			(Symbol (Or
  37  				"fmt.Print"
  38  				"fmt.Println"
  39  				"fmt.Sprint"
  40  				"fmt.Sprintln"
  41  				"log.Fatal"
  42  				"log.Fatalln"
  43  				"log.Panic"
  44  				"log.Panicln"
  45  				"log.Print"
  46  				"log.Println"
  47  				"(*log.Logger).Fatal"
  48  				"(*log.Logger).Fatalln"
  49  				"(*log.Logger).Panic"
  50  				"(*log.Logger).Panicln"
  51  				"(*log.Logger).Print"
  52  				"(*log.Logger).Println")) args)
  53  
  54  		(CallExpr (Symbol (Or
  55  			"fmt.Fprint"
  56  			"fmt.Fprintln")) _:args))`)
  57  
  58  var byteSlicePrintingR = pattern.MustParse(`(CallExpr (Ident "string") [arg])`)
  59  
  60  func run(pass *analysis.Pass) (interface{}, error) {
  61  	fn := func(node ast.Node) {
  62  		m, ok := code.Match(pass, byteSlicePrintingQ, node)
  63  		if !ok {
  64  			return
  65  		}
  66  		args := m.State["args"].([]ast.Expr)
  67  		for _, arg := range args {
  68  			if !code.IsOfStringConvertibleByteSlice(pass, arg) {
  69  				continue
  70  			}
  71  			if types.Implements(pass.TypesInfo.TypeOf(arg), knowledge.Interfaces["fmt.Stringer"]) {
  72  				continue
  73  			}
  74  
  75  			fix := edit.Fix("Convert argument to string", edit.ReplaceWithPattern(pass.Fset, arg, byteSlicePrintingR, pattern.State{"arg": arg}))
  76  			report.Report(pass, arg, "could convert argument to string", report.Fixes(fix))
  77  		}
  78  	}
  79  	code.Preorder(pass, fn, (*ast.CallExpr)(nil))
  80  	return nil, nil
  81  }
  82