forked from golang/mobile
-
Notifications
You must be signed in to change notification settings - Fork 1
/
ast.go
250 lines (235 loc) · 6.33 KB
/
ast.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
// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// The importers package uses go/ast to analyze Go packages or Go files
// and collect references to types whose package has a package prefix.
// It is used by the language specific importers to determine the set of
// wrapper types to be generated.
//
// For example, in the Go file
//
// package javaprogram
//
// import "Java/java/lang"
//
// func F() {
// o := lang.Object.New()
// ...
// }
//
// the java importer uses this package to determine that the "java/lang"
// package and the wrapper interface, lang.Object, needs to be generated.
// After calling AnalyzeFile or AnalyzePackages, the References result
// contains the reference to lang.Object and the names set will contain
// "New".
package importers
import (
"errors"
"go/ast"
"go/build"
"go/parser"
"go/token"
"path"
"path/filepath"
"sort"
"strconv"
"strings"
)
// References is the result of analyzing a Go file or set of Go packages.
//
// For example, the Go file
//
// package pkg
//
// import "Prefix/some/Package"
//
// var A = Package.Identifier
//
// Will result in a single PkgRef with the "some/Package" package and
// the Identifier name. The Names set will contain the single name,
// "Identifier".
type References struct {
// The list of references to identifiers in packages that are
// identified by a package prefix.
Refs []PkgRef
// The list of names used in at least one selector expression.
// Useful as a conservative upper bound on the set of identifiers
// referenced from a set of packages.
Names map[string]struct{}
// Embedders is a list of struct types with prefixed types
// embedded.
Embedders []Struct
}
// Struct is a representation of a struct type with embedded
// types.
type Struct struct {
Name string
Pkg string
PkgPath string
Refs []PkgRef
}
// PkgRef is a reference to an identifier in a package.
type PkgRef struct {
Name string
Pkg string
}
type refsSaver struct {
pkgPrefix string
*References
refMap map[PkgRef]struct{}
insideStruct bool
}
// AnalyzeFile scans the provided file for references to packages with the given
// package prefix. The list of unique (package, identifier) pairs is returned
func AnalyzeFile(file *ast.File, pkgPrefix string) (*References, error) {
visitor := newRefsSaver(pkgPrefix)
fset := token.NewFileSet()
files := map[string]*ast.File{file.Name.Name: file}
// Ignore errors (from unknown packages)
pkg, _ := ast.NewPackage(fset, files, visitor.importer(), nil)
ast.Walk(visitor, pkg)
visitor.findEmbeddingStructs("", pkg)
return visitor.References, nil
}
// AnalyzePackages scans the provided packages for references to packages with the given
// package prefix. The list of unique (package, identifier) pairs is returned
func AnalyzePackages(pkgs []*build.Package, pkgPrefix string) (*References, error) {
visitor := newRefsSaver(pkgPrefix)
imp := visitor.importer()
fset := token.NewFileSet()
for _, pkg := range pkgs {
fileNames := append(append([]string{}, pkg.GoFiles...), pkg.CgoFiles...)
files := make(map[string]*ast.File)
for _, name := range fileNames {
f, err := parser.ParseFile(fset, filepath.Join(pkg.Dir, name), nil, 0)
if err != nil {
return nil, err
}
files[name] = f
}
// Ignore errors (from unknown packages)
astpkg, _ := ast.NewPackage(fset, files, imp, nil)
ast.Walk(visitor, astpkg)
visitor.findEmbeddingStructs(pkg.ImportPath, astpkg)
}
return visitor.References, nil
}
// findEmbeddingStructs finds all top level declarations embedding a prefixed type.
//
// For example:
//
// import "Prefix/some/Package"
//
// type T struct {
// Package.Class
// }
func (v *refsSaver) findEmbeddingStructs(pkgpath string, pkg *ast.Package) {
var names []string
for _, obj := range pkg.Scope.Objects {
if obj.Kind != ast.Typ || !ast.IsExported(obj.Name) {
continue
}
names = append(names, obj.Name)
}
sort.Strings(names)
for _, name := range names {
obj := pkg.Scope.Objects[name]
t, ok := obj.Decl.(*ast.TypeSpec).Type.(*ast.StructType)
if !ok {
continue
}
var refs []PkgRef
for _, f := range t.Fields.List {
sel, ok := f.Type.(*ast.SelectorExpr)
if !ok {
continue
}
ref, ok := v.addRef(sel)
if !ok {
continue
}
if len(f.Names) > 0 && !f.Names[0].IsExported() {
continue
}
refs = append(refs, ref)
}
if len(refs) > 0 {
v.Embedders = append(v.Embedders, Struct{
Name: obj.Name,
Pkg: pkg.Name,
PkgPath: pkgpath,
Refs: refs,
})
}
}
}
func newRefsSaver(pkgPrefix string) *refsSaver {
s := &refsSaver{
pkgPrefix: pkgPrefix,
refMap: make(map[PkgRef]struct{}),
References: &References{},
}
s.Names = make(map[string]struct{})
return s
}
func (v *refsSaver) importer() ast.Importer {
return func(imports map[string]*ast.Object, pkgPath string) (*ast.Object, error) {
if pkg, exists := imports[pkgPath]; exists {
return pkg, nil
}
if !strings.HasPrefix(pkgPath, v.pkgPrefix) {
return nil, errors.New("ignored")
}
pkg := ast.NewObj(ast.Pkg, path.Base(pkgPath))
imports[pkgPath] = pkg
return pkg, nil
}
}
func (v *refsSaver) addRef(sel *ast.SelectorExpr) (PkgRef, bool) {
x, ok := sel.X.(*ast.Ident)
if !ok || x.Obj == nil {
return PkgRef{}, false
}
imp, ok := x.Obj.Decl.(*ast.ImportSpec)
if !ok {
return PkgRef{}, false
}
pkgPath, err := strconv.Unquote(imp.Path.Value)
if err != nil {
return PkgRef{}, false
}
if !strings.HasPrefix(pkgPath, v.pkgPrefix) {
return PkgRef{}, false
}
pkgPath = pkgPath[len(v.pkgPrefix):]
ref := PkgRef{Pkg: pkgPath, Name: sel.Sel.Name}
if _, exists := v.refMap[ref]; !exists {
v.refMap[ref] = struct{}{}
v.Refs = append(v.Refs, ref)
}
return ref, true
}
func (v *refsSaver) Visit(n ast.Node) ast.Visitor {
switch n := n.(type) {
case *ast.StructType:
// Use a copy of refsSaver that only accepts exported fields. It refers
// to the original refsSaver for collecting references.
v2 := *v
v2.insideStruct = true
return &v2
case *ast.Field:
if v.insideStruct && len(n.Names) == 1 && !n.Names[0].IsExported() {
return nil
}
case *ast.SelectorExpr:
v.Names[n.Sel.Name] = struct{}{}
if _, ok := v.addRef(n); ok {
return nil
}
case *ast.FuncDecl:
if n.Recv != nil { // Methods
v.Names[n.Name.Name] = struct{}{}
}
}
return v
}