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