github.com/SkycoinProject/gomobile@v0.0.0-20190312151609-d3739f865fa6/internal/importers/ast.go (about)

     1  // Copyright 2016 The Go Authors.  All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // The importers package uses go/ast to analyze Go packages or Go files
     6  // and collect references to types whose package has a package prefix.
     7  // It is used by the language specific importers to determine the set of
     8  // wrapper types to be generated.
     9  //
    10  // For example, in the Go file
    11  //
    12  // package javaprogram
    13  //
    14  // import "Java/java/lang"
    15  //
    16  // func F() {
    17  //     o := lang.Object.New()
    18  //     ...
    19  // }
    20  //
    21  // the java importer uses this package to determine that the "java/lang"
    22  // package and the wrapper interface, lang.Object, needs to be generated.
    23  // After calling AnalyzeFile or AnalyzePackages, the References result
    24  // contains the reference to lang.Object and the names set will contain
    25  // "New".
    26  package importers
    27  
    28  import (
    29  	"errors"
    30  	"go/ast"
    31  	"go/build"
    32  	"go/parser"
    33  	"go/token"
    34  	"path"
    35  	"path/filepath"
    36  	"sort"
    37  	"strconv"
    38  	"strings"
    39  )
    40  
    41  // References is the result of analyzing a Go file or set of Go packages.
    42  //
    43  // For example, the Go file
    44  //
    45  // package pkg
    46  //
    47  // import "Prefix/some/Package"
    48  //
    49  // var A = Package.Identifier
    50  //
    51  // Will result in a single PkgRef with the "some/Package" package and
    52  // the Identifier name. The Names set will contain the single name,
    53  // "Identifier".
    54  type References struct {
    55  	// The list of references to identifiers in packages that are
    56  	// identified by a package prefix.
    57  	Refs []PkgRef
    58  	// The list of names used in at least one selector expression.
    59  	// Useful as a conservative upper bound on the set of identifiers
    60  	// referenced from a set of packages.
    61  	Names map[string]struct{}
    62  	// Embedders is a list of struct types with prefixed types
    63  	// embedded.
    64  	Embedders []Struct
    65  }
    66  
    67  // Struct is a representation of a struct type with embedded
    68  // types.
    69  type Struct struct {
    70  	Name    string
    71  	Pkg     string
    72  	PkgPath string
    73  	Refs    []PkgRef
    74  }
    75  
    76  // PkgRef is a reference to an identifier in a package.
    77  type PkgRef struct {
    78  	Name string
    79  	Pkg  string
    80  }
    81  
    82  type refsSaver struct {
    83  	pkgPrefix string
    84  	*References
    85  	refMap       map[PkgRef]struct{}
    86  	insideStruct bool
    87  }
    88  
    89  // AnalyzeFile scans the provided file for references to packages with the given
    90  // package prefix. The list of unique (package, identifier) pairs is returned
    91  func AnalyzeFile(file *ast.File, pkgPrefix string) (*References, error) {
    92  	visitor := newRefsSaver(pkgPrefix)
    93  	fset := token.NewFileSet()
    94  	files := map[string]*ast.File{file.Name.Name: file}
    95  	// Ignore errors (from unknown packages)
    96  	pkg, _ := ast.NewPackage(fset, files, visitor.importer(), nil)
    97  	ast.Walk(visitor, pkg)
    98  	visitor.findEmbeddingStructs("", pkg)
    99  	return visitor.References, nil
   100  }
   101  
   102  // AnalyzePackages scans the provided packages for references to packages with the given
   103  // package prefix. The list of unique (package, identifier) pairs is returned
   104  func AnalyzePackages(pkgs []*build.Package, pkgPrefix string) (*References, error) {
   105  	visitor := newRefsSaver(pkgPrefix)
   106  	imp := visitor.importer()
   107  	fset := token.NewFileSet()
   108  	for _, pkg := range pkgs {
   109  		fileNames := append(append([]string{}, pkg.GoFiles...), pkg.CgoFiles...)
   110  		files := make(map[string]*ast.File)
   111  		for _, name := range fileNames {
   112  			f, err := parser.ParseFile(fset, filepath.Join(pkg.Dir, name), nil, 0)
   113  			if err != nil {
   114  				return nil, err
   115  			}
   116  			files[name] = f
   117  		}
   118  		// Ignore errors (from unknown packages)
   119  		astpkg, _ := ast.NewPackage(fset, files, imp, nil)
   120  		ast.Walk(visitor, astpkg)
   121  		visitor.findEmbeddingStructs(pkg.ImportPath, astpkg)
   122  	}
   123  	return visitor.References, nil
   124  }
   125  
   126  // findEmbeddingStructs finds all top level declarations embedding a prefixed type.
   127  //
   128  // For example:
   129  //
   130  // import "Prefix/some/Package"
   131  //
   132  // type T struct {
   133  //     Package.Class
   134  // }
   135  func (v *refsSaver) findEmbeddingStructs(pkgpath string, pkg *ast.Package) {
   136  	var names []string
   137  	for _, obj := range pkg.Scope.Objects {
   138  		if obj.Kind != ast.Typ || !ast.IsExported(obj.Name) {
   139  			continue
   140  		}
   141  		names = append(names, obj.Name)
   142  	}
   143  	sort.Strings(names)
   144  	for _, name := range names {
   145  		obj := pkg.Scope.Objects[name]
   146  
   147  		t, ok := obj.Decl.(*ast.TypeSpec).Type.(*ast.StructType)
   148  		if !ok {
   149  			continue
   150  		}
   151  		var refs []PkgRef
   152  		for _, f := range t.Fields.List {
   153  			sel, ok := f.Type.(*ast.SelectorExpr)
   154  			if !ok {
   155  				continue
   156  			}
   157  			ref, ok := v.addRef(sel)
   158  			if !ok {
   159  				continue
   160  			}
   161  			if len(f.Names) > 0 && !f.Names[0].IsExported() {
   162  				continue
   163  			}
   164  			refs = append(refs, ref)
   165  		}
   166  		if len(refs) > 0 {
   167  			v.Embedders = append(v.Embedders, Struct{
   168  				Name:    obj.Name,
   169  				Pkg:     pkg.Name,
   170  				PkgPath: pkgpath,
   171  
   172  				Refs: refs,
   173  			})
   174  		}
   175  	}
   176  }
   177  
   178  func newRefsSaver(pkgPrefix string) *refsSaver {
   179  	s := &refsSaver{
   180  		pkgPrefix:  pkgPrefix,
   181  		refMap:     make(map[PkgRef]struct{}),
   182  		References: &References{},
   183  	}
   184  	s.Names = make(map[string]struct{})
   185  	return s
   186  }
   187  
   188  func (v *refsSaver) importer() ast.Importer {
   189  	return func(imports map[string]*ast.Object, pkgPath string) (*ast.Object, error) {
   190  		if pkg, exists := imports[pkgPath]; exists {
   191  			return pkg, nil
   192  		}
   193  		if !strings.HasPrefix(pkgPath, v.pkgPrefix) {
   194  			return nil, errors.New("ignored")
   195  		}
   196  		pkg := ast.NewObj(ast.Pkg, path.Base(pkgPath))
   197  		imports[pkgPath] = pkg
   198  		return pkg, nil
   199  	}
   200  }
   201  
   202  func (v *refsSaver) addRef(sel *ast.SelectorExpr) (PkgRef, bool) {
   203  	x, ok := sel.X.(*ast.Ident)
   204  	if !ok || x.Obj == nil {
   205  		return PkgRef{}, false
   206  	}
   207  	imp, ok := x.Obj.Decl.(*ast.ImportSpec)
   208  	if !ok {
   209  		return PkgRef{}, false
   210  	}
   211  	pkgPath, err := strconv.Unquote(imp.Path.Value)
   212  	if err != nil {
   213  		return PkgRef{}, false
   214  	}
   215  	if !strings.HasPrefix(pkgPath, v.pkgPrefix) {
   216  		return PkgRef{}, false
   217  	}
   218  	pkgPath = pkgPath[len(v.pkgPrefix):]
   219  	ref := PkgRef{Pkg: pkgPath, Name: sel.Sel.Name}
   220  	if _, exists := v.refMap[ref]; !exists {
   221  		v.refMap[ref] = struct{}{}
   222  		v.Refs = append(v.Refs, ref)
   223  	}
   224  	return ref, true
   225  }
   226  
   227  func (v *refsSaver) Visit(n ast.Node) ast.Visitor {
   228  	switch n := n.(type) {
   229  	case *ast.StructType:
   230  		// Use a copy of refsSaver that only accepts exported fields. It refers
   231  		// to the original refsSaver for collecting references.
   232  		v2 := *v
   233  		v2.insideStruct = true
   234  		return &v2
   235  	case *ast.Field:
   236  		if v.insideStruct && len(n.Names) == 1 && !n.Names[0].IsExported() {
   237  			return nil
   238  		}
   239  	case *ast.SelectorExpr:
   240  		v.Names[n.Sel.Name] = struct{}{}
   241  		if _, ok := v.addRef(n); ok {
   242  			return nil
   243  		}
   244  	case *ast.FuncDecl:
   245  		if n.Recv != nil { // Methods
   246  			v.Names[n.Name.Name] = struct{}{}
   247  		}
   248  	}
   249  	return v
   250  }