gopkg.in/alecthomas/gometalinter.v3@v3.0.0/_linters/src/golang.org/x/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  
     7  package imports
     8  
     9  import (
    10  	"go/ast"
    11  	"go/token"
    12  	"sort"
    13  	"strconv"
    14  )
    15  
    16  // sortImports sorts runs of consecutive import lines in import blocks in f.
    17  // It also removes duplicate imports when it is possible to do so without data loss.
    18  func sortImports(fset *token.FileSet, f *ast.File) {
    19  	for i, d := range f.Decls {
    20  		d, ok := d.(*ast.GenDecl)
    21  		if !ok || d.Tok != token.IMPORT {
    22  			// Not an import declaration, so we're done.
    23  			// Imports are always first.
    24  			break
    25  		}
    26  
    27  		if len(d.Specs) == 0 {
    28  			// Empty import block, remove it.
    29  			f.Decls = append(f.Decls[:i], f.Decls[i+1:]...)
    30  		}
    31  
    32  		if !d.Lparen.IsValid() {
    33  			// Not a block: sorted by default.
    34  			continue
    35  		}
    36  
    37  		// Identify and sort runs of specs on successive lines.
    38  		i := 0
    39  		specs := d.Specs[:0]
    40  		for j, s := range d.Specs {
    41  			if j > i && fset.Position(s.Pos()).Line > 1+fset.Position(d.Specs[j-1].End()).Line {
    42  				// j begins a new run.  End this one.
    43  				specs = append(specs, sortSpecs(fset, f, d.Specs[i:j])...)
    44  				i = j
    45  			}
    46  		}
    47  		specs = append(specs, sortSpecs(fset, f, d.Specs[i:])...)
    48  		d.Specs = specs
    49  
    50  		// Deduping can leave a blank line before the rparen; clean that up.
    51  		if len(d.Specs) > 0 {
    52  			lastSpec := d.Specs[len(d.Specs)-1]
    53  			lastLine := fset.Position(lastSpec.Pos()).Line
    54  			if rParenLine := fset.Position(d.Rparen).Line; rParenLine > lastLine+1 {
    55  				fset.File(d.Rparen).MergeLine(rParenLine - 1)
    56  			}
    57  		}
    58  	}
    59  }
    60  
    61  func importPath(s ast.Spec) string {
    62  	t, err := strconv.Unquote(s.(*ast.ImportSpec).Path.Value)
    63  	if err == nil {
    64  		return t
    65  	}
    66  	return ""
    67  }
    68  
    69  func importName(s ast.Spec) string {
    70  	n := s.(*ast.ImportSpec).Name
    71  	if n == nil {
    72  		return ""
    73  	}
    74  	return n.Name
    75  }
    76  
    77  func importComment(s ast.Spec) string {
    78  	c := s.(*ast.ImportSpec).Comment
    79  	if c == nil {
    80  		return ""
    81  	}
    82  	return c.Text()
    83  }
    84  
    85  // collapse indicates whether prev may be removed, leaving only next.
    86  func collapse(prev, next ast.Spec) bool {
    87  	if importPath(next) != importPath(prev) || importName(next) != importName(prev) {
    88  		return false
    89  	}
    90  	return prev.(*ast.ImportSpec).Comment == nil
    91  }
    92  
    93  type posSpan struct {
    94  	Start token.Pos
    95  	End   token.Pos
    96  }
    97  
    98  func sortSpecs(fset *token.FileSet, f *ast.File, specs []ast.Spec) []ast.Spec {
    99  	// Can't short-circuit here even if specs are already sorted,
   100  	// since they might yet need deduplication.
   101  	// A lone import, however, may be safely ignored.
   102  	if len(specs) <= 1 {
   103  		return specs
   104  	}
   105  
   106  	// Record positions for specs.
   107  	pos := make([]posSpan, len(specs))
   108  	for i, s := range specs {
   109  		pos[i] = posSpan{s.Pos(), s.End()}
   110  	}
   111  
   112  	// Identify comments in this range.
   113  	// Any comment from pos[0].Start to the final line counts.
   114  	lastLine := fset.Position(pos[len(pos)-1].End).Line
   115  	cstart := len(f.Comments)
   116  	cend := len(f.Comments)
   117  	for i, g := range f.Comments {
   118  		if g.Pos() < pos[0].Start {
   119  			continue
   120  		}
   121  		if i < cstart {
   122  			cstart = i
   123  		}
   124  		if fset.Position(g.End()).Line > lastLine {
   125  			cend = i
   126  			break
   127  		}
   128  	}
   129  	comments := f.Comments[cstart:cend]
   130  
   131  	// Assign each comment to the import spec preceding it.
   132  	importComment := map[*ast.ImportSpec][]*ast.CommentGroup{}
   133  	specIndex := 0
   134  	for _, g := range comments {
   135  		for specIndex+1 < len(specs) && pos[specIndex+1].Start <= g.Pos() {
   136  			specIndex++
   137  		}
   138  		s := specs[specIndex].(*ast.ImportSpec)
   139  		importComment[s] = append(importComment[s], g)
   140  	}
   141  
   142  	// Sort the import specs by import path.
   143  	// Remove duplicates, when possible without data loss.
   144  	// Reassign the import paths to have the same position sequence.
   145  	// Reassign each comment to abut the end of its spec.
   146  	// Sort the comments by new position.
   147  	sort.Sort(byImportSpec(specs))
   148  
   149  	// Dedup. Thanks to our sorting, we can just consider
   150  	// adjacent pairs of imports.
   151  	deduped := specs[:0]
   152  	for i, s := range specs {
   153  		if i == len(specs)-1 || !collapse(s, specs[i+1]) {
   154  			deduped = append(deduped, s)
   155  		} else {
   156  			p := s.Pos()
   157  			fset.File(p).MergeLine(fset.Position(p).Line)
   158  		}
   159  	}
   160  	specs = deduped
   161  
   162  	// Fix up comment positions
   163  	for i, s := range specs {
   164  		s := s.(*ast.ImportSpec)
   165  		if s.Name != nil {
   166  			s.Name.NamePos = pos[i].Start
   167  		}
   168  		s.Path.ValuePos = pos[i].Start
   169  		s.EndPos = pos[i].End
   170  		for _, g := range importComment[s] {
   171  			for _, c := range g.List {
   172  				c.Slash = pos[i].End
   173  			}
   174  		}
   175  	}
   176  
   177  	sort.Sort(byCommentPos(comments))
   178  
   179  	return specs
   180  }
   181  
   182  type byImportSpec []ast.Spec // slice of *ast.ImportSpec
   183  
   184  func (x byImportSpec) Len() int      { return len(x) }
   185  func (x byImportSpec) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
   186  func (x byImportSpec) Less(i, j int) bool {
   187  	ipath := importPath(x[i])
   188  	jpath := importPath(x[j])
   189  
   190  	igroup := importGroup(ipath)
   191  	jgroup := importGroup(jpath)
   192  	if igroup != jgroup {
   193  		return igroup < jgroup
   194  	}
   195  
   196  	if ipath != jpath {
   197  		return ipath < jpath
   198  	}
   199  	iname := importName(x[i])
   200  	jname := importName(x[j])
   201  
   202  	if iname != jname {
   203  		return iname < jname
   204  	}
   205  	return importComment(x[i]) < importComment(x[j])
   206  }
   207  
   208  type byCommentPos []*ast.CommentGroup
   209  
   210  func (x byCommentPos) Len() int           { return len(x) }
   211  func (x byCommentPos) Swap(i, j int)      { x[i], x[j] = x[j], x[i] }
   212  func (x byCommentPos) Less(i, j int) bool { return x[i].Pos() < x[j].Pos() }