|
5 | 5 | package imports
|
6 | 6 |
|
7 | 7 | import (
|
8 |
| - "fmt" |
9 | 8 | "go/ast"
|
10 |
| - "go/build" |
11 |
| - "go/parser" |
12 | 9 | "go/token"
|
13 |
| - "os" |
14 | 10 | "path"
|
15 |
| - "path/filepath" |
16 | 11 | "strings"
|
17 |
| - "sync" |
18 | 12 |
|
19 | 13 | "golang.org/x/tools/go/ast/astutil"
|
20 | 14 | )
|
@@ -145,243 +139,41 @@ func fixImports(fset *token.FileSet, f *ast.File) (added []string, err error) {
|
145 | 139 | }
|
146 | 140 |
|
147 | 141 | // importPathToName returns the package name for the given import path.
|
148 |
| -var importPathToName = importPathToNameGoPath |
| 142 | +var importPathToName = importPathToNameBasic |
149 | 143 |
|
150 | 144 | // importPathToNameBasic assumes the package name is the base of import path.
|
151 | 145 | func importPathToNameBasic(importPath string) (packageName string) {
|
152 | 146 | return path.Base(importPath)
|
153 | 147 | }
|
154 | 148 |
|
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 | 149 | type pkg struct {
|
166 | 150 | importpath string // full pkg import path, e.g. "net/http"
|
167 | 151 | dir string // absolute file path to pkg directory e.g. "/usr/lib/go/src/fmt"
|
168 | 152 | }
|
169 | 153 |
|
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 | 154 | // findImport searches for a package with the given symbols.
|
302 | 155 | // If no package is found, findImport returns "".
|
303 | 156 | // Declared as a variable rather than a function so goimports can be easily
|
304 | 157 | // 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 |
| -} |
| 158 | +var findImport = findImportStdlib |
367 | 159 |
|
368 | 160 | type visitFn func(node ast.Node) ast.Visitor
|
369 | 161 |
|
370 | 162 | func (fn visitFn) Visit(node ast.Node) ast.Visitor {
|
371 | 163 | return fn(node)
|
372 | 164 | }
|
373 | 165 |
|
374 |
| -func findImportStdlib(shortPkg string, symbols map[string]bool) (importPath string, rename, ok bool) { |
| 166 | +func findImportStdlib(shortPkg string, symbols map[string]bool) (importPath string, rename bool, err error) { |
375 | 167 | for symbol := range symbols {
|
376 | 168 | path := stdlib[shortPkg+"."+symbol]
|
377 | 169 | if path == "" {
|
378 |
| - return "", false, false |
| 170 | + return "", false, nil |
379 | 171 | }
|
380 | 172 | if importPath != "" && importPath != path {
|
381 | 173 | // Ambiguous. Symbols pointed to different things.
|
382 |
| - return "", false, false |
| 174 | + return "", false, nil |
383 | 175 | }
|
384 | 176 | importPath = path
|
385 | 177 | }
|
386 |
| - return importPath, false, importPath != "" |
| 178 | + return importPath, false, nil |
387 | 179 | }
|
0 commit comments