Skip to content

Commit 5a3b554

Browse files
committed
Vendor copies of x/tools/imports, cmd/api packages.
1 parent 0bd6304 commit 5a3b554

File tree

6 files changed

+10172
-0
lines changed

6 files changed

+10172
-0
lines changed

playground/internal/imports/fix.go

Lines changed: 387 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,387 @@
1+
// Copyright 2013 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+
package imports
6+
7+
import (
8+
"fmt"
9+
"go/ast"
10+
"go/build"
11+
"go/parser"
12+
"go/token"
13+
"os"
14+
"path"
15+
"path/filepath"
16+
"strings"
17+
"sync"
18+
19+
"golang.org/x/tools/go/ast/astutil"
20+
)
21+
22+
// importToGroup is a list of functions which map from an import path to
23+
// a group number.
24+
var importToGroup = []func(importPath string) (num int, ok bool){
25+
func(importPath string) (num int, ok bool) {
26+
if strings.HasPrefix(importPath, "appengine") {
27+
return 2, true
28+
}
29+
return
30+
},
31+
func(importPath string) (num int, ok bool) {
32+
if strings.Contains(importPath, ".") {
33+
return 1, true
34+
}
35+
return
36+
},
37+
}
38+
39+
func importGroup(importPath string) int {
40+
for _, fn := range importToGroup {
41+
if n, ok := fn(importPath); ok {
42+
return n
43+
}
44+
}
45+
return 0
46+
}
47+
48+
func fixImports(fset *token.FileSet, f *ast.File) (added []string, err error) {
49+
// refs are a set of possible package references currently unsatisfied by imports.
50+
// first key: either base package (e.g. "fmt") or renamed package
51+
// second key: referenced package symbol (e.g. "Println")
52+
refs := make(map[string]map[string]bool)
53+
54+
// decls are the current package imports. key is base package or renamed package.
55+
decls := make(map[string]*ast.ImportSpec)
56+
57+
// collect potential uses of packages.
58+
var visitor visitFn
59+
visitor = visitFn(func(node ast.Node) ast.Visitor {
60+
if node == nil {
61+
return visitor
62+
}
63+
switch v := node.(type) {
64+
case *ast.ImportSpec:
65+
if v.Name != nil {
66+
decls[v.Name.Name] = v
67+
} else {
68+
local := importPathToName(strings.Trim(v.Path.Value, `\"`))
69+
decls[local] = v
70+
}
71+
case *ast.SelectorExpr:
72+
xident, ok := v.X.(*ast.Ident)
73+
if !ok {
74+
break
75+
}
76+
if xident.Obj != nil {
77+
// if the parser can resolve it, it's not a package ref
78+
break
79+
}
80+
pkgName := xident.Name
81+
if refs[pkgName] == nil {
82+
refs[pkgName] = make(map[string]bool)
83+
}
84+
if decls[pkgName] == nil {
85+
refs[pkgName][v.Sel.Name] = true
86+
}
87+
}
88+
return visitor
89+
})
90+
ast.Walk(visitor, f)
91+
92+
// Nil out any unused ImportSpecs, to be removed in following passes
93+
unusedImport := map[string]bool{}
94+
for pkg, is := range decls {
95+
if refs[pkg] == nil && pkg != "_" && pkg != "." {
96+
unusedImport[strings.Trim(is.Path.Value, `"`)] = true
97+
}
98+
}
99+
for ipath := range unusedImport {
100+
if ipath == "C" {
101+
// Don't remove cgo stuff.
102+
continue
103+
}
104+
astutil.DeleteImport(fset, f, ipath)
105+
}
106+
107+
// Search for imports matching potential package references.
108+
searches := 0
109+
type result struct {
110+
ipath string
111+
name string
112+
err error
113+
}
114+
results := make(chan result)
115+
for pkgName, symbols := range refs {
116+
if len(symbols) == 0 {
117+
continue // skip over packages already imported
118+
}
119+
go func(pkgName string, symbols map[string]bool) {
120+
ipath, rename, err := findImport(pkgName, symbols)
121+
r := result{ipath: ipath, err: err}
122+
if rename {
123+
r.name = pkgName
124+
}
125+
results <- r
126+
}(pkgName, symbols)
127+
searches++
128+
}
129+
for i := 0; i < searches; i++ {
130+
result := <-results
131+
if result.err != nil {
132+
return nil, result.err
133+
}
134+
if result.ipath != "" {
135+
if result.name != "" {
136+
astutil.AddNamedImport(fset, f, result.name, result.ipath)
137+
} else {
138+
astutil.AddImport(fset, f, result.ipath)
139+
}
140+
added = append(added, result.ipath)
141+
}
142+
}
143+
144+
return added, nil
145+
}
146+
147+
// importPathToName returns the package name for the given import path.
148+
var importPathToName = importPathToNameGoPath
149+
150+
// importPathToNameBasic assumes the package name is the base of import path.
151+
func importPathToNameBasic(importPath string) (packageName string) {
152+
return path.Base(importPath)
153+
}
154+
155+
// importPathToNameGoPath finds out the actual package name, as declared in its .go files.
156+
// If there's a problem, it falls back to using importPathToNameBasic.
157+
func importPathToNameGoPath(importPath string) (packageName string) {
158+
if buildPkg, err := build.Import(importPath, "", 0); err == nil {
159+
return buildPkg.Name
160+
} else {
161+
return importPathToNameBasic(importPath)
162+
}
163+
}
164+
165+
type pkg struct {
166+
importpath string // full pkg import path, e.g. "net/http"
167+
dir string // absolute file path to pkg directory e.g. "/usr/lib/go/src/fmt"
168+
}
169+
170+
var pkgIndexOnce sync.Once
171+
172+
var pkgIndex struct {
173+
sync.Mutex
174+
m map[string][]pkg // shortname => []pkg, e.g "http" => "net/http"
175+
}
176+
177+
// gate is a semaphore for limiting concurrency.
178+
type gate chan struct{}
179+
180+
func (g gate) enter() { g <- struct{}{} }
181+
func (g gate) leave() { <-g }
182+
183+
// fsgate protects the OS & filesystem from too much concurrency.
184+
// Too much disk I/O -> too many threads -> swapping and bad scheduling.
185+
var fsgate = make(gate, 8)
186+
187+
func loadPkgIndex() {
188+
pkgIndex.Lock()
189+
pkgIndex.m = make(map[string][]pkg)
190+
pkgIndex.Unlock()
191+
192+
var wg sync.WaitGroup
193+
for _, path := range build.Default.SrcDirs() {
194+
fsgate.enter()
195+
f, err := os.Open(path)
196+
if err != nil {
197+
fsgate.leave()
198+
fmt.Fprint(os.Stderr, err)
199+
continue
200+
}
201+
children, err := f.Readdir(-1)
202+
f.Close()
203+
fsgate.leave()
204+
if err != nil {
205+
fmt.Fprint(os.Stderr, err)
206+
continue
207+
}
208+
for _, child := range children {
209+
if child.IsDir() {
210+
wg.Add(1)
211+
go func(path, name string) {
212+
defer wg.Done()
213+
loadPkg(&wg, path, name)
214+
}(path, child.Name())
215+
}
216+
}
217+
}
218+
wg.Wait()
219+
}
220+
221+
func loadPkg(wg *sync.WaitGroup, root, pkgrelpath string) {
222+
importpath := filepath.ToSlash(pkgrelpath)
223+
dir := filepath.Join(root, importpath)
224+
225+
fsgate.enter()
226+
defer fsgate.leave()
227+
pkgDir, err := os.Open(dir)
228+
if err != nil {
229+
return
230+
}
231+
children, err := pkgDir.Readdir(-1)
232+
pkgDir.Close()
233+
if err != nil {
234+
return
235+
}
236+
// hasGo tracks whether a directory actually appears to be a
237+
// Go source code directory. If $GOPATH == $HOME, and
238+
// $HOME/src has lots of other large non-Go projects in it,
239+
// then the calls to importPathToName below can be expensive.
240+
hasGo := false
241+
for _, child := range children {
242+
// Avoid .foo, _foo, and testdata directory trees.
243+
name := child.Name()
244+
if name == "" || name[0] == '.' || name[0] == '_' || name == "testdata" {
245+
continue
246+
}
247+
if strings.HasSuffix(name, ".go") {
248+
hasGo = true
249+
}
250+
if child.IsDir() {
251+
wg.Add(1)
252+
go func(root, name string) {
253+
defer wg.Done()
254+
loadPkg(wg, root, name)
255+
}(root, filepath.Join(importpath, name))
256+
}
257+
}
258+
if hasGo {
259+
shortName := importPathToName(importpath)
260+
pkgIndex.Lock()
261+
pkgIndex.m[shortName] = append(pkgIndex.m[shortName], pkg{
262+
importpath: importpath,
263+
dir: dir,
264+
})
265+
pkgIndex.Unlock()
266+
}
267+
268+
}
269+
270+
// loadExports returns a list exports for a package.
271+
var loadExports = loadExportsGoPath
272+
273+
func loadExportsGoPath(dir string) map[string]bool {
274+
exports := make(map[string]bool)
275+
buildPkg, err := build.ImportDir(dir, 0)
276+
if err != nil {
277+
if strings.Contains(err.Error(), "no buildable Go source files in") {
278+
return nil
279+
}
280+
fmt.Fprintf(os.Stderr, "could not import %q: %v\n", dir, err)
281+
return nil
282+
}
283+
fset := token.NewFileSet()
284+
for _, files := range [...][]string{buildPkg.GoFiles, buildPkg.CgoFiles} {
285+
for _, file := range files {
286+
f, err := parser.ParseFile(fset, filepath.Join(dir, file), nil, 0)
287+
if err != nil {
288+
fmt.Fprintf(os.Stderr, "could not parse %q: %v\n", file, err)
289+
continue
290+
}
291+
for name := range f.Scope.Objects {
292+
if ast.IsExported(name) {
293+
exports[name] = true
294+
}
295+
}
296+
}
297+
}
298+
return exports
299+
}
300+
301+
// findImport searches for a package with the given symbols.
302+
// If no package is found, findImport returns "".
303+
// Declared as a variable rather than a function so goimports can be easily
304+
// extended by adding a file with an init function.
305+
var findImport = findImportGoPath
306+
307+
func findImportGoPath(pkgName string, symbols map[string]bool) (string, bool, error) {
308+
// Fast path for the standard library.
309+
// In the common case we hopefully never have to scan the GOPATH, which can
310+
// be slow with moving disks.
311+
if pkg, rename, ok := findImportStdlib(pkgName, symbols); ok {
312+
return pkg, rename, nil
313+
}
314+
315+
// TODO(sameer): look at the import lines for other Go files in the
316+
// local directory, since the user is likely to import the same packages
317+
// in the current Go file. Return rename=true when the other Go files
318+
// use a renamed package that's also used in the current file.
319+
320+
pkgIndexOnce.Do(loadPkgIndex)
321+
322+
// Collect exports for packages with matching names.
323+
var wg sync.WaitGroup
324+
var pkgsMu sync.Mutex // guards pkgs
325+
// full importpath => exported symbol => True
326+
// e.g. "net/http" => "Client" => True
327+
pkgs := make(map[string]map[string]bool)
328+
pkgIndex.Lock()
329+
for _, pkg := range pkgIndex.m[pkgName] {
330+
wg.Add(1)
331+
go func(importpath, dir string) {
332+
defer wg.Done()
333+
exports := loadExports(dir)
334+
if exports != nil {
335+
pkgsMu.Lock()
336+
pkgs[importpath] = exports
337+
pkgsMu.Unlock()
338+
}
339+
}(pkg.importpath, pkg.dir)
340+
}
341+
pkgIndex.Unlock()
342+
wg.Wait()
343+
344+
// Filter out packages missing required exported symbols.
345+
for symbol := range symbols {
346+
for importpath, exports := range pkgs {
347+
if !exports[symbol] {
348+
delete(pkgs, importpath)
349+
}
350+
}
351+
}
352+
if len(pkgs) == 0 {
353+
return "", false, nil
354+
}
355+
356+
// If there are multiple candidate packages, the shortest one wins.
357+
// This is a heuristic to prefer the standard library (e.g. "bytes")
358+
// over e.g. "github.com/foo/bar/bytes".
359+
shortest := ""
360+
for importPath := range pkgs {
361+
if shortest == "" || len(importPath) < len(shortest) {
362+
shortest = importPath
363+
}
364+
}
365+
return shortest, false, nil
366+
}
367+
368+
type visitFn func(node ast.Node) ast.Visitor
369+
370+
func (fn visitFn) Visit(node ast.Node) ast.Visitor {
371+
return fn(node)
372+
}
373+
374+
func findImportStdlib(shortPkg string, symbols map[string]bool) (importPath string, rename, ok bool) {
375+
for symbol := range symbols {
376+
path := stdlib[shortPkg+"."+symbol]
377+
if path == "" {
378+
return "", false, false
379+
}
380+
if importPath != "" && importPath != path {
381+
// Ambiguous. Symbols pointed to different things.
382+
return "", false, false
383+
}
384+
importPath = path
385+
}
386+
return importPath, false, importPath != ""
387+
}

0 commit comments

Comments
 (0)