honnef.co/go/tools@v0.5.0-0.dev.0.20240520180541-dcae280a5e87/analysis/facts/deprecated/deprecated.go (about) 1 package deprecated 2 3 import ( 4 "go/ast" 5 "go/token" 6 "go/types" 7 "reflect" 8 "strings" 9 10 "golang.org/x/tools/go/analysis" 11 ) 12 13 type IsDeprecated struct{ Msg string } 14 15 func (*IsDeprecated) AFact() {} 16 func (d *IsDeprecated) String() string { return "Deprecated: " + d.Msg } 17 18 type Result struct { 19 Objects map[types.Object]*IsDeprecated 20 Packages map[*types.Package]*IsDeprecated 21 } 22 23 var Analyzer = &analysis.Analyzer{ 24 Name: "fact_deprecated", 25 Doc: "Mark deprecated objects", 26 Run: deprecated, 27 FactTypes: []analysis.Fact{(*IsDeprecated)(nil)}, 28 ResultType: reflect.TypeOf(Result{}), 29 } 30 31 func deprecated(pass *analysis.Pass) (interface{}, error) { 32 var names []*ast.Ident 33 34 extractDeprecatedMessage := func(docs []*ast.CommentGroup) string { 35 for _, doc := range docs { 36 if doc == nil { 37 continue 38 } 39 parts := strings.Split(doc.Text(), "\n\n") 40 for _, part := range parts { 41 if !strings.HasPrefix(part, "Deprecated: ") { 42 continue 43 } 44 alt := part[len("Deprecated: "):] 45 alt = strings.Replace(alt, "\n", " ", -1) 46 return alt 47 } 48 } 49 return "" 50 } 51 52 doDocs := func(names []*ast.Ident, docs []*ast.CommentGroup) { 53 alt := extractDeprecatedMessage(docs) 54 if alt == "" { 55 return 56 } 57 58 for _, name := range names { 59 obj := pass.TypesInfo.ObjectOf(name) 60 pass.ExportObjectFact(obj, &IsDeprecated{alt}) 61 } 62 } 63 64 var docs []*ast.CommentGroup 65 for _, f := range pass.Files { 66 docs = append(docs, f.Doc) 67 } 68 if alt := extractDeprecatedMessage(docs); alt != "" { 69 // Don't mark package syscall as deprecated, even though 70 // it is. A lot of people still use it for simple 71 // constants like SIGKILL, and I am not comfortable 72 // telling them to use x/sys for that. 73 if pass.Pkg.Path() != "syscall" { 74 pass.ExportPackageFact(&IsDeprecated{alt}) 75 } 76 } 77 78 docs = docs[:0] 79 for _, f := range pass.Files { 80 fn := func(node ast.Node) bool { 81 if node == nil { 82 return true 83 } 84 var ret bool 85 switch node := node.(type) { 86 case *ast.GenDecl: 87 switch node.Tok { 88 case token.TYPE, token.CONST, token.VAR: 89 docs = append(docs, node.Doc) 90 for i := range node.Specs { 91 switch n := node.Specs[i].(type) { 92 case *ast.ValueSpec: 93 names = append(names, n.Names...) 94 case *ast.TypeSpec: 95 names = append(names, n.Name) 96 } 97 } 98 ret = true 99 default: 100 return false 101 } 102 case *ast.FuncDecl: 103 docs = append(docs, node.Doc) 104 names = []*ast.Ident{node.Name} 105 ret = false 106 case *ast.TypeSpec: 107 docs = append(docs, node.Doc) 108 names = []*ast.Ident{node.Name} 109 ret = true 110 case *ast.ValueSpec: 111 docs = append(docs, node.Doc) 112 names = node.Names 113 ret = false 114 case *ast.File: 115 return true 116 case *ast.StructType: 117 for _, field := range node.Fields.List { 118 doDocs(field.Names, []*ast.CommentGroup{field.Doc}) 119 } 120 return false 121 case *ast.InterfaceType: 122 for _, field := range node.Methods.List { 123 doDocs(field.Names, []*ast.CommentGroup{field.Doc}) 124 } 125 return false 126 default: 127 return false 128 } 129 if len(names) == 0 || len(docs) == 0 { 130 return ret 131 } 132 doDocs(names, docs) 133 134 docs = docs[:0] 135 names = nil 136 return ret 137 } 138 ast.Inspect(f, fn) 139 } 140 141 out := Result{ 142 Objects: map[types.Object]*IsDeprecated{}, 143 Packages: map[*types.Package]*IsDeprecated{}, 144 } 145 146 for _, fact := range pass.AllObjectFacts() { 147 out.Objects[fact.Object] = fact.Fact.(*IsDeprecated) 148 } 149 for _, fact := range pass.AllPackageFacts() { 150 out.Packages[fact.Package] = fact.Fact.(*IsDeprecated) 151 } 152 153 return out, nil 154 }