Skip to content

Commit 7f5af7c

Browse files
committed
Merge pull request #31 from gopherjs/goimports
playground: Add ability to fix imports via goimports.
2 parents 0bd6304 + dbe1134 commit 7f5af7c

File tree

11 files changed

+5649
-39
lines changed

11 files changed

+5649
-39
lines changed

playground/index.html

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@
1212
<span id="controls">
1313
<input type="button" value="Run" ng-click="run(false)" />
1414
<input type="button" value="Format" ng-click="format()" />
15+
<label title="Rewrite imports on Format">
16+
<input ng-model="imports" type="checkbox" />Imports
17+
</label>
1518
<input type="button" value="Share" ng-click="share()" />
1619
<input type="text" class="show-share-url-{{showShareUrl}}" id="share-url" value="{{shareUrl}}" onfocus="select()" />
1720
</span>

playground/internal/imports/fix.go

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
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+
"go/ast"
9+
"go/token"
10+
"path"
11+
"strings"
12+
13+
"golang.org/x/tools/go/ast/astutil"
14+
)
15+
16+
// importToGroup is a list of functions which map from an import path to
17+
// a group number.
18+
var importToGroup = []func(importPath string) (num int, ok bool){
19+
func(importPath string) (num int, ok bool) {
20+
if strings.HasPrefix(importPath, "appengine") {
21+
return 2, true
22+
}
23+
return
24+
},
25+
func(importPath string) (num int, ok bool) {
26+
if strings.Contains(importPath, ".") {
27+
return 1, true
28+
}
29+
return
30+
},
31+
}
32+
33+
func importGroup(importPath string) int {
34+
for _, fn := range importToGroup {
35+
if n, ok := fn(importPath); ok {
36+
return n
37+
}
38+
}
39+
return 0
40+
}
41+
42+
func fixImports(fset *token.FileSet, f *ast.File) (added []string, err error) {
43+
// refs are a set of possible package references currently unsatisfied by imports.
44+
// first key: either base package (e.g. "fmt") or renamed package
45+
// second key: referenced package symbol (e.g. "Println")
46+
refs := make(map[string]map[string]bool)
47+
48+
// decls are the current package imports. key is base package or renamed package.
49+
decls := make(map[string]*ast.ImportSpec)
50+
51+
// collect potential uses of packages.
52+
var visitor visitFn
53+
visitor = visitFn(func(node ast.Node) ast.Visitor {
54+
if node == nil {
55+
return visitor
56+
}
57+
switch v := node.(type) {
58+
case *ast.ImportSpec:
59+
if v.Name != nil {
60+
decls[v.Name.Name] = v
61+
} else {
62+
local := importPathToName(strings.Trim(v.Path.Value, `\"`))
63+
decls[local] = v
64+
}
65+
case *ast.SelectorExpr:
66+
xident, ok := v.X.(*ast.Ident)
67+
if !ok {
68+
break
69+
}
70+
if xident.Obj != nil {
71+
// if the parser can resolve it, it's not a package ref
72+
break
73+
}
74+
pkgName := xident.Name
75+
if refs[pkgName] == nil {
76+
refs[pkgName] = make(map[string]bool)
77+
}
78+
if decls[pkgName] == nil {
79+
refs[pkgName][v.Sel.Name] = true
80+
}
81+
}
82+
return visitor
83+
})
84+
ast.Walk(visitor, f)
85+
86+
// Nil out any unused ImportSpecs, to be removed in following passes
87+
unusedImport := map[string]bool{}
88+
for pkg, is := range decls {
89+
if refs[pkg] == nil && pkg != "_" && pkg != "." {
90+
unusedImport[strings.Trim(is.Path.Value, `"`)] = true
91+
}
92+
}
93+
for ipath := range unusedImport {
94+
if ipath == "C" {
95+
// Don't remove cgo stuff.
96+
continue
97+
}
98+
astutil.DeleteImport(fset, f, ipath)
99+
}
100+
101+
// Search for imports matching potential package references.
102+
searches := 0
103+
type result struct {
104+
ipath string
105+
name string
106+
err error
107+
}
108+
results := make(chan result)
109+
for pkgName, symbols := range refs {
110+
if len(symbols) == 0 {
111+
continue // skip over packages already imported
112+
}
113+
go func(pkgName string, symbols map[string]bool) {
114+
ipath, rename, err := findImport(pkgName, symbols)
115+
r := result{ipath: ipath, err: err}
116+
if rename {
117+
r.name = pkgName
118+
}
119+
results <- r
120+
}(pkgName, symbols)
121+
searches++
122+
}
123+
for i := 0; i < searches; i++ {
124+
result := <-results
125+
if result.err != nil {
126+
return nil, result.err
127+
}
128+
if result.ipath != "" {
129+
if result.name != "" {
130+
astutil.AddNamedImport(fset, f, result.name, result.ipath)
131+
} else {
132+
astutil.AddImport(fset, f, result.ipath)
133+
}
134+
added = append(added, result.ipath)
135+
}
136+
}
137+
138+
return added, nil
139+
}
140+
141+
// importPathToName returns the package name for the given import path.
142+
var importPathToName = importPathToNameBasic
143+
144+
// importPathToNameBasic assumes the package name is the base of import path.
145+
func importPathToNameBasic(importPath string) (packageName string) {
146+
return path.Base(importPath)
147+
}
148+
149+
type pkg struct {
150+
importpath string // full pkg import path, e.g. "net/http"
151+
dir string // absolute file path to pkg directory e.g. "/usr/lib/go/src/fmt"
152+
}
153+
154+
// findImport searches for a package with the given symbols.
155+
// If no package is found, findImport returns "".
156+
// Declared as a variable rather than a function so goimports can be easily
157+
// extended by adding a file with an init function.
158+
var findImport = findImportStdlib
159+
160+
type visitFn func(node ast.Node) ast.Visitor
161+
162+
func (fn visitFn) Visit(node ast.Node) ast.Visitor {
163+
return fn(node)
164+
}
165+
166+
func findImportStdlib(shortPkg string, symbols map[string]bool) (importPath string, rename bool, err error) {
167+
for symbol := range symbols {
168+
path := stdlib[shortPkg+"."+symbol]
169+
if path == "" {
170+
return "", false, nil
171+
}
172+
if importPath != "" && importPath != path {
173+
// Ambiguous. Symbols pointed to different things.
174+
return "", false, nil
175+
}
176+
importPath = path
177+
}
178+
return importPath, false, nil
179+
}

playground/internal/imports/gen.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
//go:generate go run mkapi.go -output=gopherjs.txt -gopath github.com/gopherjs/gopherjs/js
2+
//go:generate go run mkstdlib.go -output=zstdlib
3+
//go:generate rm gopherjs.txt
4+
5+
package imports

0 commit comments

Comments
 (0)