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 }