forked from robfig/goimports
/
fix.go
134 lines (120 loc) · 3 KB
/
fix.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
package main
import (
"go/ast"
"go/token"
"path"
"strconv"
"strings"
)
func fixImports(f *ast.File) {
declShort := map[string]*ast.ImportSpec{} // key: either base package "fmt", "http" or renamed package
usedShort := map[string]bool{} // Same key
var genDecls []*ast.GenDecl
addImport := func(ipath string) {
is := &ast.ImportSpec{
Path: &ast.BasicLit{
Kind: token.STRING,
Value: strconv.Quote(ipath),
},
}
declShort[path.Base(ipath)] = is
if len(genDecls) == 0 {
genDecls = append(genDecls, &ast.GenDecl{
Tok: token.IMPORT,
})
f.Decls = append([]ast.Decl{genDecls[0]}, f.Decls...)
f.Imports = append(f.Imports, is)
}
gd0 := genDecls[0]
// Prepend onto gd0.Specs:
// Make room for it (nil), slide everything down, then set [0].
gd0.Specs = append(gd0.Specs, nil)
copy(gd0.Specs[1:], gd0.Specs[:])
gd0.Specs[0] = is
if len(gd0.Specs) > 1 && gd0.Lparen == 0 {
gd0.Lparen = 1 // something not zero
}
}
var visitor visitFn
depth := 0
visitor = visitFn(func(node ast.Node) ast.Visitor {
if node == nil {
depth--
return visitor
}
depth++
switch v := node.(type) {
case *ast.GenDecl:
if v.Tok == token.IMPORT {
genDecls = append(genDecls, v)
}
case *ast.ImportSpec:
if v.Name != nil {
declShort[v.Name.Name] = v
} else {
local := path.Base(strings.Trim(v.Path.Value, `\"`))
declShort[local] = v
}
case *ast.SelectorExpr:
if xident, ok := v.X.(*ast.Ident); ok {
pkgName := xident.Name
usedShort[pkgName] = true
if declShort[pkgName] == nil {
key := pkgName + "." + v.Sel.Name
if fullImport, ok := common[key]; ok {
addImport(fullImport)
}
}
}
}
// fmt.Printf("%ssaw a %T\n", indent, node)
return visitor
})
ast.Walk(visitor, f)
// Nil out any unused ImportSpecs, to be removed in following passes
unusedImport := map[*ast.ImportSpec]bool{}
for pkg, is := range declShort {
if !usedShort[pkg] && pkg != "_" && pkg != "." {
unusedImport[is] = true
}
}
for _, gd := range genDecls {
gd.Specs = filterUnusedSpecs(unusedImport, gd.Specs)
if len(gd.Specs) == 1 {
gd.Lparen = 0
}
}
f.Decls = filterEmptyDecls(f.Decls)
f.Imports = filterUnusedImports(unusedImport, f.Imports)
}
func filterUnusedSpecs(unused map[*ast.ImportSpec]bool, in []ast.Spec) (out []ast.Spec) {
for _, spec := range in {
if is, ok := spec.(*ast.ImportSpec); ok && unused[is] {
continue
}
out = append(out, spec)
}
return
}
func filterUnusedImports(unused map[*ast.ImportSpec]bool, in []*ast.ImportSpec) (out []*ast.ImportSpec) {
for _, spec := range in {
if unused[spec] {
continue
}
out = append(out, spec)
}
return
}
func filterEmptyDecls(in []ast.Decl) (out []ast.Decl) {
for _, decl := range in {
if gd, ok := decl.(*ast.GenDecl); ok && gd.Tok == token.IMPORT && len(gd.Specs) == 0 {
continue
}
out = append(out, decl)
}
return
}
type visitFn func(node ast.Node) ast.Visitor
func (fn visitFn) Visit(node ast.Node) ast.Visitor {
return fn(node)
}