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