github.com/tcnksm/go@v0.0.0-20141208075154-439b32936367/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() }