// 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" "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{} } // 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) 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) } return &visitor.References, nil } 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) Visit(n ast.Node) ast.Visitor { switch n := n.(type) { case *ast.SelectorExpr: v.Names[n.Sel.Name] = struct{}{} if x, ok := n.X.(*ast.Ident); ok && x.Obj != nil { if imp, ok := x.Obj.Decl.(*ast.ImportSpec); ok { pkgPath, err := strconv.Unquote(imp.Path.Value) if err != nil { return nil } if strings.HasPrefix(pkgPath, v.pkgPrefix) { pkgPath = pkgPath[len(v.pkgPrefix):] ref := PkgRef{Pkg: pkgPath, Name: n.Sel.Name} 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 }