GoPLS Viewer

Home|gopls/internal/imports/sortimports.go
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
8package imports
9
10import (
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.
22func sortImports(localPrefix stringtokFile *token.Filef *ast.File) {
23    for id := range f.Decls {
24        dok := 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 js := 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(specssortSpecs(localPrefixtokFilefd.Specs[i:j])...)
48                i = j
49            }
50        }
51        specs = append(specssortSpecs(localPrefixtokFilefd.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.Rparenfalse).LinerParenLine > 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
69func 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 := 0i < len(f.Decls); i++ {
77        decl := f.Decls[i]
78        genok := 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.Specsspec)
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.
101func declImports(gen *ast.GenDeclpath stringbool {
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
114func importPath(s ast.Specstring {
115    terr := strconv.Unquote(s.(*ast.ImportSpec).Path.Value)
116    if err == nil {
117        return t
118    }
119    return ""
120}
121
122func importName(s ast.Specstring {
123    n := s.(*ast.ImportSpec).Name
124    if n == nil {
125        return ""
126    }
127    return n.Name
128}
129
130func importComment(s ast.Specstring {
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.
139func collapse(prevnext ast.Specbool {
140    if importPath(next) != importPath(prev) || importName(next) != importName(prev) {
141        return false
142    }
143    return prev.(*ast.ImportSpec).Comment == nil
144}
145
146type 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.
153func sortSpecs(localPrefix stringtokFile *token.Filef *ast.Filespecs []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([]posSpanlen(specs))
163    for is := 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 ig := 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{localPrefixspecs})
203
204    // Dedup. Thanks to our sorting, we can just consider
205    // adjacent pairs of imports.
206    deduped := specs[:0]
207    for is := range specs {
208        if i == len(specs)-1 || !collapse(sspecs[i+1]) {
209            deduped = append(dedupeds)
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 is := 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 - 1previousLine >= 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                    firstSpecLinelinepreviousLinetokFile.LineCount(), req)
258            }
259        }
260    }
261    return specs
262}
263
264type byImportSpec struct {
265    localPrefix string
266    specs       []ast.Spec // slice of *ast.ImportSpec
267}
268
269func (x byImportSpecLen() int      { return len(x.specs) }
270func (x byImportSpecSwap(ij int) { x.specs[i], x.specs[j] = x.specs[j], x.specs[i] }
271func (x byImportSpecLess(ij intbool {
272    ipath := importPath(x.specs[i])
273    jpath := importPath(x.specs[j])
274
275    igroup := importGroup(x.localPrefixipath)
276    jgroup := importGroup(x.localPrefixjpath)
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
293type byCommentPos []*ast.CommentGroup
294
295func (x byCommentPosLen() int           { return len(x) }
296func (x byCommentPosSwap(ij int)      { x[i], x[j] = x[j], x[i] }
297func (x byCommentPosLess(ij intbool { return x[i].Pos() < x[j].Pos() }
298
MembersX
byImportSpec.localPrefix
byImportSpec.Swap.x
byImportSpec.Less.jname
declImports.gen
importPath
importPath.err
sortSpecs
sortSpecs.tokFile
sortSpecs.RangeStmt_4327.i
byCommentPos.Swap.i
declImports.RangeStmt_3019.spec
importName.n
importComment.c
sortSpecs.RangeStmt_4591.g
sortSpecs.firstSpecLine
byImportSpec.Swap.i
sortSpecs.RangeStmt_5822.i
byImportSpec.Less.igroup
byImportSpec.Less.iname
byCommentPos.Swap.x
byCommentPos.Swap.j
sortSpecs.RangeStmt_4327.s
sortSpecs.lastLine
sortSpecs.cend
sortSpecs.RangeStmt_6505.BlockStmt.BlockStmt.BlockStmt.req
byCommentPos
byCommentPos.Less.j
sortImports.RangeStmt_616.BlockStmt.RangeStmt_1094.s
sortSpecs.RangeStmt_6505.s
byImportSpec.Swap.j
byCommentPos.Less.i
sortImports.f
sortImports.RangeStmt_616.BlockStmt.i
collapse.prev
sortSpecs.RangeStmt_5570.BlockStmt.BlockStmt.p
byCommentPos.Len.x
byCommentPos.Swap
byCommentPos.Len
sortImports.RangeStmt_616.i
importName.s
importComment
sortSpecs.specIndex
sortSpecs.RangeStmt_5570.i
byImportSpec
posSpan
sortSpecs.RangeStmt_5822.BlockStmt.RangeStmt_6021.BlockStmt.RangeStmt_6061.c
sortSpecs.RangeStmt_6505.BlockStmt.line
byImportSpec.Len.x
sortImports.RangeStmt_616.d
sortImports.RangeStmt_616.BlockStmt.BlockStmt.lastLine
sortImports.RangeStmt_616.BlockStmt.BlockStmt.rParenLine
mergeImports.i
collapse
sortSpecs.localPrefix
posSpan.Start
sortSpecs.pos
sortImports.localPrefix
sortImports.tokFile
mergeImports
importName
importComment.s
collapse.next
sortSpecs.RangeStmt_4591.i
mergeImports.f
sortSpecs.specs
sortSpecs.cstart
sortSpecs.importComment
byImportSpec.Less.jpath
byCommentPos.Less.x
sortImports.RangeStmt_616.BlockStmt.RangeStmt_1094.j
mergeImports.first
mergeImports.BlockStmt.RangeStmt_2610.spec
sortSpecs.RangeStmt_5570.s
sortSpecs.RangeStmt_6505.BlockStmt.p
byImportSpec.Less.i
importPath.s
sortSpecs.f
byImportSpec.Len
sortSpecs.RangeStmt_5822.BlockStmt.RangeStmt_6021.g
byImportSpec.specs
sortImports
declImports.path
importPath.t
sortSpecs.RangeStmt_4944.g
sortSpecs.RangeStmt_5822.s
sortSpecs.RangeStmt_5822.BlockStmt.nextSpecPos
byImportSpec.Swap
byImportSpec.Less.jgroup
byCommentPos.Less
declImports
posSpan.End
byImportSpec.Less.x
byImportSpec.Less
byImportSpec.Less.j
byImportSpec.Less.ipath
Members
X