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 func lineAt(fset *token.FileSet, pos token.Pos) int {
57 return fset.PositionFor(pos, false).Line
58 }
59
60 func importPath(s Spec) string {
61 t, err := strconv.Unquote(s.(*ImportSpec).Path.Value)
62 if err == nil {
63 return t
64 }
65 return ""
66 }
67
68 func importName(s Spec) string {
69 n := s.(*ImportSpec).Name
70 if n == nil {
71 return ""
72 }
73 return n.Name
74 }
75
76 func importComment(s Spec) string {
77 c := s.(*ImportSpec).Comment
78 if c == nil {
79 return ""
80 }
81 return c.Text()
82 }
83
84
85 func collapse(prev, next Spec) bool {
86 if importPath(next) != importPath(prev) || importName(next) != importName(prev) {
87 return false
88 }
89 return prev.(*ImportSpec).Comment == nil
90 }
91
92 type posSpan struct {
93 Start token.Pos
94 End token.Pos
95 }
96
97 type cgPos struct {
98 left bool
99 cg *CommentGroup
100 }
101
102 func sortSpecs(fset *token.FileSet, f *File, specs []Spec) []Spec {
103
104
105
106 if len(specs) <= 1 {
107 return specs
108 }
109
110
111 pos := make([]posSpan, len(specs))
112 for i, s := range specs {
113 pos[i] = posSpan{s.Pos(), s.End()}
114 }
115
116
117 begSpecs := pos[0].Start
118 endSpecs := pos[len(pos)-1].End
119 beg := fset.File(begSpecs).LineStart(lineAt(fset, begSpecs))
120 endLine := lineAt(fset, endSpecs)
121 endFile := fset.File(endSpecs)
122 var end token.Pos
123 if endLine == endFile.LineCount() {
124 end = endSpecs
125 } else {
126 end = endFile.LineStart(endLine + 1)
127 }
128 first := len(f.Comments)
129 last := -1
130 for i, g := range f.Comments {
131 if g.End() >= end {
132 break
133 }
134
135 if beg <= g.Pos() {
136
137 if i < first {
138 first = i
139 }
140 if i > last {
141 last = i
142 }
143 }
144 }
145
146 var comments []*CommentGroup
147 if last >= 0 {
148 comments = f.Comments[first : last+1]
149 }
150
151
152 importComments := map[*ImportSpec][]cgPos{}
153 specIndex := 0
154 for _, g := range comments {
155 for specIndex+1 < len(specs) && pos[specIndex+1].Start <= g.Pos() {
156 specIndex++
157 }
158 var left bool
159
160 if specIndex == 0 && pos[specIndex].Start > g.Pos() {
161 left = true
162 } else if specIndex+1 < len(specs) &&
163 lineAt(fset, pos[specIndex].Start)+1 == lineAt(fset, g.Pos()) {
164 specIndex++
165 left = true
166 }
167 s := specs[specIndex].(*ImportSpec)
168 importComments[s] = append(importComments[s], cgPos{left: left, cg: g})
169 }
170
171
172
173
174
175
176 slices.SortFunc(specs, func(a, b Spec) int {
177 ipath := importPath(a)
178 jpath := importPath(b)
179 r := cmp.Compare(ipath, jpath)
180 if r != 0 {
181 return r
182 }
183 iname := importName(a)
184 jname := importName(b)
185 r = cmp.Compare(iname, jname)
186 if r != 0 {
187 return r
188 }
189 return cmp.Compare(importComment(a), importComment(b))
190 })
191
192
193
194 deduped := specs[:0]
195 for i, s := range specs {
196 if i == len(specs)-1 || !collapse(s, specs[i+1]) {
197 deduped = append(deduped, s)
198 } else {
199 p := s.Pos()
200 fset.File(p).MergeLine(lineAt(fset, p))
201 }
202 }
203 specs = deduped
204
205
206 for i, s := range specs {
207 s := s.(*ImportSpec)
208 if s.Name != nil {
209 s.Name.NamePos = pos[i].Start
210 }
211 s.Path.ValuePos = pos[i].Start
212 s.EndPos = pos[i].End
213 for _, g := range importComments[s] {
214 for _, c := range g.cg.List {
215 if g.left {
216 c.Slash = pos[i].Start - 1
217 } else {
218
219
220
221
222 c.Slash = pos[i].End
223 }
224 }
225 }
226 }
227
228 slices.SortFunc(comments, func(a, b *CommentGroup) int {
229 return cmp.Compare(a.Pos(), b.Pos())
230 })
231
232 return specs
233 }
234
View as plain text