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

     1  package sa4027
     2  
     3  import (
     4  	"go/ast"
     5  
     6  	"github.com/amarpal/go-tools/analysis/code"
     7  	"github.com/amarpal/go-tools/analysis/lint"
     8  	"github.com/amarpal/go-tools/analysis/report"
     9  	"github.com/amarpal/go-tools/pattern"
    10  
    11  	"golang.org/x/tools/go/analysis"
    12  	"golang.org/x/tools/go/analysis/passes/inspect"
    13  )
    14  
    15  var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
    16  	Analyzer: &analysis.Analyzer{
    17  		Name:     "SA4027",
    18  		Run:      run,
    19  		Requires: []*analysis.Analyzer{inspect.Analyzer},
    20  	},
    21  	Doc: &lint.Documentation{
    22  		Title: `\'(*net/url.URL).Query\' returns a copy, modifying it doesn't change the URL`,
    23  		Text: `\'(*net/url.URL).Query\' parses the current value of \'net/url.URL.RawQuery\'
    24  and returns it as a map of type \'net/url.Values\'. Subsequent changes to
    25  this map will not affect the URL unless the map gets encoded and
    26  assigned to the URL's \'RawQuery\'.
    27  
    28  As a consequence, the following code pattern is an expensive no-op:
    29  \'u.Query().Add(key, value)\'.`,
    30  		Since:    "2021.1",
    31  		Severity: lint.SeverityWarning,
    32  		MergeIf:  lint.MergeIfAny,
    33  	},
    34  })
    35  
    36  var Analyzer = SCAnalyzer.Analyzer
    37  
    38  var ineffectiveURLQueryAddQ = pattern.MustParse(`(CallExpr (SelectorExpr (CallExpr (SelectorExpr recv (Ident "Query")) []) (Ident meth)) _)`)
    39  
    40  func run(pass *analysis.Pass) (interface{}, error) {
    41  	// TODO(dh): We could make this check more complex and detect
    42  	// pointless modifications of net/url.Values in general, but that
    43  	// requires us to get the state machine correct, else we'll cause
    44  	// false positives.
    45  
    46  	fn := func(node ast.Node) {
    47  		m, ok := code.Match(pass, ineffectiveURLQueryAddQ, node)
    48  		if !ok {
    49  			return
    50  		}
    51  		if !code.IsOfType(pass, m.State["recv"].(ast.Expr), "*net/url.URL") {
    52  			return
    53  		}
    54  		switch m.State["meth"].(string) {
    55  		case "Add", "Del", "Set":
    56  		default:
    57  			return
    58  		}
    59  		report.Report(pass, node, "(*net/url.URL).Query returns a copy, modifying it doesn't change the URL")
    60  	}
    61  	code.Preorder(pass, fn, (*ast.CallExpr)(nil))
    62  
    63  	return nil, nil
    64  }