github.com/flyinox/gosm@v0.0.0-20171117061539-16768cb62077/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  			rParenLine := fset.Position(d.Rparen).Line
    47  			for rParenLine > lastLine+1 {
    48  				rParenLine--
    49  				fset.File(d.Rparen).MergeLine(rParenLine)
    50  			}
    51  		}
    52  	}
    53  }
    54  
    55  func importPath(s Spec) string {
    56  	t, err := strconv.Unquote(s.(*ImportSpec).Path.Value)
    57  	if err == nil {
    58  		return t
    59  	}
    60  	return ""
    61  }
    62  
    63  func importName(s Spec) string {
    64  	n := s.(*ImportSpec).Name
    65  	if n == nil {
    66  		return ""
    67  	}
    68  	return n.Name
    69  }
    70  
    71  func importComment(s Spec) string {
    72  	c := s.(*ImportSpec).Comment
    73  	if c == nil {
    74  		return ""
    75  	}
    76  	return c.Text()
    77  }
    78  
    79  // collapse indicates whether prev may be removed, leaving only next.
    80  func collapse(prev, next Spec) bool {
    81  	if importPath(next) != importPath(prev) || importName(next) != importName(prev) {
    82  		return false
    83  	}
    84  	return prev.(*ImportSpec).Comment == nil
    85  }
    86  
    87  type posSpan struct {
    88  	Start token.Pos
    89  	End   token.Pos
    90  }
    91  
    92  func sortSpecs(fset *token.FileSet, f *File, specs []Spec) []Spec {
    93  	// Can't short-circuit here even if specs are already sorted,
    94  	// since they might yet need deduplication.
    95  	// A lone import, however, may be safely ignored.
    96  	if len(specs) <= 1 {
    97  		return specs
    98  	}
    99  
   100  	// Record positions for specs.
   101  	pos := make([]posSpan, len(specs))
   102  	for i, s := range specs {
   103  		pos[i] = posSpan{s.Pos(), s.End()}
   104  	}
   105  
   106  	// Identify comments in this range.
   107  	// Any comment from pos[0].Start to the final line counts.
   108  	lastLine := fset.Position(pos[len(pos)-1].End).Line
   109  	cstart := len(f.Comments)
   110  	cend := len(f.Comments)
   111  	for i, g := range f.Comments {
   112  		if g.Pos() < pos[0].Start {
   113  			continue
   114  		}
   115  		if i < cstart {
   116  			cstart = i
   117  		}
   118  		if fset.Position(g.End()).Line > lastLine {
   119  			cend = i
   120  			break
   121  		}
   122  	}
   123  	comments := f.Comments[cstart:cend]
   124  
   125  	// Assign each comment to the import spec preceding it.
   126  	importComment := map[*ImportSpec][]*CommentGroup{}
   127  	specIndex := 0
   128  	for _, g := range comments {
   129  		for specIndex+1 < len(specs) && pos[specIndex+1].Start <= g.Pos() {
   130  			specIndex++
   131  		}
   132  		s := specs[specIndex].(*ImportSpec)
   133  		importComment[s] = append(importComment[s], g)
   134  	}
   135  
   136  	// Sort the import specs by import path.
   137  	// Remove duplicates, when possible without data loss.
   138  	// Reassign the import paths to have the same position sequence.
   139  	// Reassign each comment to abut the end of its spec.
   140  	// Sort the comments by new position.
   141  	sort.Sort(byImportSpec(specs))
   142  
   143  	// Dedup. Thanks to our sorting, we can just consider
   144  	// adjacent pairs of imports.
   145  	deduped := specs[:0]
   146  	for i, s := range specs {
   147  		if i == len(specs)-1 || !collapse(s, specs[i+1]) {
   148  			deduped = append(deduped, s)
   149  		} else {
   150  			p := s.Pos()
   151  			fset.File(p).MergeLine(fset.Position(p).Line)
   152  		}
   153  	}
   154  	specs = deduped
   155  
   156  	// Fix up comment positions
   157  	for i, s := range specs {
   158  		s := s.(*ImportSpec)
   159  		if s.Name != nil {
   160  			s.Name.NamePos = pos[i].Start
   161  		}
   162  		s.Path.ValuePos = pos[i].Start
   163  		s.EndPos = pos[i].End
   164  		for _, g := range importComment[s] {
   165  			for _, c := range g.List {
   166  				c.Slash = pos[i].End
   167  			}
   168  		}
   169  	}
   170  
   171  	sort.Sort(byCommentPos(comments))
   172  
   173  	return specs
   174  }
   175  
   176  type byImportSpec []Spec // slice of *ImportSpec
   177  
   178  func (x byImportSpec) Len() int      { return len(x) }
   179  func (x byImportSpec) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
   180  func (x byImportSpec) Less(i, j int) bool {
   181  	ipath := importPath(x[i])
   182  	jpath := importPath(x[j])
   183  	if ipath != jpath {
   184  		return ipath < jpath
   185  	}
   186  	iname := importName(x[i])
   187  	jname := importName(x[j])
   188  	if iname != jname {
   189  		return iname < jname
   190  	}
   191  	return importComment(x[i]) < importComment(x[j])
   192  }
   193  
   194  type byCommentPos []*CommentGroup
   195  
   196  func (x byCommentPos) Len() int           { return len(x) }
   197  func (x byCommentPos) Swap(i, j int)      { x[i], x[j] = x[j], x[i] }
   198  func (x byCommentPos) Less(i, j int) bool { return x[i].Pos() < x[j].Pos() }