github.com/0xKiwi/rules_go@v0.24.3/go/tools/builders/imports.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 at
     4  //
     5  // https://github.com/golang/tools/blob/master/LICENSE
     6  
     7  package main
     8  
     9  import (
    10  	"go/ast"
    11  	"go/token"
    12  	"strconv"
    13  	"strings"
    14  )
    15  
    16  // Adapted from golang.org/x/tools/go/ast/astutil.AddNamedImport
    17  func addNamedImport(fset *token.FileSet, f *ast.File, name, path string) bool {
    18  	newImport := &ast.ImportSpec{
    19  		Path: &ast.BasicLit{
    20  			Kind:  token.STRING,
    21  			Value: strconv.Quote(path),
    22  		},
    23  	}
    24  	if name != "" {
    25  		newImport.Name = &ast.Ident{Name: name}
    26  	}
    27  
    28  	// Find an import decl to add to.
    29  	// The goal is to find an existing import
    30  	// whose import path has the longest shared
    31  	// prefix with path.
    32  	var (
    33  		bestMatch  = -1         // length of longest shared prefix
    34  		lastImport = -1         // index in f.Decls of the file's final import decl
    35  		impDecl    *ast.GenDecl // import decl containing the best match
    36  		impIndex   = -1         // spec index in impDecl containing the best match
    37  
    38  		isThirdPartyPath = isThirdParty(path)
    39  	)
    40  	for i, decl := range f.Decls {
    41  		gen, ok := decl.(*ast.GenDecl)
    42  		if ok && gen.Tok == token.IMPORT {
    43  			lastImport = i
    44  			// Do not add to import "C", to avoid disrupting the
    45  			// association with its doc comment, breaking cgo.
    46  			if declImports(gen, "C") {
    47  				continue
    48  			}
    49  
    50  			// Match an empty import decl if that's all that is available.
    51  			if len(gen.Specs) == 0 && bestMatch == -1 {
    52  				impDecl = gen
    53  			}
    54  
    55  			// Compute longest shared prefix with imports in this group and find best
    56  			// matched import spec.
    57  			// 1. Always prefer import spec with longest shared prefix.
    58  			// 2. While match length is 0,
    59  			// - for stdlib package: prefer first import spec.
    60  			// - for third party package: prefer first third party import spec.
    61  			// We cannot use last import spec as best match for third party package
    62  			// because grouped imports are usually placed last by goimports -local
    63  			// flag.
    64  			// See issue #19190.
    65  			seenAnyThirdParty := false
    66  			for j, spec := range gen.Specs {
    67  				impspec := spec.(*ast.ImportSpec)
    68  				p := importPath(impspec)
    69  				n := matchLen(p, path)
    70  				if n > bestMatch || (bestMatch == 0 && !seenAnyThirdParty && isThirdPartyPath) {
    71  					bestMatch = n
    72  					impDecl = gen
    73  					impIndex = j
    74  				}
    75  				seenAnyThirdParty = seenAnyThirdParty || isThirdParty(p)
    76  			}
    77  		}
    78  	}
    79  
    80  	// If no import decl found, add one after the last import.
    81  	if impDecl == nil {
    82  		impDecl = &ast.GenDecl{
    83  			Tok: token.IMPORT,
    84  		}
    85  		if lastImport >= 0 {
    86  			impDecl.TokPos = f.Decls[lastImport].End()
    87  		} else {
    88  			// There are no existing imports.
    89  			// Our new import, preceded by a blank line,  goes after the package declaration
    90  			// and after the comment, if any, that starts on the same line as the
    91  			// package declaration.
    92  			impDecl.TokPos = f.Package
    93  
    94  			file := fset.File(f.Package)
    95  			pkgLine := file.Line(f.Package)
    96  			for _, c := range f.Comments {
    97  				if file.Line(c.Pos()) > pkgLine {
    98  					break
    99  				}
   100  				// +2 for a blank line
   101  				impDecl.TokPos = c.End() + 2
   102  			}
   103  		}
   104  		f.Decls = append(f.Decls, nil)
   105  		copy(f.Decls[lastImport+2:], f.Decls[lastImport+1:])
   106  		f.Decls[lastImport+1] = impDecl
   107  	}
   108  
   109  	// Insert new import at insertAt.
   110  	insertAt := 0
   111  	if impIndex >= 0 {
   112  		// insert after the found import
   113  		insertAt = impIndex + 1
   114  	}
   115  	impDecl.Specs = append(impDecl.Specs, nil)
   116  	copy(impDecl.Specs[insertAt+1:], impDecl.Specs[insertAt:])
   117  	impDecl.Specs[insertAt] = newImport
   118  	pos := impDecl.Pos()
   119  	if insertAt > 0 {
   120  		// If there is a comment after an existing import, preserve the comment
   121  		// position by adding the new import after the comment.
   122  		if spec, ok := impDecl.Specs[insertAt-1].(*ast.ImportSpec); ok && spec.Comment != nil {
   123  			pos = spec.Comment.End()
   124  		} else {
   125  			// Assign same position as the previous import,
   126  			// so that the sorter sees it as being in the same block.
   127  			pos = impDecl.Specs[insertAt-1].Pos()
   128  		}
   129  	}
   130  	if newImport.Name != nil {
   131  		newImport.Name.NamePos = pos
   132  	}
   133  	newImport.Path.ValuePos = pos
   134  	newImport.EndPos = pos
   135  
   136  	// Clean up parens. impDecl contains at least one spec.
   137  	if len(impDecl.Specs) == 1 {
   138  		// Remove unneeded parens.
   139  		impDecl.Lparen = token.NoPos
   140  	} else if !impDecl.Lparen.IsValid() {
   141  		// impDecl needs parens added.
   142  		impDecl.Lparen = impDecl.Specs[0].Pos()
   143  	}
   144  
   145  	f.Imports = append(f.Imports, newImport)
   146  
   147  	if len(f.Decls) <= 1 {
   148  		return true
   149  	}
   150  
   151  	// Merge all the import declarations into the first one.
   152  	var first *ast.GenDecl
   153  	for i := 0; i < len(f.Decls); i++ {
   154  		decl := f.Decls[i]
   155  		gen, ok := decl.(*ast.GenDecl)
   156  		if !ok || gen.Tok != token.IMPORT || declImports(gen, "C") {
   157  			continue
   158  		}
   159  		if first == nil {
   160  			first = gen
   161  			continue // Don't touch the first one.
   162  		}
   163  		// We now know there is more than one package in this import
   164  		// declaration. Ensure that it ends up parenthesized.
   165  		first.Lparen = first.Pos()
   166  		// Move the imports of the other import declaration to the first one.
   167  		for _, spec := range gen.Specs {
   168  			spec.(*ast.ImportSpec).Path.ValuePos = first.Pos()
   169  			first.Specs = append(first.Specs, spec)
   170  		}
   171  		f.Decls = append(f.Decls[:i], f.Decls[i+1:]...)
   172  		i--
   173  	}
   174  
   175  	return true
   176  }
   177  
   178  // This function is copied from golang.org/x/tools/go/ast/astutil.isThirdParty
   179  func isThirdParty(importPath string) bool {
   180  	// Third party package import path usually contains "." (".com", ".org", ...)
   181  	// This logic is taken from golang.org/x/tools/imports package.
   182  	return strings.Contains(importPath, ".")
   183  }
   184  
   185  // importPath returns the unquoted import path of s,
   186  // or "" if the path is not properly quoted.
   187  // This function is copied from golang.org/x/tools/go/ast/astutil.importPath
   188  func importPath(s *ast.ImportSpec) string {
   189  	t, err := strconv.Unquote(s.Path.Value)
   190  	if err != nil {
   191  		return ""
   192  	}
   193  	return t
   194  }
   195  
   196  // declImports reports whether gen contains an import of path.
   197  // This function is copied from golang.org/x/tools/go/ast/astutil.declImports
   198  func declImports(gen *ast.GenDecl, path string) bool {
   199  	if gen.Tok != token.IMPORT {
   200  		return false
   201  	}
   202  	for _, spec := range gen.Specs {
   203  		impspec := spec.(*ast.ImportSpec)
   204  		if importPath(impspec) == path {
   205  			return true
   206  		}
   207  	}
   208  	return false
   209  }
   210  
   211  // matchLen returns the length of the longest path segment prefix shared by x and y.
   212  // This function is copied from golang.org/x/tools/go/ast/astutil.matchLen
   213  func matchLen(x, y string) int {
   214  	n := 0
   215  	for i := 0; i < len(x) && i < len(y) && x[i] == y[i]; i++ {
   216  		if x[i] == '/' {
   217  			n++
   218  		}
   219  	}
   220  	return n
   221  }