...

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	// 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		importComments := 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			importComments[s] = append(importComments[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.Slice(specs, func(i, j int) bool {
   142			ipath := importPath(specs[i])
   143			jpath := importPath(specs[j])
   144			if ipath != jpath {
   145				return ipath < jpath
   146			}
   147			iname := importName(specs[i])
   148			jname := importName(specs[j])
   149			if iname != jname {
   150				return iname < jname
   151			}
   152			return importComment(specs[i]) < importComment(specs[j])
   153		})
   154	
   155		// Dedup. Thanks to our sorting, we can just consider
   156		// adjacent pairs of imports.
   157		deduped := specs[:0]
   158		for i, s := range specs {
   159			if i == len(specs)-1 || !collapse(s, specs[i+1]) {
   160				deduped = append(deduped, s)
   161			} else {
   162				p := s.Pos()
   163				fset.File(p).MergeLine(fset.Position(p).Line)
   164			}
   165		}
   166		specs = deduped
   167	
   168		// Fix up comment positions
   169		for i, s := range specs {
   170			s := s.(*ImportSpec)
   171			if s.Name != nil {
   172				s.Name.NamePos = pos[i].Start
   173			}
   174			s.Path.ValuePos = pos[i].Start
   175			s.EndPos = pos[i].End
   176			for _, g := range importComments[s] {
   177				for _, c := range g.List {
   178					c.Slash = pos[i].End
   179				}
   180			}
   181		}
   182	
   183		sort.Slice(comments, func(i, j int) bool {
   184			return comments[i].Pos() < comments[j].Pos()
   185		})
   186	
   187		return specs
   188	}
   189	

View as plain text