...
Run Format

Source file src/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	// 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() }
   199	

View as plain text