cuelang.org/go@v0.10.1/internal/golangorgx/tools/imports/sortimports.go (about) 1 // Copyright 2013 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 // Hacked up copy of go/ast/import.go 6 // Modified to use a single token.File in preference to a FileSet. 7 8 package imports 9 10 import ( 11 "go/ast" 12 "go/token" 13 "log" 14 "sort" 15 "strconv" 16 ) 17 18 // sortImports sorts runs of consecutive import lines in import blocks in f. 19 // It also removes duplicate imports when it is possible to do so without data loss. 20 // 21 // It may mutate the token.File. 22 func sortImports(localPrefix string, tokFile *token.File, f *ast.File) { 23 for i, d := range f.Decls { 24 d, ok := d.(*ast.GenDecl) 25 if !ok || d.Tok != token.IMPORT { 26 // Not an import declaration, so we're done. 27 // Imports are always first. 28 break 29 } 30 31 if len(d.Specs) == 0 { 32 // Empty import block, remove it. 33 f.Decls = append(f.Decls[:i], f.Decls[i+1:]...) 34 } 35 36 if !d.Lparen.IsValid() { 37 // Not a block: sorted by default. 38 continue 39 } 40 41 // Identify and sort runs of specs on successive lines. 42 i := 0 43 specs := d.Specs[:0] 44 for j, s := range d.Specs { 45 if j > i && tokFile.Line(s.Pos()) > 1+tokFile.Line(d.Specs[j-1].End()) { 46 // j begins a new run. End this one. 47 specs = append(specs, sortSpecs(localPrefix, tokFile, f, d.Specs[i:j])...) 48 i = j 49 } 50 } 51 specs = append(specs, sortSpecs(localPrefix, tokFile, f, d.Specs[i:])...) 52 d.Specs = specs 53 54 // Deduping can leave a blank line before the rparen; clean that up. 55 // Ignore line directives. 56 if len(d.Specs) > 0 { 57 lastSpec := d.Specs[len(d.Specs)-1] 58 lastLine := tokFile.PositionFor(lastSpec.Pos(), false).Line 59 if rParenLine := tokFile.PositionFor(d.Rparen, false).Line; rParenLine > lastLine+1 { 60 tokFile.MergeLine(rParenLine - 1) // has side effects! 61 } 62 } 63 } 64 } 65 66 // mergeImports merges all the import declarations into the first one. 67 // Taken from golang.org/x/tools/ast/astutil. 68 // This does not adjust line numbers properly 69 func mergeImports(f *ast.File) { 70 if len(f.Decls) <= 1 { 71 return 72 } 73 74 // Merge all the import declarations into the first one. 75 var first *ast.GenDecl 76 for i := 0; i < len(f.Decls); i++ { 77 decl := f.Decls[i] 78 gen, ok := decl.(*ast.GenDecl) 79 if !ok || gen.Tok != token.IMPORT || declImports(gen, "C") { 80 continue 81 } 82 if first == nil { 83 first = gen 84 continue // Don't touch the first one. 85 } 86 // We now know there is more than one package in this import 87 // declaration. Ensure that it ends up parenthesized. 88 first.Lparen = first.Pos() 89 // Move the imports of the other import declaration to the first one. 90 for _, spec := range gen.Specs { 91 spec.(*ast.ImportSpec).Path.ValuePos = first.Pos() 92 first.Specs = append(first.Specs, spec) 93 } 94 f.Decls = append(f.Decls[:i], f.Decls[i+1:]...) 95 i-- 96 } 97 } 98 99 // declImports reports whether gen contains an import of path. 100 // Taken from golang.org/x/tools/ast/astutil. 101 func declImports(gen *ast.GenDecl, path string) bool { 102 if gen.Tok != token.IMPORT { 103 return false 104 } 105 for _, spec := range gen.Specs { 106 impspec := spec.(*ast.ImportSpec) 107 if importPath(impspec) == path { 108 return true 109 } 110 } 111 return false 112 } 113 114 func importPath(s ast.Spec) string { 115 t, err := strconv.Unquote(s.(*ast.ImportSpec).Path.Value) 116 if err == nil { 117 return t 118 } 119 return "" 120 } 121 122 func importName(s ast.Spec) string { 123 n := s.(*ast.ImportSpec).Name 124 if n == nil { 125 return "" 126 } 127 return n.Name 128 } 129 130 func importComment(s ast.Spec) string { 131 c := s.(*ast.ImportSpec).Comment 132 if c == nil { 133 return "" 134 } 135 return c.Text() 136 } 137 138 // collapse indicates whether prev may be removed, leaving only next. 139 func collapse(prev, next ast.Spec) bool { 140 if importPath(next) != importPath(prev) || importName(next) != importName(prev) { 141 return false 142 } 143 return prev.(*ast.ImportSpec).Comment == nil 144 } 145 146 type posSpan struct { 147 Start token.Pos 148 End token.Pos 149 } 150 151 // sortSpecs sorts the import specs within each import decl. 152 // It may mutate the token.File. 153 func sortSpecs(localPrefix string, tokFile *token.File, f *ast.File, specs []ast.Spec) []ast.Spec { 154 // Can't short-circuit here even if specs are already sorted, 155 // since they might yet need deduplication. 156 // A lone import, however, may be safely ignored. 157 if len(specs) <= 1 { 158 return specs 159 } 160 161 // Record positions for specs. 162 pos := make([]posSpan, len(specs)) 163 for i, s := range specs { 164 pos[i] = posSpan{s.Pos(), s.End()} 165 } 166 167 // Identify comments in this range. 168 // Any comment from pos[0].Start to the final line counts. 169 lastLine := tokFile.Line(pos[len(pos)-1].End) 170 cstart := len(f.Comments) 171 cend := len(f.Comments) 172 for i, g := range f.Comments { 173 if g.Pos() < pos[0].Start { 174 continue 175 } 176 if i < cstart { 177 cstart = i 178 } 179 if tokFile.Line(g.End()) > lastLine { 180 cend = i 181 break 182 } 183 } 184 comments := f.Comments[cstart:cend] 185 186 // Assign each comment to the import spec preceding it. 187 importComment := map[*ast.ImportSpec][]*ast.CommentGroup{} 188 specIndex := 0 189 for _, g := range comments { 190 for specIndex+1 < len(specs) && pos[specIndex+1].Start <= g.Pos() { 191 specIndex++ 192 } 193 s := specs[specIndex].(*ast.ImportSpec) 194 importComment[s] = append(importComment[s], g) 195 } 196 197 // Sort the import specs by import path. 198 // Remove duplicates, when possible without data loss. 199 // Reassign the import paths to have the same position sequence. 200 // Reassign each comment to abut the end of its spec. 201 // Sort the comments by new position. 202 sort.Sort(byImportSpec{localPrefix, specs}) 203 204 // Dedup. Thanks to our sorting, we can just consider 205 // adjacent pairs of imports. 206 deduped := specs[:0] 207 for i, s := range specs { 208 if i == len(specs)-1 || !collapse(s, specs[i+1]) { 209 deduped = append(deduped, s) 210 } else { 211 p := s.Pos() 212 tokFile.MergeLine(tokFile.Line(p)) // has side effects! 213 } 214 } 215 specs = deduped 216 217 // Fix up comment positions 218 for i, s := range specs { 219 s := s.(*ast.ImportSpec) 220 if s.Name != nil { 221 s.Name.NamePos = pos[i].Start 222 } 223 s.Path.ValuePos = pos[i].Start 224 s.EndPos = pos[i].End 225 nextSpecPos := pos[i].End 226 227 for _, g := range importComment[s] { 228 for _, c := range g.List { 229 c.Slash = pos[i].End 230 nextSpecPos = c.End() 231 } 232 } 233 if i < len(specs)-1 { 234 pos[i+1].Start = nextSpecPos 235 pos[i+1].End = nextSpecPos 236 } 237 } 238 239 sort.Sort(byCommentPos(comments)) 240 241 // Fixup comments can insert blank lines, because import specs are on different lines. 242 // We remove those blank lines here by merging import spec to the first import spec line. 243 firstSpecLine := tokFile.Line(specs[0].Pos()) 244 for _, s := range specs[1:] { 245 p := s.Pos() 246 line := tokFile.Line(p) 247 for previousLine := line - 1; previousLine >= firstSpecLine; { 248 // MergeLine can panic. Avoid the panic at the cost of not removing the blank line 249 // golang/go#50329 250 if previousLine > 0 && previousLine < tokFile.LineCount() { 251 tokFile.MergeLine(previousLine) // has side effects! 252 previousLine-- 253 } else { 254 // try to gather some data to diagnose how this could happen 255 req := "Please report what the imports section of your go file looked like." 256 log.Printf("panic avoided: first:%d line:%d previous:%d max:%d. %s", 257 firstSpecLine, line, previousLine, tokFile.LineCount(), req) 258 } 259 } 260 } 261 return specs 262 } 263 264 type byImportSpec struct { 265 localPrefix string 266 specs []ast.Spec // slice of *ast.ImportSpec 267 } 268 269 func (x byImportSpec) Len() int { return len(x.specs) } 270 func (x byImportSpec) Swap(i, j int) { x.specs[i], x.specs[j] = x.specs[j], x.specs[i] } 271 func (x byImportSpec) Less(i, j int) bool { 272 ipath := importPath(x.specs[i]) 273 jpath := importPath(x.specs[j]) 274 275 igroup := importGroup(x.localPrefix, ipath) 276 jgroup := importGroup(x.localPrefix, jpath) 277 if igroup != jgroup { 278 return igroup < jgroup 279 } 280 281 if ipath != jpath { 282 return ipath < jpath 283 } 284 iname := importName(x.specs[i]) 285 jname := importName(x.specs[j]) 286 287 if iname != jname { 288 return iname < jname 289 } 290 return importComment(x.specs[i]) < importComment(x.specs[j]) 291 } 292 293 type byCommentPos []*ast.CommentGroup 294 295 func (x byCommentPos) Len() int { return len(x) } 296 func (x byCommentPos) Swap(i, j int) { x[i], x[j] = x[j], x[i] } 297 func (x byCommentPos) Less(i, j int) bool { return x[i].Pos() < x[j].Pos() }