github.com/amarpal/go-tools@v0.0.0-20240422043104-40142f59f616/quickfix/qf1010/qf1010.go (about)

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