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
|
// 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/token"
"path"
"sort"
"strconv"
"strings"
"golang.org/x/tools/go/packages"
)
// 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 []*packages.Package, pkgPrefix string) (*References, error) {
visitor := newRefsSaver(pkgPrefix)
imp := visitor.importer()
fset := token.NewFileSet()
for _, pkg := range pkgs {
files := make(map[string]*ast.File)
for i, name := range pkg.GoFiles {
files[name] = pkg.Syntax[i]
}
// Ignore errors (from unknown packages)
astpkg, _ := ast.NewPackage(fset, files, imp, nil)
ast.Walk(visitor, astpkg)
visitor.findEmbeddingStructs(pkg.PkgPath, 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
}
|