1
2
3
4
5
6
7
8 package imports
9
10 import (
11 "go/ast"
12 "go/token"
13 "log"
14 "sort"
15 "strconv"
16 )
17
18
19
20
21
22 func sortImports(localPrefix string, tokFile *token.File, f *ast.File) {
23 for i, d := range f.Decls {
24 d, ok := d.(*ast.GenDecl)
25 if !ok || d.Tok != token.IMPORT {
26
27
28 break
29 }
30
31 if len(d.Specs) == 0 {
32
33 f.Decls = append(f.Decls[:i], f.Decls[i+1:]...)
34 }
35
36 if !d.Lparen.IsValid() {
37
38 continue
39 }
40
41
42 i := 0
43 specs := d.Specs[:0]
44 for j, s := range d.Specs {
45 if j > i && tokFile.Line(s.Pos()) > 1+tokFile.Line(d.Specs[j-1].End()) {
46
47 specs = append(specs, sortSpecs(localPrefix, tokFile, f, d.Specs[i:j])...)
48 i = j
49 }
50 }
51 specs = append(specs, sortSpecs(localPrefix, tokFile, f, d.Specs[i:])...)
52 d.Specs = specs
53
54
55
56 if len(d.Specs) > 0 {
57 lastSpec := d.Specs[len(d.Specs)-1]
58 lastLine := tokFile.PositionFor(lastSpec.Pos(), false).Line
59 if rParenLine := tokFile.PositionFor(d.Rparen, false).Line; rParenLine > lastLine+1 {
60 tokFile.MergeLine(rParenLine - 1)
61 }
62 }
63 }
64 }
65
66
67
68
69 func mergeImports(f *ast.File) {
70 if len(f.Decls) <= 1 {
71 return
72 }
73
74
75 var first *ast.GenDecl
76 for i := 0; i < len(f.Decls); i++ {
77 decl := f.Decls[i]
78 gen, ok := decl.(*ast.GenDecl)
79 if !ok || gen.Tok != token.IMPORT || declImports(gen, "C") {
80 continue
81 }
82 if first == nil {
83 first = gen
84 continue
85 }
86
87
88 first.Lparen = first.Pos()
89
90 for _, spec := range gen.Specs {
91 spec.(*ast.ImportSpec).Path.ValuePos = first.Pos()
92 first.Specs = append(first.Specs, spec)
93 }
94 f.Decls = append(f.Decls[:i], f.Decls[i+1:]...)
95 i--
96 }
97 }
98
99
100
101 func declImports(gen *ast.GenDecl, path string) bool {
102 if gen.Tok != token.IMPORT {
103 return false
104 }
105 for _, spec := range gen.Specs {
106 impspec := spec.(*ast.ImportSpec)
107 if importPath(impspec) == path {
108 return true
109 }
110 }
111 return false
112 }
113
114 func importPath(s ast.Spec) string {
115 t, err := strconv.Unquote(s.(*ast.ImportSpec).Path.Value)
116 if err == nil {
117 return t
118 }
119 return ""
120 }
121
122 func importName(s ast.Spec) string {
123 n := s.(*ast.ImportSpec).Name
124 if n == nil {
125 return ""
126 }
127 return n.Name
128 }
129
130 func importComment(s ast.Spec) string {
131 c := s.(*ast.ImportSpec).Comment
132 if c == nil {
133 return ""
134 }
135 return c.Text()
136 }
137
138
139 func collapse(prev, next ast.Spec) bool {
140 if importPath(next) != importPath(prev) || importName(next) != importName(prev) {
141 return false
142 }
143 return prev.(*ast.ImportSpec).Comment == nil
144 }
145
146 type posSpan struct {
147 Start token.Pos
148 End token.Pos
149 }
150
151
152
153 func sortSpecs(localPrefix string, tokFile *token.File, f *ast.File, specs []ast.Spec) []ast.Spec {
154
155
156
157 if len(specs) <= 1 {
158 return specs
159 }
160
161
162 pos := make([]posSpan, len(specs))
163 for i, s := range specs {
164 pos[i] = posSpan{s.Pos(), s.End()}
165 }
166
167
168
169 lastLine := tokFile.Line(pos[len(pos)-1].End)
170 cstart := len(f.Comments)
171 cend := len(f.Comments)
172 for i, g := range f.Comments {
173 if g.Pos() < pos[0].Start {
174 continue
175 }
176 if i < cstart {
177 cstart = i
178 }
179 if tokFile.Line(g.End()) > lastLine {
180 cend = i
181 break
182 }
183 }
184 comments := f.Comments[cstart:cend]
185
186
187 importComment := map[*ast.ImportSpec][]*ast.CommentGroup{}
188 specIndex := 0
189 for _, g := range comments {
190 for specIndex+1 < len(specs) && pos[specIndex+1].Start <= g.Pos() {
191 specIndex++
192 }
193 s := specs[specIndex].(*ast.ImportSpec)
194 importComment[s] = append(importComment[s], g)
195 }
196
197
198
199
200
201
202 sort.Sort(byImportSpec{localPrefix, specs})
203
204
205
206 deduped := specs[:0]
207 for i, s := range specs {
208 if i == len(specs)-1 || !collapse(s, specs[i+1]) {
209 deduped = append(deduped, s)
210 } else {
211 p := s.Pos()
212 tokFile.MergeLine(tokFile.Line(p))
213 }
214 }
215 specs = deduped
216
217
218 for i, s := range specs {
219 s := s.(*ast.ImportSpec)
220 if s.Name != nil {
221 s.Name.NamePos = pos[i].Start
222 }
223 s.Path.ValuePos = pos[i].Start
224 s.EndPos = pos[i].End
225 nextSpecPos := pos[i].End
226
227 for _, g := range importComment[s] {
228 for _, c := range g.List {
229 c.Slash = pos[i].End
230 nextSpecPos = c.End()
231 }
232 }
233 if i < len(specs)-1 {
234 pos[i+1].Start = nextSpecPos
235 pos[i+1].End = nextSpecPos
236 }
237 }
238
239 sort.Sort(byCommentPos(comments))
240
241
242
243 firstSpecLine := tokFile.Line(specs[0].Pos())
244 for _, s := range specs[1:] {
245 p := s.Pos()
246 line := tokFile.Line(p)
247 for previousLine := line - 1; previousLine >= firstSpecLine; {
248
249
250 if previousLine > 0 && previousLine < tokFile.LineCount() {
251 tokFile.MergeLine(previousLine)
252 previousLine--
253 } else {
254
255 req := "Please report what the imports section of your go file looked like."
256 log.Printf("panic avoided: first:%d line:%d previous:%d max:%d. %s",
257 firstSpecLine, line, previousLine, tokFile.LineCount(), req)
258 }
259 }
260 }
261 return specs
262 }
263
264 type byImportSpec struct {
265 localPrefix string
266 specs []ast.Spec
267 }
268
269 func (x byImportSpec) Len() int { return len(x.specs) }
270 func (x byImportSpec) Swap(i, j int) { x.specs[i], x.specs[j] = x.specs[j], x.specs[i] }
271 func (x byImportSpec) Less(i, j int) bool {
272 ipath := importPath(x.specs[i])
273 jpath := importPath(x.specs[j])
274
275 igroup := importGroup(x.localPrefix, ipath)
276 jgroup := importGroup(x.localPrefix, jpath)
277 if igroup != jgroup {
278 return igroup < jgroup
279 }
280
281 if ipath != jpath {
282 return ipath < jpath
283 }
284 iname := importName(x.specs[i])
285 jname := importName(x.specs[j])
286
287 if iname != jname {
288 return iname < jname
289 }
290 return importComment(x.specs[i]) < importComment(x.specs[j])
291 }
292
293 type byCommentPos []*ast.CommentGroup
294
295 func (x byCommentPos) Len() int { return len(x) }
296 func (x byCommentPos) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
297 func (x byCommentPos) Less(i, j int) bool { return x[i].Pos() < x[j].Pos() }
298
View as plain text