Source file src/pkg/go/ast/import.go
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 func SortImports(fset *token.FileSet, f *File) {
15 for _, d := range f.Decls {
16 d, ok := d.(*GenDecl)
17 if !ok || d.Tok != token.IMPORT {
18 // Not an import declaration, so we're done.
19 // Imports are always first.
20 break
21 }
22
23 if d.Lparen == token.NoPos {
24 // Not a block: sorted by default.
25 continue
26 }
27
28 // Identify and sort runs of specs on successive lines.
29 i := 0
30 for j, s := range d.Specs {
31 if j > i && fset.Position(s.Pos()).Line > 1+fset.Position(d.Specs[j-1].End()).Line {
32 // j begins a new run. End this one.
33 sortSpecs(fset, f, d.Specs[i:j])
34 i = j
35 }
36 }
37 sortSpecs(fset, f, d.Specs[i:])
38 }
39 }
40
41 func importPath(s Spec) string {
42 t, err := strconv.Unquote(s.(*ImportSpec).Path.Value)
43 if err == nil {
44 return t
45 }
46 return ""
47 }
48
49 type posSpan struct {
50 Start token.Pos
51 End token.Pos
52 }
53
54 func sortSpecs(fset *token.FileSet, f *File, specs []Spec) {
55 // Avoid work if already sorted (also catches < 2 entries).
56 sorted := true
57 for i, s := range specs {
58 if i > 0 && importPath(specs[i-1]) > importPath(s) {
59 sorted = false
60 break
61 }
62 }
63 if sorted {
64 return
65 }
66
67 // Record positions for specs.
68 pos := make([]posSpan, len(specs))
69 for i, s := range specs {
70 pos[i] = posSpan{s.Pos(), s.End()}
71 }
72
73 // Identify comments in this range.
74 // Any comment from pos[0].Start to the final line counts.
75 lastLine := fset.Position(pos[len(pos)-1].End).Line
76 cstart := len(f.Comments)
77 cend := len(f.Comments)
78 for i, g := range f.Comments {
79 if g.Pos() < pos[0].Start {
80 continue
81 }
82 if i < cstart {
83 cstart = i
84 }
85 if fset.Position(g.End()).Line > lastLine {
86 cend = i
87 break
88 }
89 }
90 comments := f.Comments[cstart:cend]
91
92 // Assign each comment to the import spec preceding it.
93 importComment := map[*ImportSpec][]*CommentGroup{}
94 specIndex := 0
95 for _, g := range comments {
96 for specIndex+1 < len(specs) && pos[specIndex+1].Start <= g.Pos() {
97 specIndex++
98 }
99 s := specs[specIndex].(*ImportSpec)
100 importComment[s] = append(importComment[s], g)
101 }
102
103 // Sort the import specs by import path.
104 // Reassign the import paths to have the same position sequence.
105 // Reassign each comment to abut the end of its spec.
106 // Sort the comments by new position.
107 sort.Sort(byImportPath(specs))
108 for i, s := range specs {
109 s := s.(*ImportSpec)
110 if s.Name != nil {
111 s.Name.NamePos = pos[i].Start
112 }
113 s.Path.ValuePos = pos[i].Start
114 s.EndPos = pos[i].End
115 for _, g := range importComment[s] {
116 for _, c := range g.List {
117 c.Slash = pos[i].End
118 }
119 }
120 }
121 sort.Sort(byCommentPos(comments))
122 }
123
124 type byImportPath []Spec // slice of *ImportSpec
125
126 func (x byImportPath) Len() int { return len(x) }
127 func (x byImportPath) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
128 func (x byImportPath) Less(i, j int) bool { return importPath(x[i]) < importPath(x[j]) }
129
130 type byCommentPos []*CommentGroup
131
132 func (x byCommentPos) Len() int { return len(x) }
133 func (x byCommentPos) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
134 func (x byCommentPos) Less(i, j int) bool { return x[i].Pos() < x[j].Pos() }