]>
Commit | Line | Data |
---|---|---|
9c63abc9 ILT |
1 | // Copyright 2011 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 ast | |
6 | ||
7 | import ( | |
8 | "go/token" | |
9 | "sort" | |
10 | "strconv" | |
11 | ) | |
12 | ||
13 | // SortImports sorts runs of consecutive import lines in import blocks in f. | |
f038dae6 | 14 | // It also removes duplicate imports when it is possible to do so without data loss. |
9c63abc9 ILT |
15 | func SortImports(fset *token.FileSet, f *File) { |
16 | for _, d := range f.Decls { | |
17 | d, ok := d.(*GenDecl) | |
18 | if !ok || d.Tok != token.IMPORT { | |
19 | // Not an import declaration, so we're done. | |
20 | // Imports are always first. | |
21 | break | |
22 | } | |
23 | ||
a42a906c | 24 | if !d.Lparen.IsValid() { |
9c63abc9 ILT |
25 | // Not a block: sorted by default. |
26 | continue | |
27 | } | |
28 | ||
29 | // Identify and sort runs of specs on successive lines. | |
30 | i := 0 | |
f038dae6 | 31 | specs := d.Specs[:0] |
9c63abc9 | 32 | for j, s := range d.Specs { |
5a8ea165 | 33 | if j > i && lineAt(fset, s.Pos()) > 1+lineAt(fset, d.Specs[j-1].End()) { |
22b955cc | 34 | // j begins a new run. End this one. |
f038dae6 | 35 | specs = append(specs, sortSpecs(fset, f, d.Specs[i:j])...) |
9c63abc9 ILT |
36 | i = j |
37 | } | |
38 | } | |
f038dae6 ILT |
39 | specs = append(specs, sortSpecs(fset, f, d.Specs[i:])...) |
40 | d.Specs = specs | |
41 | ||
42 | // Deduping can leave a blank line before the rparen; clean that up. | |
43 | if len(d.Specs) > 0 { | |
44 | lastSpec := d.Specs[len(d.Specs)-1] | |
5a8ea165 ILT |
45 | lastLine := lineAt(fset, lastSpec.Pos()) |
46 | rParenLine := lineAt(fset, d.Rparen) | |
f98dd1a3 ILT |
47 | for rParenLine > lastLine+1 { |
48 | rParenLine-- | |
49 | fset.File(d.Rparen).MergeLine(rParenLine) | |
f038dae6 ILT |
50 | } |
51 | } | |
9c63abc9 ILT |
52 | } |
53 | } | |
54 | ||
5a8ea165 ILT |
55 | func lineAt(fset *token.FileSet, pos token.Pos) int { |
56 | return fset.PositionFor(pos, false).Line | |
57 | } | |
58 | ||
9c63abc9 ILT |
59 | func importPath(s Spec) string { |
60 | t, err := strconv.Unquote(s.(*ImportSpec).Path.Value) | |
61 | if err == nil { | |
62 | return t | |
63 | } | |
64 | return "" | |
65 | } | |
66 | ||
f038dae6 ILT |
67 | func importName(s Spec) string { |
68 | n := s.(*ImportSpec).Name | |
69 | if n == nil { | |
70 | return "" | |
71 | } | |
72 | return n.Name | |
73 | } | |
74 | ||
75 | func importComment(s Spec) string { | |
76 | c := s.(*ImportSpec).Comment | |
77 | if c == nil { | |
78 | return "" | |
79 | } | |
80 | return c.Text() | |
81 | } | |
82 | ||
83 | // collapse indicates whether prev may be removed, leaving only next. | |
84 | func collapse(prev, next Spec) bool { | |
85 | if importPath(next) != importPath(prev) || importName(next) != importName(prev) { | |
86 | return false | |
87 | } | |
88 | return prev.(*ImportSpec).Comment == nil | |
89 | } | |
90 | ||
9c63abc9 ILT |
91 | type posSpan struct { |
92 | Start token.Pos | |
93 | End token.Pos | |
94 | } | |
95 | ||
5a8ea165 ILT |
96 | type cgPos struct { |
97 | left bool // true if comment is to the left of the spec, false otherwise. | |
98 | cg *CommentGroup | |
99 | } | |
100 | ||
f038dae6 ILT |
101 | func sortSpecs(fset *token.FileSet, f *File, specs []Spec) []Spec { |
102 | // Can't short-circuit here even if specs are already sorted, | |
103 | // since they might yet need deduplication. | |
104 | // A lone import, however, may be safely ignored. | |
105 | if len(specs) <= 1 { | |
106 | return specs | |
9c63abc9 ILT |
107 | } |
108 | ||
109 | // Record positions for specs. | |
110 | pos := make([]posSpan, len(specs)) | |
111 | for i, s := range specs { | |
9af4cb95 | 112 | pos[i] = posSpan{s.Pos(), s.End()} |
9c63abc9 ILT |
113 | } |
114 | ||
115 | // Identify comments in this range. | |
5a8ea165 ILT |
116 | begSpecs := pos[0].Start |
117 | endSpecs := pos[len(pos)-1].End | |
118 | beg := fset.File(begSpecs).LineStart(lineAt(fset, begSpecs)) | |
119 | endLine := lineAt(fset, endSpecs) | |
120 | endFile := fset.File(endSpecs) | |
121 | var end token.Pos | |
122 | if endLine == endFile.LineCount() { | |
123 | end = endSpecs | |
124 | } else { | |
125 | end = endFile.LineStart(endLine + 1) // beginning of next line | |
126 | } | |
127 | first := len(f.Comments) | |
128 | last := -1 | |
9c63abc9 | 129 | for i, g := range f.Comments { |
5a8ea165 | 130 | if g.End() >= end { |
656297e1 | 131 | break |
aa8901e9 | 132 | } |
5a8ea165 ILT |
133 | // g.End() < end |
134 | if beg <= g.Pos() { | |
135 | // comment is within the range [beg, end[ of import declarations | |
136 | if i < first { | |
137 | first = i | |
138 | } | |
139 | if i > last { | |
140 | last = i | |
141 | } | |
142 | } | |
9c63abc9 | 143 | } |
9c63abc9 | 144 | |
5a8ea165 ILT |
145 | var comments []*CommentGroup |
146 | if last >= 0 { | |
147 | comments = f.Comments[first : last+1] | |
148 | } | |
149 | ||
150 | // Assign each comment to the import spec on the same line. | |
151 | importComments := map[*ImportSpec][]cgPos{} | |
9c63abc9 ILT |
152 | specIndex := 0 |
153 | for _, g := range comments { | |
154 | for specIndex+1 < len(specs) && pos[specIndex+1].Start <= g.Pos() { | |
155 | specIndex++ | |
156 | } | |
5a8ea165 ILT |
157 | var left bool |
158 | // A block comment can appear before the first import spec. | |
159 | if specIndex == 0 && pos[specIndex].Start > g.Pos() { | |
160 | left = true | |
161 | } else if specIndex+1 < len(specs) && // Or it can appear on the left of an import spec. | |
162 | lineAt(fset, pos[specIndex].Start)+1 == lineAt(fset, g.Pos()) { | |
163 | specIndex++ | |
164 | left = true | |
165 | } | |
9c63abc9 | 166 | s := specs[specIndex].(*ImportSpec) |
5a8ea165 | 167 | importComments[s] = append(importComments[s], cgPos{left: left, cg: g}) |
9c63abc9 ILT |
168 | } |
169 | ||
170 | // Sort the import specs by import path. | |
f038dae6 | 171 | // Remove duplicates, when possible without data loss. |
9c63abc9 | 172 | // Reassign the import paths to have the same position sequence. |
5a8ea165 | 173 | // Reassign each comment to the spec on the same line. |
9c63abc9 | 174 | // Sort the comments by new position. |
1a2f01ef ILT |
175 | sort.Slice(specs, func(i, j int) bool { |
176 | ipath := importPath(specs[i]) | |
177 | jpath := importPath(specs[j]) | |
178 | if ipath != jpath { | |
179 | return ipath < jpath | |
180 | } | |
181 | iname := importName(specs[i]) | |
182 | jname := importName(specs[j]) | |
183 | if iname != jname { | |
184 | return iname < jname | |
185 | } | |
186 | return importComment(specs[i]) < importComment(specs[j]) | |
187 | }) | |
f038dae6 ILT |
188 | |
189 | // Dedup. Thanks to our sorting, we can just consider | |
190 | // adjacent pairs of imports. | |
191 | deduped := specs[:0] | |
192 | for i, s := range specs { | |
193 | if i == len(specs)-1 || !collapse(s, specs[i+1]) { | |
194 | deduped = append(deduped, s) | |
195 | } else { | |
196 | p := s.Pos() | |
5a8ea165 | 197 | fset.File(p).MergeLine(lineAt(fset, p)) |
f038dae6 ILT |
198 | } |
199 | } | |
200 | specs = deduped | |
201 | ||
202 | // Fix up comment positions | |
9c63abc9 ILT |
203 | for i, s := range specs { |
204 | s := s.(*ImportSpec) | |
205 | if s.Name != nil { | |
206 | s.Name.NamePos = pos[i].Start | |
207 | } | |
208 | s.Path.ValuePos = pos[i].Start | |
209 | s.EndPos = pos[i].End | |
1a2f01ef | 210 | for _, g := range importComments[s] { |
5a8ea165 ILT |
211 | for _, c := range g.cg.List { |
212 | if g.left { | |
213 | c.Slash = pos[i].Start - 1 | |
214 | } else { | |
215 | // An import spec can have both block comment and a line comment | |
216 | // to its right. In that case, both of them will have the same pos. | |
217 | // But while formatting the AST, the line comment gets moved to | |
218 | // after the block comment. | |
219 | c.Slash = pos[i].End | |
220 | } | |
9c63abc9 ILT |
221 | } |
222 | } | |
223 | } | |
f038dae6 | 224 | |
1a2f01ef ILT |
225 | sort.Slice(comments, func(i, j int) bool { |
226 | return comments[i].Pos() < comments[j].Pos() | |
227 | }) | |
f038dae6 ILT |
228 | |
229 | return specs | |
9c63abc9 | 230 | } |