-
Notifications
You must be signed in to change notification settings - Fork 1
/
cache.go
291 lines (239 loc) · 6.25 KB
/
cache.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
package pepperlint
import (
"fmt"
"go/ast"
"path/filepath"
"strconv"
)
// Cache defintion that contain type information per package.
type Cache struct {
Packages Packages
CurrentPkgImportPath string
CurrentASTFile *ast.File
}
// NewCache will return a new cache along with initializing any fields.
func NewCache() *Cache {
return &Cache{
Packages: Packages{},
}
}
// CurrentPackage will attempt to return the current package. If CurrentPkgImportPath
// was not found in the map, then false will be returned.
func (c Cache) CurrentPackage() (*Package, bool) {
v, ok := c.Packages[c.CurrentPkgImportPath]
return v, ok
}
// CurrentFile will attempt to return the current file that is being visited. If the file
// could not be found, then false will be returned.
func (c Cache) CurrentFile() (*File, bool) {
pkg, ok := c.CurrentPackage()
if !ok {
return nil, false
}
for _, f := range pkg.Files {
if f.ASTFile == c.CurrentASTFile {
return f, true
}
}
return nil, false
}
// Packages is a map of Packages that keyed off of the import path.
type Packages map[string]*Package
// Get attempts to grab the package by name and return it if it exists.
func (p Packages) Get(pkg string) (*Package, bool) {
v, ok := p[pkg]
return v, ok
}
// Package is a container for a list of files
type Package struct {
Name string
Files Files
}
// Files is a list of Files
type Files []*File
// GetTypeInfo will iterate through all the files in an attempt to grab the
// specified type info by type name provided.
func (fs Files) GetTypeInfo(typeName string) (TypeInfo, bool) {
for _, f := range fs {
info, ok := f.TypeInfos[typeName]
if !ok {
continue
}
return info, true
}
return TypeInfo{}, false
}
// GetOpInfo will iterate through all the files in an atttempt to grab the specified
// op info by the op name provided.
func (fs Files) GetOpInfo(opName string) (OpInfo, bool) {
for _, f := range fs {
info, ok := f.OpInfos[opName]
if !ok {
continue
}
return info, true
}
return OpInfo{}, false
}
// File contains the file scope of types and operation infos
type File struct {
ASTFile *ast.File
TypeInfos TypeInfos
OpInfos OpInfos
Imports map[string]string
}
// NewFile will return a new file with any instantiation that needs to be
// done.
func NewFile(t *ast.File) *File {
return &File{
ASTFile: t,
TypeInfos: TypeInfos{},
OpInfos: OpInfos{},
Imports: map[string]string{},
}
}
// TypeInfos represents a map of TypeInfos
type TypeInfos map[string]TypeInfo
// TypeInfo contains the type definition and documentation tied to that definition.
type TypeInfo struct {
Doc *ast.CommentGroup
Spec *ast.TypeSpec
PkgName string
}
// OpInfos is a map of key operation name and OpInfo containing
// relevant per package operation declarations.
type OpInfos map[string]OpInfo
// OpInfo signifies an operation which is a method or function.
type OpInfo struct {
IsMethod bool
TypeSpecs []*ast.TypeSpec
Decl *ast.FuncDecl
PkgName string
}
// HasReceiverType will return true if the method has a receiver of type
// spec.
func (oi OpInfo) HasReceiverType(spec *ast.TypeSpec) bool {
if !oi.IsMethod {
return false
}
for _, s := range oi.TypeSpecs {
if s == spec {
return true
}
}
return false
}
func (c *Cache) getTypeFromField(expr ast.Expr) (*ast.TypeSpec, bool) {
switch fieldType := expr.(type) {
case *ast.Ident:
if fieldType.Obj == nil {
return nil, false
}
if fieldType.Obj.Decl == nil {
return nil, false
}
spec, ok := fieldType.Obj.Decl.(*ast.TypeSpec)
if !ok {
return nil, false
}
return spec, true
case *ast.StarExpr:
return c.getTypeFromField(fieldType.X)
default:
Log("TODO getTypeFromField %T", fieldType)
}
return nil, false
}
// Visit will cache all specifications and docs
func (c *Cache) Visit(node ast.Node) ast.Visitor {
switch t := node.(type) {
case *ast.FuncDecl:
pkg, ok := c.Packages.Get(c.CurrentPkgImportPath)
if !ok {
pkg = &Package{}
c.Packages[c.CurrentPkgImportPath] = pkg
}
// Get most recent file
f := pkg.Files[len(pkg.Files)-1]
method := false
opInfo := OpInfo{
Decl: t,
PkgName: pkg.Name,
}
// If receiver is nil, this implies that this is a function and
// not a method.
if t.Recv != nil {
// methods can have multiple receivers it looks like.
for _, field := range t.Recv.List {
method = true
spec, ok := c.getTypeFromField(field.Type)
if !ok {
continue
}
opInfo.TypeSpecs = append(opInfo.TypeSpecs, spec)
}
opInfo.IsMethod = method
}
f.OpInfos[t.Name.Name] = opInfo
case *ast.ImportSpec:
pkg, ok := c.CurrentPackage()
if !ok {
panic("Current package could not be found")
}
importPath, err := strconv.Unquote(t.Path.Value)
if err != nil {
importPath = t.Path.Value
}
f := pkg.Files[len(pkg.Files)-1]
if t.Name != nil {
name, err := strconv.Unquote(t.Name.Name)
if err != nil {
name = t.Name.Name
}
f.Imports[name] = importPath
} else {
f.Imports[GetPackageNameFromImportPath(importPath)] = importPath
}
case *ast.Package:
// iterate through files to get the full path of the file
for k := range t.Files {
c.CurrentPkgImportPath = GetImportPathFromFullPath(filepath.Dir(k))
break
}
// Issue #13
//
// Was squashing over package names that may exist in the same package
// but contain a pkg_test format. That would mean all previous cached
// files would be lost
if _, ok := c.Packages[c.CurrentPkgImportPath]; ok {
return c
}
c.Packages[c.CurrentPkgImportPath] = &Package{
Name: GetPackageNameFromImportPath(c.CurrentPkgImportPath),
}
case *ast.File:
pkg, ok := c.Packages.Get(c.CurrentPkgImportPath)
if !ok {
panic(fmt.Errorf("package import path not found: %q", c.CurrentPkgImportPath))
}
pkg.Files = append(pkg.Files, NewFile(t))
case *ast.GenDecl:
pkg, ok := c.Packages.Get(c.CurrentPkgImportPath)
if !ok {
panic(fmt.Errorf("package import path not found: %q", c.CurrentPkgImportPath))
}
f := pkg.Files[len(pkg.Files)-1]
pkgName := GetPackageNameFromImportPath(c.CurrentPkgImportPath)
for _, spec := range t.Specs {
switch spec := spec.(type) {
case *ast.TypeSpec:
f.TypeInfos[spec.Name.Name] = TypeInfo{
t.Doc,
spec,
pkgName,
}
}
}
}
return c
}