// 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 Refs []PkgRef } // PkgRef is a reference to an identifier in a package. type PkgRef struct { Pkg string Name string } type refsSaver struct { pkgPrefix string References refMap map[PkgRef]struct{} } // 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(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(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 } if ref, ok := v.parseRef(sel); ok { refs = append(refs, ref) } } if len(refs) > 0 { v.Embedders = append(v.Embedders, Struct{ Name: obj.Name, Pkg: pkg.Name, Refs: refs, }) } } } func newRefsSaver(pkgPrefix string) *refsSaver { s := &refsSaver{ pkgPrefix: pkgPrefix, refMap: make(map[PkgRef]struct{}), } 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) parseRef(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):] return PkgRef{Pkg: pkgPath, Name: sel.Sel.Name}, true } func (v *refsSaver) Visit(n ast.Node) ast.Visitor { switch n := n.(type) { case *ast.SelectorExpr: v.Names[n.Sel.Name] = struct{}{} if ref, ok := v.parseRef(n); ok { if _, exists := v.refMap[ref]; !exists { v.refMap[ref] = struct{}{} v.Refs = append(v.Refs, ref) } } return nil case *ast.FuncDecl: if n.Recv != nil { // Methods v.Names[n.Name.Name] = struct{}{} } } return v }