123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260 |
- // Copyright 2013 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.
- package imports
- import (
- "bytes"
- "context"
- "fmt"
- "go/ast"
- "go/build"
- "go/parser"
- "go/token"
- "io/ioutil"
- "log"
- "os"
- "os/exec"
- "path"
- "path/filepath"
- "sort"
- "strconv"
- "strings"
- "sync"
- "time"
- "unicode"
- "unicode/utf8"
- "golang.org/x/tools/go/ast/astutil"
- "golang.org/x/tools/go/packages"
- "golang.org/x/tools/internal/gopathwalk"
- )
- // Debug controls verbose logging.
- var Debug = false
- // LocalPrefix is a comma-separated string of import path prefixes, which, if
- // set, instructs Process to sort the import paths with the given prefixes
- // into another group after 3rd-party packages.
- var LocalPrefix string
- func localPrefixes() []string {
- if LocalPrefix != "" {
- return strings.Split(LocalPrefix, ",")
- }
- return nil
- }
- // importToGroup is a list of functions which map from an import path to
- // a group number.
- var importToGroup = []func(importPath string) (num int, ok bool){
- func(importPath string) (num int, ok bool) {
- for _, p := range localPrefixes() {
- if strings.HasPrefix(importPath, p) || strings.TrimSuffix(p, "/") == importPath {
- return 3, true
- }
- }
- return
- },
- func(importPath string) (num int, ok bool) {
- if strings.HasPrefix(importPath, "appengine") {
- return 2, true
- }
- return
- },
- func(importPath string) (num int, ok bool) {
- if strings.Contains(importPath, ".") {
- return 1, true
- }
- return
- },
- }
- func importGroup(importPath string) int {
- for _, fn := range importToGroup {
- if n, ok := fn(importPath); ok {
- return n
- }
- }
- return 0
- }
- // An importInfo represents a single import statement.
- type importInfo struct {
- importPath string // import path, e.g. "crypto/rand".
- name string // import name, e.g. "crand", or "" if none.
- }
- // A packageInfo represents what's known about a package.
- type packageInfo struct {
- name string // real package name, if known.
- exports map[string]bool // known exports.
- }
- // parseOtherFiles parses all the Go files in srcDir except filename, including
- // test files if filename looks like a test.
- func parseOtherFiles(fset *token.FileSet, srcDir, filename string) []*ast.File {
- // This could use go/packages but it doesn't buy much, and it fails
- // with https://golang.org/issue/26296 in LoadFiles mode in some cases.
- considerTests := strings.HasSuffix(filename, "_test.go")
- fileBase := filepath.Base(filename)
- packageFileInfos, err := ioutil.ReadDir(srcDir)
- if err != nil {
- return nil
- }
- var files []*ast.File
- for _, fi := range packageFileInfos {
- if fi.Name() == fileBase || !strings.HasSuffix(fi.Name(), ".go") {
- continue
- }
- if !considerTests && strings.HasSuffix(fi.Name(), "_test.go") {
- continue
- }
- f, err := parser.ParseFile(fset, filepath.Join(srcDir, fi.Name()), nil, 0)
- if err != nil {
- continue
- }
- files = append(files, f)
- }
- return files
- }
- // addGlobals puts the names of package vars into the provided map.
- func addGlobals(f *ast.File, globals map[string]bool) {
- for _, decl := range f.Decls {
- genDecl, ok := decl.(*ast.GenDecl)
- if !ok {
- continue
- }
- for _, spec := range genDecl.Specs {
- valueSpec, ok := spec.(*ast.ValueSpec)
- if !ok {
- continue
- }
- globals[valueSpec.Names[0].Name] = true
- }
- }
- }
- // collectReferences builds a map of selector expressions, from
- // left hand side (X) to a set of right hand sides (Sel).
- func collectReferences(f *ast.File) references {
- refs := references{}
- var visitor visitFn
- visitor = func(node ast.Node) ast.Visitor {
- if node == nil {
- return visitor
- }
- switch v := node.(type) {
- case *ast.SelectorExpr:
- xident, ok := v.X.(*ast.Ident)
- if !ok {
- break
- }
- if xident.Obj != nil {
- // If the parser can resolve it, it's not a package ref.
- break
- }
- if !ast.IsExported(v.Sel.Name) {
- // Whatever this is, it's not exported from a package.
- break
- }
- pkgName := xident.Name
- r := refs[pkgName]
- if r == nil {
- r = make(map[string]bool)
- refs[pkgName] = r
- }
- r[v.Sel.Name] = true
- }
- return visitor
- }
- ast.Walk(visitor, f)
- return refs
- }
- // collectImports returns all the imports in f, keyed by their package name as
- // determined by pathToName. Unnamed imports (., _) and "C" are ignored.
- func collectImports(f *ast.File) []*importInfo {
- var imports []*importInfo
- for _, imp := range f.Imports {
- var name string
- if imp.Name != nil {
- name = imp.Name.Name
- }
- if imp.Path.Value == `"C"` || name == "_" || name == "." {
- continue
- }
- path := strings.Trim(imp.Path.Value, `"`)
- imports = append(imports, &importInfo{
- name: name,
- importPath: path,
- })
- }
- return imports
- }
- // findMissingImport searches pass's candidates for an import that provides
- // pkg, containing all of syms.
- func (p *pass) findMissingImport(pkg string, syms map[string]bool) *importInfo {
- for _, candidate := range p.candidates {
- pkgInfo, ok := p.knownPackages[candidate.importPath]
- if !ok {
- continue
- }
- if p.importIdentifier(candidate) != pkg {
- continue
- }
- allFound := true
- for right := range syms {
- if !pkgInfo.exports[right] {
- allFound = false
- break
- }
- }
- if allFound {
- return candidate
- }
- }
- return nil
- }
- // references is set of references found in a Go file. The first map key is the
- // left hand side of a selector expression, the second key is the right hand
- // side, and the value should always be true.
- type references map[string]map[string]bool
- // A pass contains all the inputs and state necessary to fix a file's imports.
- // It can be modified in some ways during use; see comments below.
- type pass struct {
- // Inputs. These must be set before a call to load, and not modified after.
- fset *token.FileSet // fset used to parse f and its siblings.
- f *ast.File // the file being fixed.
- srcDir string // the directory containing f.
- fixEnv *fixEnv // the environment to use for go commands, etc.
- loadRealPackageNames bool // if true, load package names from disk rather than guessing them.
- otherFiles []*ast.File // sibling files.
- // Intermediate state, generated by load.
- existingImports map[string]*importInfo
- allRefs references
- missingRefs references
- // Inputs to fix. These can be augmented between successive fix calls.
- lastTry bool // indicates that this is the last call and fix should clean up as best it can.
- candidates []*importInfo // candidate imports in priority order.
- knownPackages map[string]*packageInfo // information about all known packages.
- }
- // loadPackageNames saves the package names for everything referenced by imports.
- func (p *pass) loadPackageNames(imports []*importInfo) error {
- var unknown []string
- for _, imp := range imports {
- if _, ok := p.knownPackages[imp.importPath]; ok {
- continue
- }
- unknown = append(unknown, imp.importPath)
- }
- names, err := p.fixEnv.getResolver().loadPackageNames(unknown, p.srcDir)
- if err != nil {
- return err
- }
- for path, name := range names {
- p.knownPackages[path] = &packageInfo{
- name: name,
- exports: map[string]bool{},
- }
- }
- return nil
- }
- // importIdentifier returns the identifier that imp will introduce. It will
- // guess if the package name has not been loaded, e.g. because the source
- // is not available.
- func (p *pass) importIdentifier(imp *importInfo) string {
- if imp.name != "" {
- return imp.name
- }
- known := p.knownPackages[imp.importPath]
- if known != nil && known.name != "" {
- return known.name
- }
- return importPathToAssumedName(imp.importPath)
- }
- // load reads in everything necessary to run a pass, and reports whether the
- // file already has all the imports it needs. It fills in p.missingRefs with the
- // file's missing symbols, if any, or removes unused imports if not.
- func (p *pass) load() bool {
- p.knownPackages = map[string]*packageInfo{}
- p.missingRefs = references{}
- p.existingImports = map[string]*importInfo{}
- // Load basic information about the file in question.
- p.allRefs = collectReferences(p.f)
- // Load stuff from other files in the same package:
- // global variables so we know they don't need resolving, and imports
- // that we might want to mimic.
- globals := map[string]bool{}
- for _, otherFile := range p.otherFiles {
- // Don't load globals from files that are in the same directory
- // but a different package. Using them to suggest imports is OK.
- if p.f.Name.Name == otherFile.Name.Name {
- addGlobals(otherFile, globals)
- }
- p.candidates = append(p.candidates, collectImports(otherFile)...)
- }
- // Resolve all the import paths we've seen to package names, and store
- // f's imports by the identifier they introduce.
- imports := collectImports(p.f)
- if p.loadRealPackageNames {
- err := p.loadPackageNames(append(imports, p.candidates...))
- if err != nil {
- if Debug {
- log.Printf("loading package names: %v", err)
- }
- return false
- }
- }
- for _, imp := range imports {
- p.existingImports[p.importIdentifier(imp)] = imp
- }
- // Find missing references.
- for left, rights := range p.allRefs {
- if globals[left] {
- continue
- }
- _, ok := p.existingImports[left]
- if !ok {
- p.missingRefs[left] = rights
- continue
- }
- }
- if len(p.missingRefs) != 0 {
- return false
- }
- return p.fix()
- }
- // fix attempts to satisfy missing imports using p.candidates. If it finds
- // everything, or if p.lastTry is true, it adds the imports it found,
- // removes anything unused, and returns true.
- func (p *pass) fix() bool {
- // Find missing imports.
- var selected []*importInfo
- for left, rights := range p.missingRefs {
- if imp := p.findMissingImport(left, rights); imp != nil {
- selected = append(selected, imp)
- }
- }
- if !p.lastTry && len(selected) != len(p.missingRefs) {
- return false
- }
- // Found everything, or giving up. Add the new imports and remove any unused.
- for _, imp := range p.existingImports {
- // We deliberately ignore globals here, because we can't be sure
- // they're in the same package. People do things like put multiple
- // main packages in the same directory, and we don't want to
- // remove imports if they happen to have the same name as a var in
- // a different package.
- if _, ok := p.allRefs[p.importIdentifier(imp)]; !ok {
- astutil.DeleteNamedImport(p.fset, p.f, imp.name, imp.importPath)
- }
- }
- for _, imp := range selected {
- astutil.AddNamedImport(p.fset, p.f, imp.name, imp.importPath)
- }
- if p.loadRealPackageNames {
- for _, imp := range p.f.Imports {
- if imp.Name != nil {
- continue
- }
- path := strings.Trim(imp.Path.Value, `""`)
- ident := p.importIdentifier(&importInfo{importPath: path})
- if ident != importPathToAssumedName(path) {
- imp.Name = &ast.Ident{Name: ident, NamePos: imp.Pos()}
- }
- }
- }
- return true
- }
- // assumeSiblingImportsValid assumes that siblings' use of packages is valid,
- // adding the exports they use.
- func (p *pass) assumeSiblingImportsValid() {
- for _, f := range p.otherFiles {
- refs := collectReferences(f)
- imports := collectImports(f)
- importsByName := map[string]*importInfo{}
- for _, imp := range imports {
- importsByName[p.importIdentifier(imp)] = imp
- }
- for left, rights := range refs {
- if imp, ok := importsByName[left]; ok {
- if _, ok := stdlib[imp.importPath]; ok {
- // We have the stdlib in memory; no need to guess.
- rights = stdlib[imp.importPath]
- }
- p.addCandidate(imp, &packageInfo{
- // no name; we already know it.
- exports: rights,
- })
- }
- }
- }
- }
- // addCandidate adds a candidate import to p, and merges in the information
- // in pkg.
- func (p *pass) addCandidate(imp *importInfo, pkg *packageInfo) {
- p.candidates = append(p.candidates, imp)
- if existing, ok := p.knownPackages[imp.importPath]; ok {
- if existing.name == "" {
- existing.name = pkg.name
- }
- for export := range pkg.exports {
- existing.exports[export] = true
- }
- } else {
- p.knownPackages[imp.importPath] = pkg
- }
- }
- // fixImports adds and removes imports from f so that all its references are
- // satisfied and there are no unused imports.
- //
- // This is declared as a variable rather than a function so goimports can
- // easily be extended by adding a file with an init function.
- var fixImports = fixImportsDefault
- func fixImportsDefault(fset *token.FileSet, f *ast.File, filename string, env *fixEnv) error {
- abs, err := filepath.Abs(filename)
- if err != nil {
- return err
- }
- srcDir := filepath.Dir(abs)
- if Debug {
- log.Printf("fixImports(filename=%q), abs=%q, srcDir=%q ...", filename, abs, srcDir)
- }
- // First pass: looking only at f, and using the naive algorithm to
- // derive package names from import paths, see if the file is already
- // complete. We can't add any imports yet, because we don't know
- // if missing references are actually package vars.
- p := &pass{fset: fset, f: f, srcDir: srcDir}
- if p.load() {
- return nil
- }
- otherFiles := parseOtherFiles(fset, srcDir, filename)
- // Second pass: add information from other files in the same package,
- // like their package vars and imports.
- p.otherFiles = otherFiles
- if p.load() {
- return nil
- }
- // Now we can try adding imports from the stdlib.
- p.assumeSiblingImportsValid()
- addStdlibCandidates(p, p.missingRefs)
- if p.fix() {
- return nil
- }
- // Third pass: get real package names where we had previously used
- // the naive algorithm. This is the first step that will use the
- // environment, so we provide it here for the first time.
- p = &pass{fset: fset, f: f, srcDir: srcDir, fixEnv: env}
- p.loadRealPackageNames = true
- p.otherFiles = otherFiles
- if p.load() {
- return nil
- }
- addStdlibCandidates(p, p.missingRefs)
- p.assumeSiblingImportsValid()
- if p.fix() {
- return nil
- }
- // Go look for candidates in $GOPATH, etc. We don't necessarily load
- // the real exports of sibling imports, so keep assuming their contents.
- if err := addExternalCandidates(p, p.missingRefs, filename); err != nil {
- return err
- }
- p.lastTry = true
- p.fix()
- return nil
- }
- // fixEnv contains environment variables and settings that affect the use of
- // the go command, the go/build package, etc.
- type fixEnv struct {
- // If non-empty, these will be used instead of the
- // process-wide values.
- GOPATH, GOROOT, GO111MODULE, GOPROXY, GOFLAGS string
- WorkingDir string
- // If true, use go/packages regardless of the environment.
- ForceGoPackages bool
- resolver resolver
- }
- func (e *fixEnv) env() []string {
- env := os.Environ()
- add := func(k, v string) {
- if v != "" {
- env = append(env, k+"="+v)
- }
- }
- add("GOPATH", e.GOPATH)
- add("GOROOT", e.GOROOT)
- add("GO111MODULE", e.GO111MODULE)
- add("GOPROXY", e.GOPROXY)
- add("GOFLAGS", e.GOFLAGS)
- if e.WorkingDir != "" {
- add("PWD", e.WorkingDir)
- }
- return env
- }
- func (e *fixEnv) getResolver() resolver {
- if e.resolver != nil {
- return e.resolver
- }
- if e.ForceGoPackages {
- return &goPackagesResolver{env: e}
- }
- out, err := e.invokeGo("env", "GOMOD")
- if err != nil || len(bytes.TrimSpace(out.Bytes())) == 0 {
- return &gopathResolver{env: e}
- }
- return &moduleResolver{env: e}
- }
- func (e *fixEnv) newPackagesConfig(mode packages.LoadMode) *packages.Config {
- return &packages.Config{
- Mode: mode,
- Dir: e.WorkingDir,
- Env: e.env(),
- }
- }
- func (e *fixEnv) buildContext() *build.Context {
- ctx := build.Default
- ctx.GOROOT = e.GOROOT
- ctx.GOPATH = e.GOPATH
- return &ctx
- }
- func (e *fixEnv) invokeGo(args ...string) (*bytes.Buffer, error) {
- cmd := exec.Command("go", args...)
- stdout := &bytes.Buffer{}
- stderr := &bytes.Buffer{}
- cmd.Stdout = stdout
- cmd.Stderr = stderr
- cmd.Env = e.env()
- cmd.Dir = e.WorkingDir
- if Debug {
- defer func(start time.Time) { log.Printf("%s for %v", time.Since(start), cmdDebugStr(cmd)) }(time.Now())
- }
- if err := cmd.Run(); err != nil {
- return nil, fmt.Errorf("running go: %v (stderr:\n%s)", err, stderr)
- }
- return stdout, nil
- }
- func cmdDebugStr(cmd *exec.Cmd) string {
- env := make(map[string]string)
- for _, kv := range cmd.Env {
- split := strings.Split(kv, "=")
- k, v := split[0], split[1]
- env[k] = v
- }
- return fmt.Sprintf("GOROOT=%v GOPATH=%v GO111MODULE=%v GOPROXY=%v PWD=%v go %v", env["GOROOT"], env["GOPATH"], env["GO111MODULE"], env["GOPROXY"], env["PWD"], cmd.Args)
- }
- func addStdlibCandidates(pass *pass, refs references) {
- add := func(pkg string) {
- pass.addCandidate(
- &importInfo{importPath: pkg},
- &packageInfo{name: path.Base(pkg), exports: stdlib[pkg]})
- }
- for left := range refs {
- if left == "rand" {
- // Make sure we try crypto/rand before math/rand.
- add("crypto/rand")
- add("math/rand")
- continue
- }
- for importPath := range stdlib {
- if path.Base(importPath) == left {
- add(importPath)
- }
- }
- }
- }
- // A resolver does the build-system-specific parts of goimports.
- type resolver interface {
- // loadPackageNames loads the package names in importPaths.
- loadPackageNames(importPaths []string, srcDir string) (map[string]string, error)
- // scan finds (at least) the packages satisfying refs. The returned slice is unordered.
- scan(refs references) ([]*pkg, error)
- }
- // gopathResolver implements resolver for GOPATH and module workspaces using go/packages.
- type goPackagesResolver struct {
- env *fixEnv
- }
- func (r *goPackagesResolver) loadPackageNames(importPaths []string, srcDir string) (map[string]string, error) {
- cfg := r.env.newPackagesConfig(packages.LoadFiles)
- pkgs, err := packages.Load(cfg, importPaths...)
- if err != nil {
- return nil, err
- }
- names := map[string]string{}
- for _, pkg := range pkgs {
- names[VendorlessPath(pkg.PkgPath)] = pkg.Name
- }
- // We may not have found all the packages. Guess the rest.
- for _, path := range importPaths {
- if _, ok := names[path]; ok {
- continue
- }
- names[path] = importPathToAssumedName(path)
- }
- return names, nil
- }
- func (r *goPackagesResolver) scan(refs references) ([]*pkg, error) {
- var loadQueries []string
- for pkgName := range refs {
- loadQueries = append(loadQueries, "iamashamedtousethedisabledqueryname="+pkgName)
- }
- sort.Strings(loadQueries)
- cfg := r.env.newPackagesConfig(packages.LoadFiles)
- goPackages, err := packages.Load(cfg, loadQueries...)
- if err != nil {
- return nil, err
- }
- var scan []*pkg
- for _, goPackage := range goPackages {
- scan = append(scan, &pkg{
- dir: filepath.Dir(goPackage.CompiledGoFiles[0]),
- importPathShort: VendorlessPath(goPackage.PkgPath),
- goPackage: goPackage,
- })
- }
- return scan, nil
- }
- func addExternalCandidates(pass *pass, refs references, filename string) error {
- dirScan, err := pass.fixEnv.getResolver().scan(refs)
- if err != nil {
- return err
- }
- // Search for imports matching potential package references.
- type result struct {
- imp *importInfo
- pkg *packageInfo
- }
- results := make(chan result, len(refs))
- ctx, cancel := context.WithCancel(context.TODO())
- var wg sync.WaitGroup
- defer func() {
- cancel()
- wg.Wait()
- }()
- var (
- firstErr error
- firstErrOnce sync.Once
- )
- for pkgName, symbols := range refs {
- wg.Add(1)
- go func(pkgName string, symbols map[string]bool) {
- defer wg.Done()
- found, err := findImport(ctx, pass.fixEnv, dirScan, pkgName, symbols, filename)
- if err != nil {
- firstErrOnce.Do(func() {
- firstErr = err
- cancel()
- })
- return
- }
- if found == nil {
- return // No matching package.
- }
- imp := &importInfo{
- importPath: found.importPathShort,
- }
- pkg := &packageInfo{
- name: pkgName,
- exports: symbols,
- }
- results <- result{imp, pkg}
- }(pkgName, symbols)
- }
- go func() {
- wg.Wait()
- close(results)
- }()
- for result := range results {
- pass.addCandidate(result.imp, result.pkg)
- }
- return firstErr
- }
- // notIdentifier reports whether ch is an invalid identifier character.
- func notIdentifier(ch rune) bool {
- return !('a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' ||
- '0' <= ch && ch <= '9' ||
- ch == '_' ||
- ch >= utf8.RuneSelf && (unicode.IsLetter(ch) || unicode.IsDigit(ch)))
- }
- // importPathToAssumedName returns the assumed package name of an import path.
- // It does this using only string parsing of the import path.
- // It picks the last element of the path that does not look like a major
- // version, and then picks the valid identifier off the start of that element.
- // It is used to determine if a local rename should be added to an import for
- // clarity.
- // This function could be moved to a standard package and exported if we want
- // for use in other tools.
- func importPathToAssumedName(importPath string) string {
- base := path.Base(importPath)
- if strings.HasPrefix(base, "v") {
- if _, err := strconv.Atoi(base[1:]); err == nil {
- dir := path.Dir(importPath)
- if dir != "." {
- base = path.Base(dir)
- }
- }
- }
- base = strings.TrimPrefix(base, "go-")
- if i := strings.IndexFunc(base, notIdentifier); i >= 0 {
- base = base[:i]
- }
- return base
- }
- // gopathResolver implements resolver for GOPATH workspaces.
- type gopathResolver struct {
- env *fixEnv
- }
- func (r *gopathResolver) loadPackageNames(importPaths []string, srcDir string) (map[string]string, error) {
- names := map[string]string{}
- for _, path := range importPaths {
- names[path] = importPathToName(r.env, path, srcDir)
- }
- return names, nil
- }
- // importPathToNameGoPath finds out the actual package name, as declared in its .go files.
- // If there's a problem, it returns "".
- func importPathToName(env *fixEnv, importPath, srcDir string) (packageName string) {
- // Fast path for standard library without going to disk.
- if _, ok := stdlib[importPath]; ok {
- return path.Base(importPath) // stdlib packages always match their paths.
- }
- buildPkg, err := env.buildContext().Import(importPath, srcDir, build.FindOnly)
- if err != nil {
- return ""
- }
- pkgName, err := packageDirToName(buildPkg.Dir)
- if err != nil {
- return ""
- }
- return pkgName
- }
- // packageDirToName is a faster version of build.Import if
- // the only thing desired is the package name. It uses build.FindOnly
- // to find the directory and then only parses one file in the package,
- // trusting that the files in the directory are consistent.
- func packageDirToName(dir string) (packageName string, err error) {
- d, err := os.Open(dir)
- if err != nil {
- return "", err
- }
- names, err := d.Readdirnames(-1)
- d.Close()
- if err != nil {
- return "", err
- }
- sort.Strings(names) // to have predictable behavior
- var lastErr error
- var nfile int
- for _, name := range names {
- if !strings.HasSuffix(name, ".go") {
- continue
- }
- if strings.HasSuffix(name, "_test.go") {
- continue
- }
- nfile++
- fullFile := filepath.Join(dir, name)
- fset := token.NewFileSet()
- f, err := parser.ParseFile(fset, fullFile, nil, parser.PackageClauseOnly)
- if err != nil {
- lastErr = err
- continue
- }
- pkgName := f.Name.Name
- if pkgName == "documentation" {
- // Special case from go/build.ImportDir, not
- // handled by ctx.MatchFile.
- continue
- }
- if pkgName == "main" {
- // Also skip package main, assuming it's a +build ignore generator or example.
- // Since you can't import a package main anyway, there's no harm here.
- continue
- }
- return pkgName, nil
- }
- if lastErr != nil {
- return "", lastErr
- }
- return "", fmt.Errorf("no importable package found in %d Go files", nfile)
- }
- type pkg struct {
- goPackage *packages.Package
- dir string // absolute file path to pkg directory ("/usr/lib/go/src/net/http")
- importPathShort string // vendorless import path ("net/http", "a/b")
- }
- type pkgDistance struct {
- pkg *pkg
- distance int // relative distance to target
- }
- // byDistanceOrImportPathShortLength sorts by relative distance breaking ties
- // on the short import path length and then the import string itself.
- type byDistanceOrImportPathShortLength []pkgDistance
- func (s byDistanceOrImportPathShortLength) Len() int { return len(s) }
- func (s byDistanceOrImportPathShortLength) Less(i, j int) bool {
- di, dj := s[i].distance, s[j].distance
- if di == -1 {
- return false
- }
- if dj == -1 {
- return true
- }
- if di != dj {
- return di < dj
- }
- vi, vj := s[i].pkg.importPathShort, s[j].pkg.importPathShort
- if len(vi) != len(vj) {
- return len(vi) < len(vj)
- }
- return vi < vj
- }
- func (s byDistanceOrImportPathShortLength) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
- func distance(basepath, targetpath string) int {
- p, err := filepath.Rel(basepath, targetpath)
- if err != nil {
- return -1
- }
- if p == "." {
- return 0
- }
- return strings.Count(p, string(filepath.Separator)) + 1
- }
- func (r *gopathResolver) scan(_ references) ([]*pkg, error) {
- dupCheck := make(map[string]bool)
- var result []*pkg
- var mu sync.Mutex
- add := func(root gopathwalk.Root, dir string) {
- mu.Lock()
- defer mu.Unlock()
- if _, dup := dupCheck[dir]; dup {
- return
- }
- dupCheck[dir] = true
- importpath := filepath.ToSlash(dir[len(root.Path)+len("/"):])
- result = append(result, &pkg{
- importPathShort: VendorlessPath(importpath),
- dir: dir,
- })
- }
- gopathwalk.Walk(gopathwalk.SrcDirsRoots(r.env.buildContext()), add, gopathwalk.Options{Debug: Debug, ModulesEnabled: false})
- return result, nil
- }
- // VendorlessPath returns the devendorized version of the import path ipath.
- // For example, VendorlessPath("foo/bar/vendor/a/b") returns "a/b".
- func VendorlessPath(ipath string) string {
- // Devendorize for use in import statement.
- if i := strings.LastIndex(ipath, "/vendor/"); i >= 0 {
- return ipath[i+len("/vendor/"):]
- }
- if strings.HasPrefix(ipath, "vendor/") {
- return ipath[len("vendor/"):]
- }
- return ipath
- }
- // loadExports returns the set of exported symbols in the package at dir.
- // It returns nil on error or if the package name in dir does not match expectPackage.
- func loadExports(ctx context.Context, env *fixEnv, expectPackage string, pkg *pkg) (map[string]bool, error) {
- if Debug {
- log.Printf("loading exports in dir %s (seeking package %s)", pkg.dir, expectPackage)
- }
- if pkg.goPackage != nil {
- exports := map[string]bool{}
- fset := token.NewFileSet()
- for _, fname := range pkg.goPackage.CompiledGoFiles {
- f, err := parser.ParseFile(fset, fname, nil, 0)
- if err != nil {
- return nil, fmt.Errorf("parsing %s: %v", fname, err)
- }
- for name := range f.Scope.Objects {
- if ast.IsExported(name) {
- exports[name] = true
- }
- }
- }
- return exports, nil
- }
- exports := make(map[string]bool)
- // Look for non-test, buildable .go files which could provide exports.
- all, err := ioutil.ReadDir(pkg.dir)
- if err != nil {
- return nil, err
- }
- var files []os.FileInfo
- for _, fi := range all {
- name := fi.Name()
- if !strings.HasSuffix(name, ".go") || strings.HasSuffix(name, "_test.go") {
- continue
- }
- match, err := env.buildContext().MatchFile(pkg.dir, fi.Name())
- if err != nil || !match {
- continue
- }
- files = append(files, fi)
- }
- if len(files) == 0 {
- return nil, fmt.Errorf("dir %v contains no buildable, non-test .go files", pkg.dir)
- }
- fset := token.NewFileSet()
- for _, fi := range files {
- select {
- case <-ctx.Done():
- return nil, ctx.Err()
- default:
- }
- fullFile := filepath.Join(pkg.dir, fi.Name())
- f, err := parser.ParseFile(fset, fullFile, nil, 0)
- if err != nil {
- return nil, fmt.Errorf("parsing %s: %v", fullFile, err)
- }
- pkgName := f.Name.Name
- if pkgName == "documentation" {
- // Special case from go/build.ImportDir, not
- // handled by MatchFile above.
- continue
- }
- if pkgName != expectPackage {
- return nil, fmt.Errorf("scan of dir %v is not expected package %v (actually %v)", pkg.dir, expectPackage, pkgName)
- }
- for name := range f.Scope.Objects {
- if ast.IsExported(name) {
- exports[name] = true
- }
- }
- }
- if Debug {
- exportList := make([]string, 0, len(exports))
- for k := range exports {
- exportList = append(exportList, k)
- }
- sort.Strings(exportList)
- log.Printf("loaded exports in dir %v (package %v): %v", pkg.dir, expectPackage, strings.Join(exportList, ", "))
- }
- return exports, nil
- }
- // findImport searches for a package with the given symbols.
- // If no package is found, findImport returns ("", false, nil)
- func findImport(ctx context.Context, env *fixEnv, dirScan []*pkg, pkgName string, symbols map[string]bool, filename string) (*pkg, error) {
- pkgDir, err := filepath.Abs(filename)
- if err != nil {
- return nil, err
- }
- pkgDir = filepath.Dir(pkgDir)
- // Find candidate packages, looking only at their directory names first.
- var candidates []pkgDistance
- for _, pkg := range dirScan {
- if pkgIsCandidate(filename, pkgName, pkg) {
- candidates = append(candidates, pkgDistance{
- pkg: pkg,
- distance: distance(pkgDir, pkg.dir),
- })
- }
- }
- // Sort the candidates by their import package length,
- // assuming that shorter package names are better than long
- // ones. Note that this sorts by the de-vendored name, so
- // there's no "penalty" for vendoring.
- sort.Sort(byDistanceOrImportPathShortLength(candidates))
- if Debug {
- for i, c := range candidates {
- log.Printf("%s candidate %d/%d: %v in %v", pkgName, i+1, len(candidates), c.pkg.importPathShort, c.pkg.dir)
- }
- }
- // Collect exports for packages with matching names.
- rescv := make([]chan *pkg, len(candidates))
- for i := range candidates {
- rescv[i] = make(chan *pkg, 1)
- }
- const maxConcurrentPackageImport = 4
- loadExportsSem := make(chan struct{}, maxConcurrentPackageImport)
- ctx, cancel := context.WithCancel(ctx)
- var wg sync.WaitGroup
- defer func() {
- cancel()
- wg.Wait()
- }()
- wg.Add(1)
- go func() {
- defer wg.Done()
- for i, c := range candidates {
- select {
- case loadExportsSem <- struct{}{}:
- case <-ctx.Done():
- return
- }
- wg.Add(1)
- go func(c pkgDistance, resc chan<- *pkg) {
- defer func() {
- <-loadExportsSem
- wg.Done()
- }()
- exports, err := loadExports(ctx, env, pkgName, c.pkg)
- if err != nil {
- if Debug {
- log.Printf("loading exports in dir %s (seeking package %s): %v", c.pkg.dir, pkgName, err)
- }
- resc <- nil
- return
- }
- // If it doesn't have the right
- // symbols, send nil to mean no match.
- for symbol := range symbols {
- if !exports[symbol] {
- resc <- nil
- return
- }
- }
- resc <- c.pkg
- }(c, rescv[i])
- }
- }()
- for _, resc := range rescv {
- pkg := <-resc
- if pkg == nil {
- continue
- }
- return pkg, nil
- }
- return nil, nil
- }
- // pkgIsCandidate reports whether pkg is a candidate for satisfying the
- // finding which package pkgIdent in the file named by filename is trying
- // to refer to.
- //
- // This check is purely lexical and is meant to be as fast as possible
- // because it's run over all $GOPATH directories to filter out poor
- // candidates in order to limit the CPU and I/O later parsing the
- // exports in candidate packages.
- //
- // filename is the file being formatted.
- // pkgIdent is the package being searched for, like "client" (if
- // searching for "client.New")
- func pkgIsCandidate(filename, pkgIdent string, pkg *pkg) bool {
- // Check "internal" and "vendor" visibility:
- if !canUse(filename, pkg.dir) {
- return false
- }
- // Speed optimization to minimize disk I/O:
- // the last two components on disk must contain the
- // package name somewhere.
- //
- // This permits mismatch naming like directory
- // "go-foo" being package "foo", or "pkg.v3" being "pkg",
- // or directory "google.golang.org/api/cloudbilling/v1"
- // being package "cloudbilling", but doesn't
- // permit a directory "foo" to be package
- // "bar", which is strongly discouraged
- // anyway. There's no reason goimports needs
- // to be slow just to accommodate that.
- lastTwo := lastTwoComponents(pkg.importPathShort)
- if strings.Contains(lastTwo, pkgIdent) {
- return true
- }
- if hasHyphenOrUpperASCII(lastTwo) && !hasHyphenOrUpperASCII(pkgIdent) {
- lastTwo = lowerASCIIAndRemoveHyphen(lastTwo)
- if strings.Contains(lastTwo, pkgIdent) {
- return true
- }
- }
- return false
- }
- func hasHyphenOrUpperASCII(s string) bool {
- for i := 0; i < len(s); i++ {
- b := s[i]
- if b == '-' || ('A' <= b && b <= 'Z') {
- return true
- }
- }
- return false
- }
- func lowerASCIIAndRemoveHyphen(s string) (ret string) {
- buf := make([]byte, 0, len(s))
- for i := 0; i < len(s); i++ {
- b := s[i]
- switch {
- case b == '-':
- continue
- case 'A' <= b && b <= 'Z':
- buf = append(buf, b+('a'-'A'))
- default:
- buf = append(buf, b)
- }
- }
- return string(buf)
- }
- // canUse reports whether the package in dir is usable from filename,
- // respecting the Go "internal" and "vendor" visibility rules.
- func canUse(filename, dir string) bool {
- // Fast path check, before any allocations. If it doesn't contain vendor
- // or internal, it's not tricky:
- // Note that this can false-negative on directories like "notinternal",
- // but we check it correctly below. This is just a fast path.
- if !strings.Contains(dir, "vendor") && !strings.Contains(dir, "internal") {
- return true
- }
- dirSlash := filepath.ToSlash(dir)
- if !strings.Contains(dirSlash, "/vendor/") && !strings.Contains(dirSlash, "/internal/") && !strings.HasSuffix(dirSlash, "/internal") {
- return true
- }
- // Vendor or internal directory only visible from children of parent.
- // That means the path from the current directory to the target directory
- // can contain ../vendor or ../internal but not ../foo/vendor or ../foo/internal
- // or bar/vendor or bar/internal.
- // After stripping all the leading ../, the only okay place to see vendor or internal
- // is at the very beginning of the path.
- absfile, err := filepath.Abs(filename)
- if err != nil {
- return false
- }
- absdir, err := filepath.Abs(dir)
- if err != nil {
- return false
- }
- rel, err := filepath.Rel(absfile, absdir)
- if err != nil {
- return false
- }
- relSlash := filepath.ToSlash(rel)
- if i := strings.LastIndex(relSlash, "../"); i >= 0 {
- relSlash = relSlash[i+len("../"):]
- }
- return !strings.Contains(relSlash, "/vendor/") && !strings.Contains(relSlash, "/internal/") && !strings.HasSuffix(relSlash, "/internal")
- }
- // lastTwoComponents returns at most the last two path components
- // of v, using either / or \ as the path separator.
- func lastTwoComponents(v string) string {
- nslash := 0
- for i := len(v) - 1; i >= 0; i-- {
- if v[i] == '/' || v[i] == '\\' {
- nslash++
- if nslash == 2 {
- return v[i:]
- }
- }
- }
- return v
- }
- type visitFn func(node ast.Node) ast.Visitor
- func (fn visitFn) Visit(node ast.Node) ast.Visitor {
- return fn(node)
- }
|