deprecated.go raw
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 }
155