go.tools/imports: move goimports from github to go.tools.
From revision d0880223588919729793727c9d65f202a73cda77. R=golang-dev, bradfitz CC=golang-dev https://golang.org/cl/35850048
This commit is contained in:
parent
53dfbf8a00
commit
c87866116c
|
|
@ -0,0 +1,30 @@
|
|||
This tool updates your Go import lines, adding missing ones and
|
||||
removing unreferenced ones.
|
||||
|
||||
$ go get code.google.com/p/go.tools/cmd/goimports
|
||||
|
||||
It's a fork of gofmt, and will also format your code, so it can be
|
||||
used as a replacement for your gofmt-on-save hook in your editor of
|
||||
choice.
|
||||
|
||||
For emacs, make sure you have the latest (Go 1.2) go-mode.el:
|
||||
https://go.googlecode.com/hg/misc/emacs/go-mode.el
|
||||
|
||||
Then in your .emacs file:
|
||||
(setq gofmt-command "goimports")
|
||||
(add-to-list 'load-path "/home/you/goroot/misc/emacs/")
|
||||
(require 'go-mode-load)
|
||||
(add-hook 'before-save-hook 'gofmt-before-save)
|
||||
|
||||
For vim, set "gofmt_command" to "goimports":
|
||||
|
||||
https://code.google.com/p/go/source/detail?r=39c724dd7f252
|
||||
https://code.google.com/p/go/source/browse#hg%2Fmisc%2Fvim
|
||||
etc
|
||||
|
||||
For GoSublime, follow the steps described here:
|
||||
http://mdwhatcott.wordpress.com/2013/12/15/installing-and-enabling-goimports-with-gosublime/
|
||||
|
||||
For other editors, you probably know what to do.
|
||||
|
||||
Happy hacking!
|
||||
|
|
@ -0,0 +1,193 @@
|
|||
// 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 main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"flag"
|
||||
"fmt"
|
||||
"go/scanner"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"code.google.com/p/go.tools/imports"
|
||||
)
|
||||
|
||||
var (
|
||||
// main operation modes
|
||||
list = flag.Bool("l", false, "list files whose formatting differs from goimport's")
|
||||
write = flag.Bool("w", false, "write result to (source) file instead of stdout")
|
||||
doDiff = flag.Bool("d", false, "display diffs instead of rewriting files")
|
||||
|
||||
options = &imports.Options{}
|
||||
exitCode = 0
|
||||
)
|
||||
|
||||
func init() {
|
||||
flag.BoolVar(&options.AllErrors, "e", false, "report all errors (not just the first 10 on different lines)")
|
||||
flag.BoolVar(&options.Comments, "comments", true, "print comments")
|
||||
flag.IntVar(&options.TabWidth, "tabwidth", 8, "tab width")
|
||||
flag.BoolVar(&options.TabIndent, "tabs", true, "indent with tabs")
|
||||
}
|
||||
|
||||
func report(err error) {
|
||||
scanner.PrintError(os.Stderr, err)
|
||||
exitCode = 2
|
||||
}
|
||||
|
||||
func usage() {
|
||||
fmt.Fprintf(os.Stderr, "usage: goimports [flags] [path ...]\n")
|
||||
flag.PrintDefaults()
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
func isGoFile(f os.FileInfo) bool {
|
||||
// ignore non-Go files
|
||||
name := f.Name()
|
||||
return !f.IsDir() && !strings.HasPrefix(name, ".") && strings.HasSuffix(name, ".go")
|
||||
}
|
||||
|
||||
func processFile(filename string, in io.Reader, out io.Writer, stdin bool) error {
|
||||
opt := options
|
||||
if stdin {
|
||||
nopt := *options
|
||||
nopt.Fragment = true
|
||||
opt = &nopt
|
||||
}
|
||||
|
||||
if in == nil {
|
||||
f, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
in = f
|
||||
}
|
||||
|
||||
src, err := ioutil.ReadAll(in)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
res, err := imports.Process(filename, src, opt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !bytes.Equal(src, res) {
|
||||
// formatting has changed
|
||||
if *list {
|
||||
fmt.Fprintln(out, filename)
|
||||
}
|
||||
if *write {
|
||||
err = ioutil.WriteFile(filename, res, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if *doDiff {
|
||||
data, err := diff(src, res)
|
||||
if err != nil {
|
||||
return fmt.Errorf("computing diff: %s", err)
|
||||
}
|
||||
fmt.Printf("diff %s gofmt/%s\n", filename, filename)
|
||||
out.Write(data)
|
||||
}
|
||||
}
|
||||
|
||||
if !*list && !*write && !*doDiff {
|
||||
_, err = out.Write(res)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func visitFile(path string, f os.FileInfo, err error) error {
|
||||
if err == nil && isGoFile(f) {
|
||||
err = processFile(path, nil, os.Stdout, false)
|
||||
}
|
||||
if err != nil {
|
||||
report(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func walkDir(path string) {
|
||||
filepath.Walk(path, visitFile)
|
||||
}
|
||||
|
||||
func main() {
|
||||
runtime.GOMAXPROCS(runtime.NumCPU())
|
||||
|
||||
// call gofmtMain in a separate function
|
||||
// so that it can use defer and have them
|
||||
// run before the exit.
|
||||
gofmtMain()
|
||||
os.Exit(exitCode)
|
||||
}
|
||||
|
||||
func gofmtMain() {
|
||||
flag.Usage = usage
|
||||
flag.Parse()
|
||||
|
||||
if options.TabWidth < 0 {
|
||||
fmt.Fprintf(os.Stderr, "negative tabwidth %d\n", options.TabWidth)
|
||||
exitCode = 2
|
||||
return
|
||||
}
|
||||
|
||||
if flag.NArg() == 0 {
|
||||
if err := processFile("<standard input>", os.Stdin, os.Stdout, true); err != nil {
|
||||
report(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
for i := 0; i < flag.NArg(); i++ {
|
||||
path := flag.Arg(i)
|
||||
switch dir, err := os.Stat(path); {
|
||||
case err != nil:
|
||||
report(err)
|
||||
case dir.IsDir():
|
||||
walkDir(path)
|
||||
default:
|
||||
if err := processFile(path, nil, os.Stdout, false); err != nil {
|
||||
report(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func diff(b1, b2 []byte) (data []byte, err error) {
|
||||
f1, err := ioutil.TempFile("", "gofmt")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer os.Remove(f1.Name())
|
||||
defer f1.Close()
|
||||
|
||||
f2, err := ioutil.TempFile("", "gofmt")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer os.Remove(f2.Name())
|
||||
defer f2.Close()
|
||||
|
||||
f1.Write(b1)
|
||||
f2.Write(b2)
|
||||
|
||||
data, err = exec.Command("diff", "-u", f1.Name(), f2.Name()).CombinedOutput()
|
||||
if len(data) > 0 {
|
||||
// diff exits with a non-zero status when the files don't match.
|
||||
// Ignore that failure as long as we get output.
|
||||
err = nil
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
@ -0,0 +1,328 @@
|
|||
// 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 (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/build"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"code.google.com/p/go.tools/astutil"
|
||||
)
|
||||
|
||||
// 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) {
|
||||
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
|
||||
}
|
||||
|
||||
func fixImports(f *ast.File) (added []string, err error) {
|
||||
// refs are a set of possible package references currently unsatisified by imports.
|
||||
// first key: either base package (e.g. "fmt") or renamed package
|
||||
// second key: referenced package symbol (e.g. "Println")
|
||||
refs := make(map[string]map[string]bool)
|
||||
|
||||
// decls are the current package imports. key is base package or renamed package.
|
||||
decls := make(map[string]*ast.ImportSpec)
|
||||
|
||||
// collect potential uses of packages.
|
||||
var visitor visitFn
|
||||
visitor = visitFn(func(node ast.Node) ast.Visitor {
|
||||
if node == nil {
|
||||
return visitor
|
||||
}
|
||||
switch v := node.(type) {
|
||||
case *ast.ImportSpec:
|
||||
if v.Name != nil {
|
||||
decls[v.Name.Name] = v
|
||||
} else {
|
||||
local := importPathToName(strings.Trim(v.Path.Value, `\"`))
|
||||
decls[local] = v
|
||||
}
|
||||
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
|
||||
}
|
||||
pkgName := xident.Name
|
||||
if refs[pkgName] == nil {
|
||||
refs[pkgName] = make(map[string]bool)
|
||||
}
|
||||
if decls[pkgName] == nil {
|
||||
refs[pkgName][v.Sel.Name] = true
|
||||
}
|
||||
}
|
||||
return visitor
|
||||
})
|
||||
ast.Walk(visitor, f)
|
||||
|
||||
// Search for imports matching potential package references.
|
||||
searches := 0
|
||||
type result struct {
|
||||
ipath string
|
||||
err error
|
||||
}
|
||||
results := make(chan result)
|
||||
for pkgName, symbols := range refs {
|
||||
if len(symbols) == 0 {
|
||||
continue // skip over packages already imported
|
||||
}
|
||||
go func(pkgName string, symbols map[string]bool) {
|
||||
ipath, err := findImport(pkgName, symbols)
|
||||
results <- result{ipath, err}
|
||||
}(pkgName, symbols)
|
||||
searches++
|
||||
}
|
||||
for i := 0; i < searches; i++ {
|
||||
result := <-results
|
||||
if result.err != nil {
|
||||
return nil, result.err
|
||||
}
|
||||
if result.ipath != "" {
|
||||
astutil.AddImport(fset, f, result.ipath)
|
||||
added = append(added, result.ipath)
|
||||
}
|
||||
}
|
||||
|
||||
// Nil out any unused ImportSpecs, to be removed in following passes
|
||||
unusedImport := map[string]bool{}
|
||||
for pkg, is := range decls {
|
||||
if refs[pkg] == nil && pkg != "_" && pkg != "." {
|
||||
unusedImport[strings.Trim(is.Path.Value, `"`)] = true
|
||||
}
|
||||
}
|
||||
for ipath := range unusedImport {
|
||||
if ipath == "C" {
|
||||
// Don't remove cgo stuff.
|
||||
continue
|
||||
}
|
||||
astutil.DeleteImport(fset, f, ipath)
|
||||
}
|
||||
|
||||
return added, nil
|
||||
}
|
||||
|
||||
// importPathToName returns the package name for the given import path.
|
||||
var importPathToName = importPathToNameGoPath
|
||||
|
||||
// importPathToNameBasic assumes the package name is the base of import path.
|
||||
func importPathToNameBasic(importPath string) (packageName string) {
|
||||
return path.Base(importPath)
|
||||
}
|
||||
|
||||
// importPathToNameGoPath finds out the actual package name, as declared in its .go files.
|
||||
// If there's a problem, it falls back to using importPathToNameBasic.
|
||||
func importPathToNameGoPath(importPath string) (packageName string) {
|
||||
if buildPkg, err := build.Import(importPath, "", 0); err == nil {
|
||||
return buildPkg.Name
|
||||
} else {
|
||||
return importPathToNameBasic(importPath)
|
||||
}
|
||||
}
|
||||
|
||||
type pkg struct {
|
||||
importpath string // full pkg import path, e.g. "net/http"
|
||||
dir string // absolute file path to pkg directory e.g. "/usr/lib/go/src/fmt"
|
||||
}
|
||||
|
||||
var pkgIndexOnce sync.Once
|
||||
|
||||
var pkgIndex struct {
|
||||
sync.Mutex
|
||||
m map[string][]pkg // shortname => []pkg, e.g "http" => "net/http"
|
||||
}
|
||||
|
||||
func loadPkgIndex() {
|
||||
pkgIndex.Lock()
|
||||
pkgIndex.m = make(map[string][]pkg)
|
||||
pkgIndex.Unlock()
|
||||
|
||||
var wg sync.WaitGroup
|
||||
for _, path := range build.Default.SrcDirs() {
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
fmt.Fprint(os.Stderr, err)
|
||||
continue
|
||||
}
|
||||
children, err := f.Readdir(-1)
|
||||
f.Close()
|
||||
if err != nil {
|
||||
fmt.Fprint(os.Stderr, err)
|
||||
continue
|
||||
}
|
||||
for _, child := range children {
|
||||
if child.IsDir() {
|
||||
wg.Add(1)
|
||||
go func(path, name string) {
|
||||
defer wg.Done()
|
||||
loadPkg(&wg, path, name)
|
||||
}(path, child.Name())
|
||||
}
|
||||
}
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
var fset = token.NewFileSet()
|
||||
|
||||
func loadPkg(wg *sync.WaitGroup, root, pkgrelpath string) {
|
||||
importpath := filepath.ToSlash(pkgrelpath)
|
||||
shortName := importPathToName(importpath)
|
||||
|
||||
dir := filepath.Join(root, importpath)
|
||||
pkgIndex.Lock()
|
||||
pkgIndex.m[shortName] = append(pkgIndex.m[shortName], pkg{
|
||||
importpath: importpath,
|
||||
dir: dir,
|
||||
})
|
||||
pkgIndex.Unlock()
|
||||
|
||||
pkgDir, err := os.Open(dir)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
children, err := pkgDir.Readdir(-1)
|
||||
pkgDir.Close()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for _, child := range children {
|
||||
name := child.Name()
|
||||
if name == "" {
|
||||
continue
|
||||
}
|
||||
if c := name[0]; c == '.' || ('0' <= c && c <= '9') {
|
||||
continue
|
||||
}
|
||||
if child.IsDir() {
|
||||
wg.Add(1)
|
||||
go func(root, name string) {
|
||||
defer wg.Done()
|
||||
loadPkg(wg, root, name)
|
||||
}(root, filepath.Join(importpath, name))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// loadExports returns a list exports for a package.
|
||||
var loadExports = loadExportsGoPath
|
||||
|
||||
func loadExportsGoPath(dir string) map[string]bool {
|
||||
exports := make(map[string]bool)
|
||||
buildPkg, err := build.ImportDir(dir, 0)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "no buildable Go source files in") {
|
||||
return nil
|
||||
}
|
||||
fmt.Fprintf(os.Stderr, "could not import %q: %v", dir, err)
|
||||
return nil
|
||||
}
|
||||
for _, file := range buildPkg.GoFiles {
|
||||
f, err := parser.ParseFile(fset, filepath.Join(dir, file), nil, 0)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "could not parse %q: %v", file, err)
|
||||
continue
|
||||
}
|
||||
for name := range f.Scope.Objects {
|
||||
if ast.IsExported(name) {
|
||||
exports[name] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
return exports
|
||||
}
|
||||
|
||||
// findImport searches for a package with the given symbols.
|
||||
// If no package is found, findImport returns "".
|
||||
// Declared as a variable rather than a function so goimports can be easily
|
||||
// extended by adding a file with an init function.
|
||||
var findImport = findImportGoPath
|
||||
|
||||
func findImportGoPath(pkgName string, symbols map[string]bool) (string, error) {
|
||||
|
||||
pkgIndexOnce.Do(loadPkgIndex)
|
||||
|
||||
// Collect exports for packages with matching names.
|
||||
var wg sync.WaitGroup
|
||||
var pkgsMu sync.Mutex // guards pkgs
|
||||
// full importpath => exported symbol => True
|
||||
// e.g. "net/http" => "Client" => True
|
||||
pkgs := make(map[string]map[string]bool)
|
||||
pkgIndex.Lock()
|
||||
for _, pkg := range pkgIndex.m[pkgName] {
|
||||
wg.Add(1)
|
||||
go func(importpath, dir string) {
|
||||
defer wg.Done()
|
||||
exports := loadExports(dir)
|
||||
if exports != nil {
|
||||
pkgsMu.Lock()
|
||||
pkgs[importpath] = exports
|
||||
pkgsMu.Unlock()
|
||||
}
|
||||
}(pkg.importpath, pkg.dir)
|
||||
}
|
||||
pkgIndex.Unlock()
|
||||
wg.Wait()
|
||||
|
||||
// Filter out packages missing required exported symbols.
|
||||
for symbol := range symbols {
|
||||
for importpath, exports := range pkgs {
|
||||
if !exports[symbol] {
|
||||
delete(pkgs, importpath)
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(pkgs) == 0 {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// If there are multiple candidate packages, the shortest one wins.
|
||||
// This is a heuristic to prefer the standard library (e.g. "bytes")
|
||||
// over e.g. "github.com/foo/bar/bytes".
|
||||
shortest := ""
|
||||
for importPath := range pkgs {
|
||||
if shortest == "" || len(importPath) < len(shortest) {
|
||||
shortest = importPath
|
||||
}
|
||||
}
|
||||
return shortest, nil
|
||||
}
|
||||
|
||||
type visitFn func(node ast.Node) ast.Visitor
|
||||
|
||||
func (fn visitFn) Visit(node ast.Node) ast.Visitor {
|
||||
return fn(node)
|
||||
}
|
||||
|
|
@ -0,0 +1,531 @@
|
|||
// 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"
|
||||
"flag"
|
||||
"go/build"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var only = flag.String("only", "", "If non-empty, the fix test to run")
|
||||
|
||||
var tests = []struct {
|
||||
name string
|
||||
in, out string
|
||||
}{
|
||||
// Adding an import to an existing parenthesized import
|
||||
{
|
||||
name: "factored_imports_add",
|
||||
in: `package foo
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
func bar() {
|
||||
var b bytes.Buffer
|
||||
fmt.Println(b.String())
|
||||
}
|
||||
`,
|
||||
out: `package foo
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func bar() {
|
||||
var b bytes.Buffer
|
||||
fmt.Println(b.String())
|
||||
}
|
||||
`,
|
||||
},
|
||||
|
||||
// Adding an import to an existing parenthesized import,
|
||||
// verifying it goes into the first section.
|
||||
{
|
||||
name: "factored_imports_add_first_sec",
|
||||
in: `package foo
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"appengine"
|
||||
)
|
||||
func bar() {
|
||||
var b bytes.Buffer
|
||||
_ = appengine.IsDevServer
|
||||
fmt.Println(b.String())
|
||||
}
|
||||
`,
|
||||
out: `package foo
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
|
||||
"appengine"
|
||||
)
|
||||
|
||||
func bar() {
|
||||
var b bytes.Buffer
|
||||
_ = appengine.IsDevServer
|
||||
fmt.Println(b.String())
|
||||
}
|
||||
`,
|
||||
},
|
||||
|
||||
// Adding an import to an existing parenthesized import,
|
||||
// verifying it goes into the first section. (test 2)
|
||||
{
|
||||
name: "factored_imports_add_first_sec_2",
|
||||
in: `package foo
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"appengine"
|
||||
)
|
||||
func bar() {
|
||||
_ = math.NaN
|
||||
_ = fmt.Sprintf
|
||||
_ = appengine.IsDevServer
|
||||
}
|
||||
`,
|
||||
out: `package foo
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
|
||||
"appengine"
|
||||
)
|
||||
|
||||
func bar() {
|
||||
_ = math.NaN
|
||||
_ = fmt.Sprintf
|
||||
_ = appengine.IsDevServer
|
||||
}
|
||||
`,
|
||||
},
|
||||
|
||||
// Adding a new import line, without parens
|
||||
{
|
||||
name: "add_import_section",
|
||||
in: `package foo
|
||||
func bar() {
|
||||
var b bytes.Buffer
|
||||
}
|
||||
`,
|
||||
out: `package foo
|
||||
|
||||
import "bytes"
|
||||
|
||||
func bar() {
|
||||
var b bytes.Buffer
|
||||
}
|
||||
`,
|
||||
},
|
||||
|
||||
// Adding two new imports, which should make a parenthesized import decl.
|
||||
{
|
||||
name: "add_import_paren_section",
|
||||
in: `package foo
|
||||
func bar() {
|
||||
_, _ := bytes.Buffer, zip.NewReader
|
||||
}
|
||||
`,
|
||||
out: `package foo
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"bytes"
|
||||
)
|
||||
|
||||
func bar() {
|
||||
_, _ := bytes.Buffer, zip.NewReader
|
||||
}
|
||||
`,
|
||||
},
|
||||
|
||||
// Make sure we don't add things twice
|
||||
{
|
||||
name: "no_double_add",
|
||||
in: `package foo
|
||||
func bar() {
|
||||
_, _ := bytes.Buffer, bytes.NewReader
|
||||
}
|
||||
`,
|
||||
out: `package foo
|
||||
|
||||
import "bytes"
|
||||
|
||||
func bar() {
|
||||
_, _ := bytes.Buffer, bytes.NewReader
|
||||
}
|
||||
`,
|
||||
},
|
||||
|
||||
// Remove unused imports, 1 of a factored block
|
||||
{
|
||||
name: "remove_unused_1_of_2",
|
||||
in: `package foo
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func bar() {
|
||||
_, _ := bytes.Buffer, bytes.NewReader
|
||||
}
|
||||
`,
|
||||
out: `package foo
|
||||
|
||||
import "bytes"
|
||||
|
||||
func bar() {
|
||||
_, _ := bytes.Buffer, bytes.NewReader
|
||||
}
|
||||
`,
|
||||
},
|
||||
|
||||
// Remove unused imports, 2 of 2
|
||||
{
|
||||
name: "remove_unused_2_of_2",
|
||||
in: `package foo
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func bar() {
|
||||
}
|
||||
`,
|
||||
out: `package foo
|
||||
|
||||
func bar() {
|
||||
}
|
||||
`,
|
||||
},
|
||||
|
||||
// Remove unused imports, 1 of 1
|
||||
{
|
||||
name: "remove_unused_1_of_1",
|
||||
in: `package foo
|
||||
|
||||
import "fmt"
|
||||
|
||||
func bar() {
|
||||
}
|
||||
`,
|
||||
out: `package foo
|
||||
|
||||
func bar() {
|
||||
}
|
||||
`,
|
||||
},
|
||||
|
||||
// Don't remove empty imports.
|
||||
{
|
||||
name: "dont_remove_empty_imports",
|
||||
in: `package foo
|
||||
import (
|
||||
_ "image/png"
|
||||
_ "image/jpeg"
|
||||
)
|
||||
`,
|
||||
out: `package foo
|
||||
|
||||
import (
|
||||
_ "image/jpeg"
|
||||
_ "image/png"
|
||||
)
|
||||
`,
|
||||
},
|
||||
|
||||
// Don't remove dot imports.
|
||||
{
|
||||
name: "dont_remove_dot_imports",
|
||||
in: `package foo
|
||||
import (
|
||||
. "foo"
|
||||
. "bar"
|
||||
)
|
||||
`,
|
||||
out: `package foo
|
||||
|
||||
import (
|
||||
. "bar"
|
||||
. "foo"
|
||||
)
|
||||
`,
|
||||
},
|
||||
|
||||
// Skip refs the parser can resolve.
|
||||
{
|
||||
name: "skip_resolved_refs",
|
||||
in: `package foo
|
||||
|
||||
func f() {
|
||||
type t struct{ Println func(string) }
|
||||
fmt := t{Println: func(string) {}}
|
||||
fmt.Println("foo")
|
||||
}
|
||||
`,
|
||||
out: `package foo
|
||||
|
||||
func f() {
|
||||
type t struct{ Println func(string) }
|
||||
fmt := t{Println: func(string) {}}
|
||||
fmt.Println("foo")
|
||||
}
|
||||
`,
|
||||
},
|
||||
|
||||
// Do not add a package we already have a resolution for.
|
||||
{
|
||||
name: "skip_template",
|
||||
in: `package foo
|
||||
|
||||
import "html/template"
|
||||
|
||||
func f() { t = template.New("sometemplate") }
|
||||
`,
|
||||
out: `package foo
|
||||
|
||||
import "html/template"
|
||||
|
||||
func f() { t = template.New("sometemplate") }
|
||||
`,
|
||||
},
|
||||
|
||||
// Don't touch cgo
|
||||
{
|
||||
name: "cgo",
|
||||
in: `package foo
|
||||
|
||||
/*
|
||||
#include <foo.h>
|
||||
*/
|
||||
import "C"
|
||||
`,
|
||||
out: `package foo
|
||||
|
||||
/*
|
||||
#include <foo.h>
|
||||
*/
|
||||
import "C"
|
||||
`,
|
||||
},
|
||||
|
||||
// Put some things in their own section
|
||||
{
|
||||
name: "make_sections",
|
||||
in: `package foo
|
||||
|
||||
import (
|
||||
"os"
|
||||
)
|
||||
|
||||
func foo () {
|
||||
_, _ = os.Args, fmt.Println
|
||||
_, _ = appengine.FooSomething, user.Current
|
||||
}
|
||||
`,
|
||||
out: `package foo
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"appengine"
|
||||
"appengine/user"
|
||||
)
|
||||
|
||||
func foo() {
|
||||
_, _ = os.Args, fmt.Println
|
||||
_, _ = appengine.FooSomething, user.Current
|
||||
}
|
||||
`,
|
||||
},
|
||||
|
||||
// Delete existing empty import block
|
||||
{
|
||||
name: "delete_empty_import_block",
|
||||
in: `package foo
|
||||
|
||||
import ()
|
||||
`,
|
||||
out: `package foo
|
||||
`,
|
||||
},
|
||||
|
||||
// Use existing empty import block
|
||||
{
|
||||
name: "use_empty_import_block",
|
||||
in: `package foo
|
||||
|
||||
import ()
|
||||
|
||||
func f() {
|
||||
_ = fmt.Println
|
||||
}
|
||||
`,
|
||||
out: `package foo
|
||||
|
||||
import "fmt"
|
||||
|
||||
func f() {
|
||||
_ = fmt.Println
|
||||
}
|
||||
`,
|
||||
},
|
||||
|
||||
// Blank line before adding new section.
|
||||
{
|
||||
name: "blank_line_before_new_group",
|
||||
in: `package foo
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
)
|
||||
|
||||
func f() {
|
||||
_ = net.Dial
|
||||
_ = fmt.Printf
|
||||
_ = snappy.Foo
|
||||
}
|
||||
`,
|
||||
out: `package foo
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"code.google.com/p/snappy-go/snappy"
|
||||
)
|
||||
|
||||
func f() {
|
||||
_ = net.Dial
|
||||
_ = fmt.Printf
|
||||
_ = snappy.Foo
|
||||
}
|
||||
`,
|
||||
},
|
||||
|
||||
// Blank line between standard library and third-party stuff.
|
||||
{
|
||||
name: "blank_line_separating_std_and_third_party",
|
||||
in: `package foo
|
||||
|
||||
import (
|
||||
"code.google.com/p/snappy-go/snappy"
|
||||
"fmt"
|
||||
"net"
|
||||
)
|
||||
|
||||
func f() {
|
||||
_ = net.Dial
|
||||
_ = fmt.Printf
|
||||
_ = snappy.Foo
|
||||
}
|
||||
`,
|
||||
out: `package foo
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"code.google.com/p/snappy-go/snappy"
|
||||
)
|
||||
|
||||
func f() {
|
||||
_ = net.Dial
|
||||
_ = fmt.Printf
|
||||
_ = snappy.Foo
|
||||
}
|
||||
`,
|
||||
},
|
||||
}
|
||||
|
||||
func TestFixImports(t *testing.T) {
|
||||
simplePkgs := map[string]string{
|
||||
"fmt": "fmt",
|
||||
"os": "os",
|
||||
"math": "math",
|
||||
"appengine": "appengine",
|
||||
"user": "appengine/user",
|
||||
"zip": "archive/zip",
|
||||
"bytes": "bytes",
|
||||
"snappy": "code.google.com/p/snappy-go/snappy",
|
||||
}
|
||||
findImport = func(pkgName string, symbols map[string]bool) (string, error) {
|
||||
return simplePkgs[pkgName], nil
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
if *only != "" && tt.name != *only {
|
||||
continue
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
err := processFile("foo.go", strings.NewReader(tt.in), &buf, false)
|
||||
if err != nil {
|
||||
t.Errorf("error on %q: %v", tt.name, err)
|
||||
continue
|
||||
}
|
||||
if got := buf.String(); got != tt.out {
|
||||
t.Errorf("results diff on %q\nGOT:\n%s\nWANT:\n%s\n", tt.name, got, tt.out)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindImportGoPath(t *testing.T) {
|
||||
goroot, err := ioutil.TempDir("", "goimports-")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(goroot)
|
||||
// Test against imaginary bits/bytes package in std lib
|
||||
bytesDir := filepath.Join(goroot, "src", "pkg", "bits", "bytes")
|
||||
if err := os.MkdirAll(bytesDir, 0755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
bytesSrcPath := filepath.Join(bytesDir, "bytes.go")
|
||||
bytesPkgPath := "bits/bytes"
|
||||
bytesSrc := []byte(`package bytes
|
||||
|
||||
type Buffer2 struct {}
|
||||
`)
|
||||
if err := ioutil.WriteFile(bytesSrcPath, bytesSrc, 0775); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
oldGOROOT := build.Default.GOROOT
|
||||
oldGOPATH := build.Default.GOPATH
|
||||
build.Default.GOROOT = goroot
|
||||
build.Default.GOPATH = ""
|
||||
defer func() {
|
||||
build.Default.GOROOT = oldGOROOT
|
||||
build.Default.GOPATH = oldGOPATH
|
||||
}()
|
||||
|
||||
got, err := findImportGoPath("bytes", map[string]bool{"Buffer2": true})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got != bytesPkgPath {
|
||||
t.Errorf(`findImportGoPath("bytes", Buffer2 ...)=%q, want "%s"`, got, bytesPkgPath)
|
||||
}
|
||||
|
||||
got, err = findImportGoPath("bytes", map[string]bool{"Missing": true})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got != "" {
|
||||
t.Errorf(`findImportGoPath("bytes", Missing ...)=%q, want ""`, got)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,240 @@
|
|||
// 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 implements a Go pretty-printer (like package "go/format")
|
||||
// that also adds or removes import statements as necessary.
|
||||
package imports
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/parser"
|
||||
"go/printer"
|
||||
"go/token"
|
||||
"io"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"code.google.com/p/go.tools/astutil"
|
||||
)
|
||||
|
||||
// Options specifies options for processing files.
|
||||
type Options struct {
|
||||
Fragment bool // Accept fragement of a source file (no package statement)
|
||||
AllErrors bool // Report all errors (not just the first 10 on different lines)
|
||||
|
||||
Comments bool // Print comments (true if nil *Options provided)
|
||||
TabIndent bool // Use tabs for indent (true if nil *Options provided)
|
||||
TabWidth int // Tab width (8 if nil *Options provided)
|
||||
}
|
||||
|
||||
// Process formats and adjusts imports for the provided file.
|
||||
// If opt is nil the defaults are used.
|
||||
func Process(filename string, src []byte, opt *Options) ([]byte, error) {
|
||||
if opt == nil {
|
||||
opt = &Options{Comments: true, TabIndent: true, TabWidth: 8}
|
||||
}
|
||||
|
||||
fileSet := token.NewFileSet()
|
||||
file, adjust, err := parse(fileSet, filename, src, opt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err = fixImports(file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sortImports(fileSet, file)
|
||||
imps := astutil.Imports(fileSet, file)
|
||||
|
||||
var spacesBefore []string // import paths we need spaces before
|
||||
if len(imps) == 1 {
|
||||
// We have just one block of imports. See if any are in different groups numbers.
|
||||
lastGroup := -1
|
||||
for _, importSpec := range imps[0] {
|
||||
importPath, _ := strconv.Unquote(importSpec.Path.Value)
|
||||
groupNum := importGroup(importPath)
|
||||
if groupNum != lastGroup && lastGroup != -1 {
|
||||
spacesBefore = append(spacesBefore, importPath)
|
||||
}
|
||||
lastGroup = groupNum
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
printerMode := printer.UseSpaces
|
||||
if opt.TabIndent {
|
||||
printerMode |= printer.TabIndent
|
||||
}
|
||||
printConfig := &printer.Config{Mode: printerMode, Tabwidth: opt.TabWidth}
|
||||
|
||||
var buf bytes.Buffer
|
||||
err = printConfig.Fprint(&buf, fileSet, file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := buf.Bytes()
|
||||
if adjust != nil {
|
||||
out = adjust(src, out)
|
||||
}
|
||||
if len(spacesBefore) > 0 {
|
||||
out = addImportSpaces(bytes.NewReader(out), spacesBefore)
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// parse parses src, which was read from filename,
|
||||
// as a Go source file or statement list.
|
||||
func parse(fset *token.FileSet, filename string, src []byte, opt *Options) (*ast.File, func(orig, src []byte) []byte, error) {
|
||||
parserMode := parser.Mode(0)
|
||||
if opt.Comments {
|
||||
parserMode |= parser.ParseComments
|
||||
}
|
||||
if opt.AllErrors {
|
||||
parserMode |= parser.AllErrors
|
||||
}
|
||||
|
||||
// Try as whole source file.
|
||||
file, err := parser.ParseFile(fset, filename, src, parserMode)
|
||||
if err == nil {
|
||||
return file, nil, nil
|
||||
}
|
||||
// If the error is that the source file didn't begin with a
|
||||
// package line and we accept fragmented input, fall through to
|
||||
// try as a source fragment. Stop and return on any other error.
|
||||
if !opt.Fragment || !strings.Contains(err.Error(), "expected 'package'") {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// If this is a declaration list, make it a source file
|
||||
// by inserting a package clause.
|
||||
// Insert using a ;, not a newline, so that the line numbers
|
||||
// in psrc match the ones in src.
|
||||
psrc := append([]byte("package p;"), src...)
|
||||
file, err = parser.ParseFile(fset, filename, psrc, parserMode)
|
||||
if err == nil {
|
||||
adjust := func(orig, src []byte) []byte {
|
||||
// Remove the package clause.
|
||||
// Gofmt has turned the ; into a \n.
|
||||
src = src[len("package p\n"):]
|
||||
return matchSpace(orig, src)
|
||||
}
|
||||
return file, adjust, nil
|
||||
}
|
||||
// If the error is that the source file didn't begin with a
|
||||
// declaration, fall through to try as a statement list.
|
||||
// Stop and return on any other error.
|
||||
if !strings.Contains(err.Error(), "expected declaration") {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// If this is a statement list, make it a source file
|
||||
// by inserting a package clause and turning the list
|
||||
// into a function body. This handles expressions too.
|
||||
// Insert using a ;, not a newline, so that the line numbers
|
||||
// in fsrc match the ones in src.
|
||||
fsrc := append(append([]byte("package p; func _() {"), src...), '}')
|
||||
file, err = parser.ParseFile(fset, filename, fsrc, parserMode)
|
||||
if err == nil {
|
||||
adjust := func(orig, src []byte) []byte {
|
||||
// Remove the wrapping.
|
||||
// Gofmt has turned the ; into a \n\n.
|
||||
src = src[len("package p\n\nfunc _() {"):]
|
||||
src = src[:len(src)-len("}\n")]
|
||||
// Gofmt has also indented the function body one level.
|
||||
// Remove that indent.
|
||||
src = bytes.Replace(src, []byte("\n\t"), []byte("\n"), -1)
|
||||
return matchSpace(orig, src)
|
||||
}
|
||||
return file, adjust, nil
|
||||
}
|
||||
|
||||
// Failed, and out of options.
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
func cutSpace(b []byte) (before, middle, after []byte) {
|
||||
i := 0
|
||||
for i < len(b) && (b[i] == ' ' || b[i] == '\t' || b[i] == '\n') {
|
||||
i++
|
||||
}
|
||||
j := len(b)
|
||||
for j > 0 && (b[j-1] == ' ' || b[j-1] == '\t' || b[j-1] == '\n') {
|
||||
j--
|
||||
}
|
||||
if i <= j {
|
||||
return b[:i], b[i:j], b[j:]
|
||||
}
|
||||
return nil, nil, b[j:]
|
||||
}
|
||||
|
||||
// matchSpace reformats src to use the same space context as orig.
|
||||
// 1) If orig begins with blank lines, matchSpace inserts them at the beginning of src.
|
||||
// 2) matchSpace copies the indentation of the first non-blank line in orig
|
||||
// to every non-blank line in src.
|
||||
// 3) matchSpace copies the trailing space from orig and uses it in place
|
||||
// of src's trailing space.
|
||||
func matchSpace(orig []byte, src []byte) []byte {
|
||||
before, _, after := cutSpace(orig)
|
||||
i := bytes.LastIndex(before, []byte{'\n'})
|
||||
before, indent := before[:i+1], before[i+1:]
|
||||
|
||||
_, src, _ = cutSpace(src)
|
||||
|
||||
var b bytes.Buffer
|
||||
b.Write(before)
|
||||
for len(src) > 0 {
|
||||
line := src
|
||||
if i := bytes.IndexByte(line, '\n'); i >= 0 {
|
||||
line, src = line[:i+1], line[i+1:]
|
||||
} else {
|
||||
src = nil
|
||||
}
|
||||
if len(line) > 0 && line[0] != '\n' { // not blank
|
||||
b.Write(indent)
|
||||
}
|
||||
b.Write(line)
|
||||
}
|
||||
b.Write(after)
|
||||
return b.Bytes()
|
||||
}
|
||||
|
||||
var impLine = regexp.MustCompile(`^\s+(?:\w+\s+)?"(.+)"`)
|
||||
|
||||
func addImportSpaces(r io.Reader, breaks []string) []byte {
|
||||
var out bytes.Buffer
|
||||
sc := bufio.NewScanner(r)
|
||||
inImports := false
|
||||
done := false
|
||||
for sc.Scan() {
|
||||
s := sc.Text()
|
||||
|
||||
if !inImports && !done && strings.HasPrefix(s, "import") {
|
||||
inImports = true
|
||||
}
|
||||
if inImports && (strings.HasPrefix(s, "var") ||
|
||||
strings.HasPrefix(s, "func") ||
|
||||
strings.HasPrefix(s, "const") ||
|
||||
strings.HasPrefix(s, "type")) {
|
||||
done = true
|
||||
inImports = false
|
||||
}
|
||||
if inImports && len(breaks) > 0 {
|
||||
if m := impLine.FindStringSubmatch(s); m != nil {
|
||||
if m[1] == string(breaks[0]) {
|
||||
out.WriteByte('\n')
|
||||
breaks = breaks[1:]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Fprintln(&out, s)
|
||||
}
|
||||
return out.Bytes()
|
||||
}
|
||||
|
|
@ -0,0 +1,173 @@
|
|||
// +build ignore
|
||||
|
||||
// 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.
|
||||
|
||||
// Command mkindex creates the file "pkgindex.go" containing an index of the Go
|
||||
// standard library. The file is intended to be built as part of the imports
|
||||
// package, so that the package may be used in environments where a GOROOT is
|
||||
// not available (such as App Engine).
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/build"
|
||||
"go/format"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
pkgIndex = make(map[string][]pkg)
|
||||
exports = make(map[string]map[string]bool)
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Don't use GOPATH.
|
||||
ctx := build.Default
|
||||
ctx.GOPATH = ""
|
||||
|
||||
// Populate pkgIndex global from GOROOT.
|
||||
for _, path := range ctx.SrcDirs() {
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
log.Print(err)
|
||||
continue
|
||||
}
|
||||
children, err := f.Readdir(-1)
|
||||
f.Close()
|
||||
if err != nil {
|
||||
log.Print(err)
|
||||
continue
|
||||
}
|
||||
for _, child := range children {
|
||||
if child.IsDir() {
|
||||
loadPkg(path, child.Name())
|
||||
}
|
||||
}
|
||||
}
|
||||
// Populate exports global.
|
||||
for _, ps := range pkgIndex {
|
||||
for _, p := range ps {
|
||||
e := loadExports(p.dir)
|
||||
if e != nil {
|
||||
exports[p.dir] = e
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Construct source file.
|
||||
var buf bytes.Buffer
|
||||
fmt.Fprint(&buf, pkgIndexHead)
|
||||
fmt.Fprintf(&buf, "var pkgIndexMaster = %#v\n", pkgIndex)
|
||||
fmt.Fprintf(&buf, "var exportsMaster = %#v\n", exports)
|
||||
src := buf.Bytes()
|
||||
|
||||
// Replace main.pkg type name with pkg.
|
||||
src = bytes.Replace(src, []byte("main.pkg"), []byte("pkg"), -1)
|
||||
// Replace actual GOROOT with "/go".
|
||||
src = bytes.Replace(src, []byte(ctx.GOROOT), []byte("/go"), -1)
|
||||
// Add some line wrapping.
|
||||
src = bytes.Replace(src, []byte("}, "), []byte("},\n"), -1)
|
||||
src = bytes.Replace(src, []byte("true, "), []byte("true,\n"), -1)
|
||||
|
||||
var err error
|
||||
src, err = format.Source(src)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Write out source file.
|
||||
err = ioutil.WriteFile("pkgindex.go", src, 0644)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
const pkgIndexHead = `package imports
|
||||
|
||||
func init() {
|
||||
pkgIndexOnce.Do(func() {
|
||||
pkgIndex.m = pkgIndexMaster
|
||||
})
|
||||
loadExports = func(dir string) map[string]bool {
|
||||
return exportsMaster[dir]
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
type pkg struct {
|
||||
importpath string // full pkg import path, e.g. "net/http"
|
||||
dir string // absolute file path to pkg directory e.g. "/usr/lib/go/src/fmt"
|
||||
}
|
||||
|
||||
var fset = token.NewFileSet()
|
||||
|
||||
func loadPkg(root, importpath string) {
|
||||
shortName := path.Base(importpath)
|
||||
if shortName == "testdata" {
|
||||
return
|
||||
}
|
||||
|
||||
dir := filepath.Join(root, importpath)
|
||||
pkgIndex[shortName] = append(pkgIndex[shortName], pkg{
|
||||
importpath: importpath,
|
||||
dir: dir,
|
||||
})
|
||||
|
||||
pkgDir, err := os.Open(dir)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
children, err := pkgDir.Readdir(-1)
|
||||
pkgDir.Close()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for _, child := range children {
|
||||
name := child.Name()
|
||||
if name == "" {
|
||||
continue
|
||||
}
|
||||
if c := name[0]; c == '.' || ('0' <= c && c <= '9') {
|
||||
continue
|
||||
}
|
||||
if child.IsDir() {
|
||||
loadPkg(root, filepath.Join(importpath, name))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func loadExports(dir string) map[string]bool {
|
||||
exports := make(map[string]bool)
|
||||
buildPkg, err := build.ImportDir(dir, 0)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "no buildable Go source files in") {
|
||||
return nil
|
||||
}
|
||||
log.Printf("could not import %q: %v", dir, err)
|
||||
return nil
|
||||
}
|
||||
for _, file := range buildPkg.GoFiles {
|
||||
f, err := parser.ParseFile(fset, filepath.Join(dir, file), nil, 0)
|
||||
if err != nil {
|
||||
log.Printf("could not parse %q: %v", file, err)
|
||||
continue
|
||||
}
|
||||
for name := range f.Scope.Objects {
|
||||
if ast.IsExported(name) {
|
||||
exports[name] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
return exports
|
||||
}
|
||||
|
|
@ -0,0 +1,214 @@
|
|||
// +build go1.2
|
||||
|
||||
// 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.
|
||||
|
||||
// Hacked up copy of go/ast/import.go
|
||||
|
||||
package imports
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"sort"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// sortImports sorts runs of consecutive import lines in import blocks in f.
|
||||
// It also removes duplicate imports when it is possible to do so without data loss.
|
||||
func sortImports(fset *token.FileSet, f *ast.File) {
|
||||
for i, d := range f.Decls {
|
||||
d, ok := d.(*ast.GenDecl)
|
||||
if !ok || d.Tok != token.IMPORT {
|
||||
// Not an import declaration, so we're done.
|
||||
// Imports are always first.
|
||||
break
|
||||
}
|
||||
|
||||
if len(d.Specs) == 0 {
|
||||
// Empty import block, remove it.
|
||||
f.Decls = append(f.Decls[:i], f.Decls[i+1:]...)
|
||||
}
|
||||
|
||||
if !d.Lparen.IsValid() {
|
||||
// Not a block: sorted by default.
|
||||
continue
|
||||
}
|
||||
|
||||
// Identify and sort runs of specs on successive lines.
|
||||
i := 0
|
||||
specs := d.Specs[:0]
|
||||
for j, s := range d.Specs {
|
||||
if j > i && fset.Position(s.Pos()).Line > 1+fset.Position(d.Specs[j-1].End()).Line {
|
||||
// j begins a new run. End this one.
|
||||
specs = append(specs, sortSpecs(fset, f, d.Specs[i:j])...)
|
||||
i = j
|
||||
}
|
||||
}
|
||||
specs = append(specs, sortSpecs(fset, f, d.Specs[i:])...)
|
||||
d.Specs = specs
|
||||
|
||||
// Deduping can leave a blank line before the rparen; clean that up.
|
||||
if len(d.Specs) > 0 {
|
||||
lastSpec := d.Specs[len(d.Specs)-1]
|
||||
lastLine := fset.Position(lastSpec.Pos()).Line
|
||||
if rParenLine := fset.Position(d.Rparen).Line; rParenLine > lastLine+1 {
|
||||
fset.File(d.Rparen).MergeLine(rParenLine - 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func importPath(s ast.Spec) string {
|
||||
t, err := strconv.Unquote(s.(*ast.ImportSpec).Path.Value)
|
||||
if err == nil {
|
||||
return t
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func importName(s ast.Spec) string {
|
||||
n := s.(*ast.ImportSpec).Name
|
||||
if n == nil {
|
||||
return ""
|
||||
}
|
||||
return n.Name
|
||||
}
|
||||
|
||||
func importComment(s ast.Spec) string {
|
||||
c := s.(*ast.ImportSpec).Comment
|
||||
if c == nil {
|
||||
return ""
|
||||
}
|
||||
return c.Text()
|
||||
}
|
||||
|
||||
// collapse indicates whether prev may be removed, leaving only next.
|
||||
func collapse(prev, next ast.Spec) bool {
|
||||
if importPath(next) != importPath(prev) || importName(next) != importName(prev) {
|
||||
return false
|
||||
}
|
||||
return prev.(*ast.ImportSpec).Comment == nil
|
||||
}
|
||||
|
||||
type posSpan struct {
|
||||
Start token.Pos
|
||||
End token.Pos
|
||||
}
|
||||
|
||||
func sortSpecs(fset *token.FileSet, f *ast.File, specs []ast.Spec) []ast.Spec {
|
||||
// Can't short-circuit here even if specs are already sorted,
|
||||
// since they might yet need deduplication.
|
||||
// A lone import, however, may be safely ignored.
|
||||
if len(specs) <= 1 {
|
||||
return specs
|
||||
}
|
||||
|
||||
// Record positions for specs.
|
||||
pos := make([]posSpan, len(specs))
|
||||
for i, s := range specs {
|
||||
pos[i] = posSpan{s.Pos(), s.End()}
|
||||
}
|
||||
|
||||
// Identify comments in this range.
|
||||
// Any comment from pos[0].Start to the final line counts.
|
||||
lastLine := fset.Position(pos[len(pos)-1].End).Line
|
||||
cstart := len(f.Comments)
|
||||
cend := len(f.Comments)
|
||||
for i, g := range f.Comments {
|
||||
if g.Pos() < pos[0].Start {
|
||||
continue
|
||||
}
|
||||
if i < cstart {
|
||||
cstart = i
|
||||
}
|
||||
if fset.Position(g.End()).Line > lastLine {
|
||||
cend = i
|
||||
break
|
||||
}
|
||||
}
|
||||
comments := f.Comments[cstart:cend]
|
||||
|
||||
// Assign each comment to the import spec preceding it.
|
||||
importComment := map[*ast.ImportSpec][]*ast.CommentGroup{}
|
||||
specIndex := 0
|
||||
for _, g := range comments {
|
||||
for specIndex+1 < len(specs) && pos[specIndex+1].Start <= g.Pos() {
|
||||
specIndex++
|
||||
}
|
||||
s := specs[specIndex].(*ast.ImportSpec)
|
||||
importComment[s] = append(importComment[s], g)
|
||||
}
|
||||
|
||||
// Sort the import specs by import path.
|
||||
// Remove duplicates, when possible without data loss.
|
||||
// Reassign the import paths to have the same position sequence.
|
||||
// Reassign each comment to abut the end of its spec.
|
||||
// Sort the comments by new position.
|
||||
sort.Sort(byImportSpec(specs))
|
||||
|
||||
// Dedup. Thanks to our sorting, we can just consider
|
||||
// adjacent pairs of imports.
|
||||
deduped := specs[:0]
|
||||
for i, s := range specs {
|
||||
if i == len(specs)-1 || !collapse(s, specs[i+1]) {
|
||||
deduped = append(deduped, s)
|
||||
} else {
|
||||
p := s.Pos()
|
||||
fset.File(p).MergeLine(fset.Position(p).Line)
|
||||
}
|
||||
}
|
||||
specs = deduped
|
||||
|
||||
// Fix up comment positions
|
||||
for i, s := range specs {
|
||||
s := s.(*ast.ImportSpec)
|
||||
if s.Name != nil {
|
||||
s.Name.NamePos = pos[i].Start
|
||||
}
|
||||
s.Path.ValuePos = pos[i].Start
|
||||
s.EndPos = pos[i].End
|
||||
for _, g := range importComment[s] {
|
||||
for _, c := range g.List {
|
||||
c.Slash = pos[i].End
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sort.Sort(byCommentPos(comments))
|
||||
|
||||
return specs
|
||||
}
|
||||
|
||||
type byImportSpec []ast.Spec // slice of *ast.ImportSpec
|
||||
|
||||
func (x byImportSpec) Len() int { return len(x) }
|
||||
func (x byImportSpec) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
|
||||
func (x byImportSpec) Less(i, j int) bool {
|
||||
ipath := importPath(x[i])
|
||||
jpath := importPath(x[j])
|
||||
|
||||
igroup := importGroup(ipath)
|
||||
jgroup := importGroup(jpath)
|
||||
if igroup != jgroup {
|
||||
return igroup < jgroup
|
||||
}
|
||||
|
||||
if ipath != jpath {
|
||||
return ipath < jpath
|
||||
}
|
||||
iname := importName(x[i])
|
||||
jname := importName(x[j])
|
||||
|
||||
if iname != jname {
|
||||
return iname < jname
|
||||
}
|
||||
return importComment(x[i]) < importComment(x[j])
|
||||
}
|
||||
|
||||
type byCommentPos []*ast.CommentGroup
|
||||
|
||||
func (x byCommentPos) Len() int { return len(x) }
|
||||
func (x byCommentPos) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
|
||||
func (x byCommentPos) Less(i, j int) bool { return x[i].Pos() < x[j].Pos() }
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
// +build !go1.2
|
||||
|
||||
// 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 "go/ast"
|
||||
|
||||
// Go 1.1 users don't get fancy package grouping.
|
||||
// But this is still gofmt-compliant:
|
||||
|
||||
var sortImports = ast.SortImports
|
||||
Loading…
Reference in New Issue