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

     1  package s1004
     2  
     3  import (
     4  	"fmt"
     5  	"go/ast"
     6  	"go/token"
     7  
     8  	"github.com/amarpal/go-tools/analysis/code"
     9  	"github.com/amarpal/go-tools/analysis/edit"
    10  	"github.com/amarpal/go-tools/analysis/facts/generated"
    11  	"github.com/amarpal/go-tools/analysis/lint"
    12  	"github.com/amarpal/go-tools/analysis/report"
    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:     "S1004",
    22  		Run:      CheckBytesCompare,
    23  		Requires: []*analysis.Analyzer{inspect.Analyzer, generated.Analyzer},
    24  	},
    25  	Doc: &lint.Documentation{
    26  		Title:   `Replace call to \'bytes.Compare\' with \'bytes.Equal\'`,
    27  		Before:  `if bytes.Compare(x, y) == 0 {}`,
    28  		After:   `if bytes.Equal(x, y) {}`,
    29  		Since:   "2017.1",
    30  		MergeIf: lint.MergeIfAny,
    31  	},
    32  })
    33  
    34  var Analyzer = SCAnalyzer.Analyzer
    35  
    36  var (
    37  	checkBytesCompareQ  = pattern.MustParse(`(BinaryExpr (CallExpr (Symbol "bytes.Compare") args) op@(Or "==" "!=") (IntegerLiteral "0"))`)
    38  	checkBytesCompareRe = pattern.MustParse(`(CallExpr (SelectorExpr (Ident "bytes") (Ident "Equal")) args)`)
    39  	checkBytesCompareRn = pattern.MustParse(`(UnaryExpr "!" (CallExpr (SelectorExpr (Ident "bytes") (Ident "Equal")) args))`)
    40  )
    41  
    42  func CheckBytesCompare(pass *analysis.Pass) (interface{}, error) {
    43  	if pass.Pkg.Path() == "bytes" || pass.Pkg.Path() == "bytes_test" {
    44  		// the bytes package is free to use bytes.Compare as it sees fit
    45  		return nil, nil
    46  	}
    47  	fn := func(node ast.Node) {
    48  		m, ok := code.Match(pass, checkBytesCompareQ, node)
    49  		if !ok {
    50  			return
    51  		}
    52  
    53  		args := report.RenderArgs(pass, m.State["args"].([]ast.Expr))
    54  		prefix := ""
    55  		if m.State["op"].(token.Token) == token.NEQ {
    56  			prefix = "!"
    57  		}
    58  
    59  		var fix analysis.SuggestedFix
    60  		switch tok := m.State["op"].(token.Token); tok {
    61  		case token.EQL:
    62  			fix = edit.Fix("simplify use of bytes.Compare", edit.ReplaceWithPattern(pass.Fset, node, checkBytesCompareRe, m.State))
    63  		case token.NEQ:
    64  			fix = edit.Fix("simplify use of bytes.Compare", edit.ReplaceWithPattern(pass.Fset, node, checkBytesCompareRn, m.State))
    65  		default:
    66  			panic(fmt.Sprintf("unexpected token %v", tok))
    67  		}
    68  		report.Report(pass, node, fmt.Sprintf("should use %sbytes.Equal(%s) instead", prefix, args), report.FilterGenerated(), report.Fixes(fix))
    69  	}
    70  	code.Preorder(pass, fn, (*ast.BinaryExpr)(nil))
    71  	return nil, nil
    72  }