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

     1  package sa1019
     2  
     3  import (
     4  	"fmt"
     5  	"go/ast"
     6  	"go/types"
     7  	"strings"
     8  
     9  	"github.com/amarpal/go-tools/analysis/code"
    10  	"github.com/amarpal/go-tools/analysis/facts/deprecated"
    11  	"github.com/amarpal/go-tools/analysis/facts/generated"
    12  	"github.com/amarpal/go-tools/analysis/lint"
    13  	"github.com/amarpal/go-tools/analysis/report"
    14  	"github.com/amarpal/go-tools/knowledge"
    15  
    16  	"golang.org/x/tools/go/analysis"
    17  	"golang.org/x/tools/go/analysis/passes/inspect"
    18  	"golang.org/x/tools/go/ast/inspector"
    19  )
    20  
    21  var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
    22  	Analyzer: &analysis.Analyzer{
    23  		Name:     "SA1019",
    24  		Run:      run,
    25  		Requires: []*analysis.Analyzer{inspect.Analyzer, deprecated.Analyzer, generated.Analyzer},
    26  	},
    27  	Doc: &lint.Documentation{
    28  		Title:    `Using a deprecated function, variable, constant or field`,
    29  		Since:    "2017.1",
    30  		Severity: lint.SeverityDeprecated,
    31  		MergeIf:  lint.MergeIfAny,
    32  	},
    33  })
    34  
    35  var Analyzer = SCAnalyzer.Analyzer
    36  
    37  func run(pass *analysis.Pass) (interface{}, error) {
    38  	deprs := pass.ResultOf[deprecated.Analyzer].(deprecated.Result)
    39  
    40  	// Selectors can appear outside of function literals, e.g. when
    41  	// declaring package level variables.
    42  
    43  	isStdlibPath := func(path string) bool {
    44  		// Modules with no dot in the first path element are reserved for the standard library and tooling.
    45  		// This is the best we can currently do.
    46  		// Nobody tells us which import paths are part of the standard library.
    47  		//
    48  		// We check the entire path instead of just the first path element, because the standard library doesn't contain paths with any dots, anyway.
    49  
    50  		return !strings.Contains(path, ".")
    51  	}
    52  
    53  	handleDeprecation := func(depr *deprecated.IsDeprecated, node ast.Node, deprecatedObjName string, pkgPath string, tfn types.Object) {
    54  		std, ok := knowledge.StdlibDeprecations[deprecatedObjName]
    55  		if !ok && isStdlibPath(pkgPath) {
    56  			// Deprecated object in the standard library, but we don't know the details of the deprecation.
    57  			// Don't flag it at all, to avoid flagging an object that was deprecated in 1.N when targeting 1.N-1.
    58  			// See https://staticcheck.dev/issues/1108 for the background on this.
    59  			return
    60  		}
    61  		if ok {
    62  			// In the past, we made use of the AlternativeAvailableSince field. If a function was deprecated in Go
    63  			// 1.6 and an alternative had been available in Go 1.0, then we'd recommend using the alternative even
    64  			// if targeting Go 1.2. The idea was to suggest writing future-proof code by using already-existing
    65  			// alternatives. This had a major flaw, however: the user would need to use at least Go 1.6 for
    66  			// Staticcheck to know that the function had been deprecated. Thus, targeting Go 1.2 and using Go 1.2
    67  			// would behave differently from targeting Go 1.2 and using Go 1.6. This is especially a problem if the
    68  			// user tries to ignore the warning. Depending on the Go version in use, the ignore directive may or may
    69  			// not match, causing a warning of its own.
    70  			//
    71  			// To avoid this issue, we no longer try to be smart. We now only compare the targeted version against
    72  			// the version that deprecated an object.
    73  			//
    74  			// Unfortunately, this issue also applies to AlternativeAvailableSince == DeprecatedNeverUse. Even though it
    75  			// is only applied to seriously flawed API, such as broken cryptography, users may wish to ignore those
    76  			// warnings.
    77  			//
    78  			// See also https://staticcheck.dev/issues/1318.
    79  			if code.StdlibVersion(pass, node) < std.DeprecatedSince {
    80  				return
    81  			}
    82  		}
    83  
    84  		if tfn != nil {
    85  			if _, ok := deprs.Objects[tfn]; ok {
    86  				// functions that are deprecated may use deprecated
    87  				// symbols
    88  				return
    89  			}
    90  		}
    91  
    92  		if ok {
    93  			switch std.AlternativeAvailableSince {
    94  			case knowledge.DeprecatedNeverUse:
    95  				report.Report(pass, node,
    96  					fmt.Sprintf("%s has been deprecated since Go 1.%d because it shouldn't be used: %s",
    97  						report.Render(pass, node), std.DeprecatedSince, depr.Msg))
    98  			case std.DeprecatedSince, knowledge.DeprecatedUseNoLonger:
    99  				report.Report(pass, node,
   100  					fmt.Sprintf("%s has been deprecated since Go 1.%d: %s",
   101  						report.Render(pass, node), std.DeprecatedSince, depr.Msg))
   102  			default:
   103  				report.Report(pass, node,
   104  					fmt.Sprintf("%s has been deprecated since Go 1.%d and an alternative has been available since Go 1.%d: %s",
   105  						report.Render(pass, node), std.DeprecatedSince, std.AlternativeAvailableSince, depr.Msg))
   106  			}
   107  		} else {
   108  			report.Report(pass, node, fmt.Sprintf("%s is deprecated: %s", report.Render(pass, node), depr.Msg))
   109  		}
   110  	}
   111  
   112  	var tfn types.Object
   113  	stack := 0
   114  	fn := func(node ast.Node, push bool) bool {
   115  		if !push {
   116  			stack--
   117  			return false
   118  		}
   119  		stack++
   120  		if stack == 1 {
   121  			tfn = nil
   122  		}
   123  		if fn, ok := node.(*ast.FuncDecl); ok {
   124  			tfn = pass.TypesInfo.ObjectOf(fn.Name)
   125  		}
   126  
   127  		// FIXME(dh): this misses dot-imported objects
   128  		sel, ok := node.(*ast.SelectorExpr)
   129  		if !ok {
   130  			return true
   131  		}
   132  
   133  		obj := pass.TypesInfo.ObjectOf(sel.Sel)
   134  		if obj_, ok := obj.(*types.Func); ok {
   135  			obj = obj_.Origin()
   136  		}
   137  		if obj.Pkg() == nil {
   138  			return true
   139  		}
   140  
   141  		if obj.Pkg() == pass.Pkg {
   142  			// A package is allowed to use its own deprecated objects
   143  			return true
   144  		}
   145  
   146  		// A package "foo" has two related packages "foo_test" and "foo.test", for external tests and the package main
   147  		// generated by 'go test' respectively. "foo_test" can import and use "foo", "foo.test" imports and uses "foo"
   148  		// and "foo_test".
   149  
   150  		if strings.TrimSuffix(pass.Pkg.Path(), "_test") == obj.Pkg().Path() {
   151  			// foo_test (the external tests of foo) can use objects from foo.
   152  			return true
   153  		}
   154  		if strings.TrimSuffix(pass.Pkg.Path(), ".test") == obj.Pkg().Path() {
   155  			// foo.test (the main package of foo's tests) can use objects from foo.
   156  			return true
   157  		}
   158  		if strings.TrimSuffix(pass.Pkg.Path(), ".test") == strings.TrimSuffix(obj.Pkg().Path(), "_test") {
   159  			// foo.test (the main package of foo's tests) can use objects from foo's external tests.
   160  			return true
   161  		}
   162  
   163  		if depr, ok := deprs.Objects[obj]; ok {
   164  			handleDeprecation(depr, sel, code.SelectorName(pass, sel), obj.Pkg().Path(), tfn)
   165  		}
   166  		return true
   167  	}
   168  
   169  	fn2 := func(node ast.Node) {
   170  		spec := node.(*ast.ImportSpec)
   171  		var imp *types.Package
   172  		if spec.Name != nil {
   173  			imp = pass.TypesInfo.ObjectOf(spec.Name).(*types.PkgName).Imported()
   174  		} else {
   175  			imp = pass.TypesInfo.Implicits[spec].(*types.PkgName).Imported()
   176  		}
   177  
   178  		p := spec.Path.Value
   179  		path := p[1 : len(p)-1]
   180  		if depr, ok := deprs.Packages[imp]; ok {
   181  			if path == "github.com/golang/protobuf/proto" {
   182  				gen, ok := code.Generator(pass, spec.Path.Pos())
   183  				if ok && gen == generated.ProtocGenGo {
   184  					return
   185  				}
   186  			}
   187  
   188  			if strings.TrimSuffix(pass.Pkg.Path(), "_test") == path {
   189  				// foo_test can import foo
   190  				return
   191  			}
   192  			if strings.TrimSuffix(pass.Pkg.Path(), ".test") == path {
   193  				// foo.test can import foo
   194  				return
   195  			}
   196  			if strings.TrimSuffix(pass.Pkg.Path(), ".test") == strings.TrimSuffix(path, "_test") {
   197  				// foo.test can import foo_test
   198  				return
   199  			}
   200  
   201  			handleDeprecation(depr, spec.Path, path, path, nil)
   202  		}
   203  	}
   204  	pass.ResultOf[inspect.Analyzer].(*inspector.Inspector).Nodes(nil, fn)
   205  	code.Preorder(pass, fn2, (*ast.ImportSpec)(nil))
   206  	return nil, nil
   207  }