github.com/q45/go@v0.0.0-20151101211701-a4fb8c13db3f/src/go/ast/import.go (about) 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. 14 // It also removes duplicate imports when it is possible to do so without data loss. 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 24 if !d.Lparen.IsValid() { 25 // Not a block: sorted by default. 26 continue 27 } 28 29 // Identify and sort runs of specs on successive lines. 30 i := 0 31 specs := d.Specs[:0] 32 for j, s := range d.Specs { 33 if j > i && fset.Position(s.Pos()).Line > 1+fset.Position(d.Specs[j-1].End()).Line { 34 // j begins a new run. End this one. 35 specs = append(specs, sortSpecs(fset, f, d.Specs[i:j])...) 36 i = j 37 } 38 } 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] 45 lastLine := fset.Position(lastSpec.Pos()).Line 46 if rParenLine := fset.Position(d.Rparen).Line; rParenLine > lastLine+1 { 47 fset.File(d.Rparen).MergeLine(rParenLine - 1) 48 } 49 } 50 } 51 } 52 53 func importPath(s Spec) string { 54 t, err := strconv.Unquote(s.(*ImportSpec).Path.Value) 55 if err == nil { 56 return t 57 } 58 return "" 59 } 60 61 func importName(s Spec) string { 62 n := s.(*ImportSpec).Name 63 if n == nil { 64 return "" 65 } 66 return n.Name 67 } 68 69 func importComment(s Spec) string { 70 c := s.(*ImportSpec).Comment 71 if c == nil { 72 return "" 73 } 74 return c.Text() 75 } 76 77 // collapse indicates whether prev may be removed, leaving only next. 78 func collapse(prev, next Spec) bool { 79 if importPath(next) != importPath(prev) || importName(next) != importName(prev) { 80 return false 81 } 82 return prev.(*ImportSpec).Comment == nil 83 } 84 85 type posSpan struct { 86 Start token.Pos 87 End token.Pos 88 } 89 90 func sortSpecs(fset *token.FileSet, f *File, specs []Spec) []Spec { 91 // Can't short-circuit here even if specs are already sorted, 92 // since they might yet need deduplication. 93 // A lone import, however, may be safely ignored. 94 if len(specs) <= 1 { 95 return specs 96 } 97 98 // Record positions for specs. 99 pos := make([]posSpan, len(specs)) 100 for i, s := range specs { 101 pos[i] = posSpan{s.Pos(), s.End()} 102 } 103 104 // Identify comments in this range. 105 // Any comment from pos[0].Start to the final line counts. 106 lastLine := fset.Position(pos[len(pos)-1].End).Line 107 cstart := len(f.Comments) 108 cend := len(f.Comments) 109 for i, g := range f.Comments { 110 if g.Pos() < pos[0].Start { 111 continue 112 } 113 if i < cstart { 114 cstart = i 115 } 116 if fset.Position(g.End()).Line > lastLine { 117 cend = i 118 break 119 } 120 } 121 comments := f.Comments[cstart:cend] 122 123 // Assign each comment to the import spec preceding it. 124 importComment := map[*ImportSpec][]*CommentGroup{} 125 specIndex := 0 126 for _, g := range comments { 127 for specIndex+1 < len(specs) && pos[specIndex+1].Start <= g.Pos() { 128 specIndex++ 129 } 130 s := specs[specIndex].(*ImportSpec) 131 importComment[s] = append(importComment[s], g) 132 } 133 134 // Sort the import specs by import path. 135 // Remove duplicates, when possible without data loss. 136 // Reassign the import paths to have the same position sequence. 137 // Reassign each comment to abut the end of its spec. 138 // Sort the comments by new position. 139 sort.Sort(byImportSpec(specs)) 140 141 // Dedup. Thanks to our sorting, we can just consider 142 // adjacent pairs of imports. 143 deduped := specs[:0] 144 for i, s := range specs { 145 if i == len(specs)-1 || !collapse(s, specs[i+1]) { 146 deduped = append(deduped, s) 147 } else { 148 p := s.Pos() 149 fset.File(p).MergeLine(fset.Position(p).Line) 150 } 151 } 152 specs = deduped 153 154 // Fix up comment positions 155 for i, s := range specs { 156 s := s.(*ImportSpec) 157 if s.Name != nil { 158 s.Name.NamePos = pos[i].Start 159 } 160 s.Path.ValuePos = pos[i].Start 161 s.EndPos = pos[i].End 162 for _, g := range importComment[s] { 163 for _, c := range g.List { 164 c.Slash = pos[i].End 165 } 166 } 167 } 168 169 sort.Sort(byCommentPos(comments)) 170 171 return specs 172 } 173 174 type byImportSpec []Spec // slice of *ImportSpec 175 176 func (x byImportSpec) Len() int { return len(x) } 177 func (x byImportSpec) Swap(i, j int) { x[i], x[j] = x[j], x[i] } 178 func (x byImportSpec) Less(i, j int) bool { 179 ipath := importPath(x[i]) 180 jpath := importPath(x[j]) 181 if ipath != jpath { 182 return ipath < jpath 183 } 184 iname := importName(x[i]) 185 jname := importName(x[j]) 186 if iname != jname { 187 return iname < jname 188 } 189 return importComment(x[i]) < importComment(x[j]) 190 } 191 192 type byCommentPos []*CommentGroup 193 194 func (x byCommentPos) Len() int { return len(x) } 195 func (x byCommentPos) Swap(i, j int) { x[i], x[j] = x[j], x[i] } 196 func (x byCommentPos) Less(i, j int) bool { return x[i].Pos() < x[j].Pos() }