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

     1  package sa1012
     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/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:     "SA1012",
    21  		Run:      run,
    22  		Requires: []*analysis.Analyzer{inspect.Analyzer},
    23  	},
    24  	Doc: &lint.Documentation{
    25  		Title:    `A nil \'context.Context\' is being passed to a function, consider using \'context.TODO\' instead`,
    26  		Since:    "2017.1",
    27  		Severity: lint.SeverityWarning,
    28  		MergeIf:  lint.MergeIfAny,
    29  	},
    30  })
    31  
    32  var Analyzer = SCAnalyzer.Analyzer
    33  
    34  var checkNilContextQ = pattern.MustParse(`(CallExpr fun@(Symbol _) (Builtin "nil"):_)`)
    35  
    36  func run(pass *analysis.Pass) (interface{}, error) {
    37  	todo := &ast.CallExpr{
    38  		Fun: edit.Selector("context", "TODO"),
    39  	}
    40  	bg := &ast.CallExpr{
    41  		Fun: edit.Selector("context", "Background"),
    42  	}
    43  	fn := func(node ast.Node) {
    44  		m, ok := code.Match(pass, checkNilContextQ, node)
    45  		if !ok {
    46  			return
    47  		}
    48  
    49  		call := node.(*ast.CallExpr)
    50  		fun, ok := m.State["fun"].(*types.Func)
    51  		if !ok {
    52  			// it might also be a builtin
    53  			return
    54  		}
    55  		sig := fun.Type().(*types.Signature)
    56  		if sig.Params().Len() == 0 {
    57  			// Our CallExpr might've matched a method expression, like
    58  			// (*T).Foo(nil) – here, nil isn't the first argument of
    59  			// the Foo method, but the method receiver.
    60  			return
    61  		}
    62  		if !typeutil.IsType(sig.Params().At(0).Type(), "context.Context") {
    63  			return
    64  		}
    65  		report.Report(pass, call.Args[0],
    66  			"do not pass a nil Context, even if a function permits it; pass context.TODO if you are unsure about which Context to use", report.Fixes(
    67  				edit.Fix("use context.TODO", edit.ReplaceWithNode(pass.Fset, call.Args[0], todo)),
    68  				edit.Fix("use context.Background", edit.ReplaceWithNode(pass.Fset, call.Args[0], bg))))
    69  	}
    70  	code.Preorder(pass, fn, (*ast.CallExpr)(nil))
    71  	return nil, nil
    72  }