Source file
src/go/ast/import.go
1
2
3
4
5 package ast
6
7 import (
8 "cmp"
9 "go/token"
10 "slices"
11 "strconv"
12 )
13
14
15
16 func SortImports(fset *token.FileSet, f *File) {
17 for _, d := range f.Decls {
18 d, ok := d.(*GenDecl)
19 if !ok || d.Tok != token.IMPORT {
20
21
22 break
23 }
24
25 if !d.Lparen.IsValid() {
26
27 continue
28 }
29
30
31 i := 0
32 specs := d.Specs[:0]
33 for j, s := range d.Specs {
34 if j > i && lineAt(fset, s.Pos()) > 1+lineAt(fset, d.Specs[j-1].End()) {
35
36 specs = append(specs, sortSpecs(fset, f, d.Specs[i:j])...)
37 i = j
38 }
39 }
40 specs = append(specs, sortSpecs(fset, f, d.Specs[i:])...)
41 d.Specs = specs
42
43
44 if len(d.Specs) > 0 {
45 lastSpec := d.Specs[len(d.Specs)-1]
46 lastLine := lineAt(fset, lastSpec.Pos())
47 rParenLine := lineAt(fset, d.Rparen)
48 for rParenLine > lastLine+1 {
49 rParenLine--
50 fset.File(d.Rparen).MergeLine(rParenLine)
51 }
52 }
53 }
54
55
56 f.Imports = f.Imports[:0]
57 for _, decl := range f.Decls {
58 if decl, ok := decl.(*GenDecl); ok && decl.Tok == token.IMPORT {
59 for _, spec := range decl.Specs {
60 f.Imports = append(f.Imports, spec.(*ImportSpec))
61 }
62 }
63 }
64 }
65
66 func lineAt(fset *token.FileSet, pos token.Pos) int {
67 return fset.PositionFor(pos, false).Line
68 }
69
70 func importPath(s Spec) string {
71 t, err := strconv.Unquote(s.(*ImportSpec).Path.Value)
72 if err == nil {
73 return t
74 }
75 return ""
76 }
77
78 func importName(s Spec) string {
79 n := s.(*ImportSpec).Name
80 if n == nil {
81 return ""
82 }
83 return n.Name
84 }
85
86 func importComment(s Spec) string {
87 c := s.(*ImportSpec).Comment
88 if c == nil {
89 return ""
90 }
91 return c.Text()
92 }
93
94
95 func collapse(prev, next Spec) bool {
96 if importPath(next) != importPath(prev) || importName(next) != importName(prev) {
97 return false
98 }
99 return prev.(*ImportSpec).Comment == nil
100 }
101
102 type posSpan struct {
103 Start token.Pos
104 End token.Pos
105 }
106
107 type cgPos struct {
108 left bool
109 cg *CommentGroup
110 }
111
112 func sortSpecs(fset *token.FileSet, f *File, specs []Spec) []Spec {
113
114
115
116 if len(specs) <= 1 {
117 return specs
118 }
119
120
121 pos := make([]posSpan, len(specs))
122 for i, s := range specs {
123 pos[i] = posSpan{s.Pos(), s.End()}
124 }
125
126
127 begSpecs := pos[0].Start
128 endSpecs := pos[len(pos)-1].End
129 beg := fset.File(begSpecs).LineStart(lineAt(fset, begSpecs))
130 endLine := lineAt(fset, endSpecs)
131 endFile := fset.File(endSpecs)
132 var end token.Pos
133 if endLine == endFile.LineCount() {
134 end = endSpecs
135 } else {
136 end = endFile.LineStart(endLine + 1)
137 }
138 first := len(f.Comments)
139 last := -1
140 for i, g := range f.Comments {
141 if g.End() >= end {
142 break
143 }
144
145 if beg <= g.Pos() {
146
147 if i < first {
148 first = i
149 }
150 if i > last {
151 last = i
152 }
153 }
154 }
155
156 var comments []*CommentGroup
157 if last >= 0 {
158 comments = f.Comments[first : last+1]
159 }
160
161
162 importComments := map[*ImportSpec][]cgPos{}
163 specIndex := 0
164 for _, g := range comments {
165 for specIndex+1 < len(specs) && pos[specIndex+1].Start <= g.Pos() {
166 specIndex++
167 }
168 var left bool
169
170 if specIndex == 0 && pos[specIndex].Start > g.Pos() {
171 left = true
172 } else if specIndex+1 < len(specs) &&
173 lineAt(fset, pos[specIndex].Start)+1 == lineAt(fset, g.Pos()) {
174 specIndex++
175 left = true
176 }
177 s := specs[specIndex].(*ImportSpec)
178 importComments[s] = append(importComments[s], cgPos{left: left, cg: g})
179 }
180
181
182
183
184
185
186 slices.SortFunc(specs, func(a, b Spec) int {
187 ipath := importPath(a)
188 jpath := importPath(b)
189 r := cmp.Compare(ipath, jpath)
190 if r != 0 {
191 return r
192 }
193 iname := importName(a)
194 jname := importName(b)
195 r = cmp.Compare(iname, jname)
196 if r != 0 {
197 return r
198 }
199 return cmp.Compare(importComment(a), importComment(b))
200 })
201
202
203
204 deduped := specs[:0]
205 for i, s := range specs {
206 if i == len(specs)-1 || !collapse(s, specs[i+1]) {
207 deduped = append(deduped, s)
208 } else {
209 p := s.Pos()
210 fset.File(p).MergeLine(lineAt(fset, p))
211 }
212 }
213 specs = deduped
214
215
216 for i, s := range specs {
217 s := s.(*ImportSpec)
218 if s.Name != nil {
219 s.Name.NamePos = pos[i].Start
220 }
221 s.Path.ValuePos = pos[i].Start
222 s.EndPos = pos[i].End
223 for _, g := range importComments[s] {
224 for _, c := range g.cg.List {
225 if g.left {
226 c.Slash = pos[i].Start - 1
227 } else {
228
229
230
231
232 c.Slash = pos[i].End
233 }
234 }
235 }
236 }
237
238 slices.SortFunc(comments, func(a, b *CommentGroup) int {
239 return cmp.Compare(a.Pos(), b.Pos())
240 })
241
242 return specs
243 }
244
View as plain text