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