github.com/graybobo/golang.org-package-offline-cache@v0.0.0-20200626051047-6608995c132f/x/tools/go/ast/astutil/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 in the LICENSE file.
     4  
     5  // Package astutil contains common utilities for working with the Go AST.
     6  package astutil // import "golang.org/x/tools/go/ast/astutil"
     7  
     8  import (
     9  	"fmt"
    10  	"go/ast"
    11  	"go/token"
    12  	"strconv"
    13  	"strings"
    14  )
    15  
    16  // AddImport adds the import path to the file f, if absent.
    17  func AddImport(fset *token.FileSet, f *ast.File, ipath string) (added bool) {
    18  	return AddNamedImport(fset, f, "", ipath)
    19  }
    20  
    21  // AddNamedImport adds the import path to the file f, if absent.
    22  // If name is not empty, it is used to rename the import.
    23  //
    24  // For example, calling
    25  //	AddNamedImport(fset, f, "pathpkg", "path")
    26  // adds
    27  //	import pathpkg "path"
    28  func AddNamedImport(fset *token.FileSet, f *ast.File, name, ipath string) (added bool) {
    29  	if imports(f, ipath) {
    30  		return false
    31  	}
    32  
    33  	newImport := &ast.ImportSpec{
    34  		Path: &ast.BasicLit{
    35  			Kind:  token.STRING,
    36  			Value: strconv.Quote(ipath),
    37  		},
    38  	}
    39  	if name != "" {
    40  		newImport.Name = &ast.Ident{Name: name}
    41  	}
    42  
    43  	// Find an import decl to add to.
    44  	// The goal is to find an existing import
    45  	// whose import path has the longest shared
    46  	// prefix with ipath.
    47  	var (
    48  		bestMatch  = -1         // length of longest shared prefix
    49  		lastImport = -1         // index in f.Decls of the file's final import decl
    50  		impDecl    *ast.GenDecl // import decl containing the best match
    51  		impIndex   = -1         // spec index in impDecl containing the best match
    52  	)
    53  	for i, decl := range f.Decls {
    54  		gen, ok := decl.(*ast.GenDecl)
    55  		if ok && gen.Tok == token.IMPORT {
    56  			lastImport = i
    57  			// Do not add to import "C", to avoid disrupting the
    58  			// association with its doc comment, breaking cgo.
    59  			if declImports(gen, "C") {
    60  				continue
    61  			}
    62  
    63  			// Match an empty import decl if that's all that is available.
    64  			if len(gen.Specs) == 0 && bestMatch == -1 {
    65  				impDecl = gen
    66  			}
    67  
    68  			// Compute longest shared prefix with imports in this group.
    69  			for j, spec := range gen.Specs {
    70  				impspec := spec.(*ast.ImportSpec)
    71  				n := matchLen(importPath(impspec), ipath)
    72  				if n > bestMatch {
    73  					bestMatch = n
    74  					impDecl = gen
    75  					impIndex = j
    76  				}
    77  			}
    78  		}
    79  	}
    80  
    81  	// If no import decl found, add one after the last import.
    82  	if impDecl == nil {
    83  		impDecl = &ast.GenDecl{
    84  			Tok: token.IMPORT,
    85  		}
    86  		if lastImport >= 0 {
    87  			impDecl.TokPos = f.Decls[lastImport].End()
    88  		} else {
    89  			// There are no existing imports.
    90  			// Our new import goes after the package declaration and after
    91  			// the comment, if any, that starts on the same line as the
    92  			// package declaration.
    93  			impDecl.TokPos = f.Package
    94  
    95  			file := fset.File(f.Package)
    96  			pkgLine := file.Line(f.Package)
    97  			for _, c := range f.Comments {
    98  				if file.Line(c.Pos()) > pkgLine {
    99  					break
   100  				}
   101  				impDecl.TokPos = c.End()
   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  		// Assign same position as the previous import,
   121  		// so that the sorter sees it as being in the same block.
   122  		pos = impDecl.Specs[insertAt-1].Pos()
   123  	}
   124  	if newImport.Name != nil {
   125  		newImport.Name.NamePos = pos
   126  	}
   127  	newImport.Path.ValuePos = pos
   128  	newImport.EndPos = pos
   129  
   130  	// Clean up parens. impDecl contains at least one spec.
   131  	if len(impDecl.Specs) == 1 {
   132  		// Remove unneeded parens.
   133  		impDecl.Lparen = token.NoPos
   134  	} else if !impDecl.Lparen.IsValid() {
   135  		// impDecl needs parens added.
   136  		impDecl.Lparen = impDecl.Specs[0].Pos()
   137  	}
   138  
   139  	f.Imports = append(f.Imports, newImport)
   140  	return true
   141  }
   142  
   143  // DeleteImport deletes the import path from the file f, if present.
   144  func DeleteImport(fset *token.FileSet, f *ast.File, path string) (deleted bool) {
   145  	var delspecs []*ast.ImportSpec
   146  
   147  	// Find the import nodes that import path, if any.
   148  	for i := 0; i < len(f.Decls); i++ {
   149  		decl := f.Decls[i]
   150  		gen, ok := decl.(*ast.GenDecl)
   151  		if !ok || gen.Tok != token.IMPORT {
   152  			continue
   153  		}
   154  		for j := 0; j < len(gen.Specs); j++ {
   155  			spec := gen.Specs[j]
   156  			impspec := spec.(*ast.ImportSpec)
   157  			if importPath(impspec) != path {
   158  				continue
   159  			}
   160  
   161  			// We found an import spec that imports path.
   162  			// Delete it.
   163  			delspecs = append(delspecs, impspec)
   164  			deleted = true
   165  			copy(gen.Specs[j:], gen.Specs[j+1:])
   166  			gen.Specs = gen.Specs[:len(gen.Specs)-1]
   167  
   168  			// If this was the last import spec in this decl,
   169  			// delete the decl, too.
   170  			if len(gen.Specs) == 0 {
   171  				copy(f.Decls[i:], f.Decls[i+1:])
   172  				f.Decls = f.Decls[:len(f.Decls)-1]
   173  				i--
   174  				break
   175  			} else if len(gen.Specs) == 1 {
   176  				gen.Lparen = token.NoPos // drop parens
   177  			}
   178  			if j > 0 {
   179  				lastImpspec := gen.Specs[j-1].(*ast.ImportSpec)
   180  				lastLine := fset.Position(lastImpspec.Path.ValuePos).Line
   181  				line := fset.Position(impspec.Path.ValuePos).Line
   182  
   183  				// We deleted an entry but now there may be
   184  				// a blank line-sized hole where the import was.
   185  				if line-lastLine > 1 {
   186  					// There was a blank line immediately preceding the deleted import,
   187  					// so there's no need to close the hole.
   188  					// Do nothing.
   189  				} else {
   190  					// There was no blank line. Close the hole.
   191  					fset.File(gen.Rparen).MergeLine(line)
   192  				}
   193  			}
   194  			j--
   195  		}
   196  	}
   197  
   198  	// Delete them from f.Imports.
   199  	for i := 0; i < len(f.Imports); i++ {
   200  		imp := f.Imports[i]
   201  		for j, del := range delspecs {
   202  			if imp == del {
   203  				copy(f.Imports[i:], f.Imports[i+1:])
   204  				f.Imports = f.Imports[:len(f.Imports)-1]
   205  				copy(delspecs[j:], delspecs[j+1:])
   206  				delspecs = delspecs[:len(delspecs)-1]
   207  				i--
   208  				break
   209  			}
   210  		}
   211  	}
   212  
   213  	if len(delspecs) > 0 {
   214  		panic(fmt.Sprintf("deleted specs from Decls but not Imports: %v", delspecs))
   215  	}
   216  
   217  	return
   218  }
   219  
   220  // RewriteImport rewrites any import of path oldPath to path newPath.
   221  func RewriteImport(fset *token.FileSet, f *ast.File, oldPath, newPath string) (rewrote bool) {
   222  	for _, imp := range f.Imports {
   223  		if importPath(imp) == oldPath {
   224  			rewrote = true
   225  			// record old End, because the default is to compute
   226  			// it using the length of imp.Path.Value.
   227  			imp.EndPos = imp.End()
   228  			imp.Path.Value = strconv.Quote(newPath)
   229  		}
   230  	}
   231  	return
   232  }
   233  
   234  // UsesImport reports whether a given import is used.
   235  func UsesImport(f *ast.File, path string) (used bool) {
   236  	spec := importSpec(f, path)
   237  	if spec == nil {
   238  		return
   239  	}
   240  
   241  	name := spec.Name.String()
   242  	switch name {
   243  	case "<nil>":
   244  		// If the package name is not explicitly specified,
   245  		// make an educated guess. This is not guaranteed to be correct.
   246  		lastSlash := strings.LastIndex(path, "/")
   247  		if lastSlash == -1 {
   248  			name = path
   249  		} else {
   250  			name = path[lastSlash+1:]
   251  		}
   252  	case "_", ".":
   253  		// Not sure if this import is used - err on the side of caution.
   254  		return true
   255  	}
   256  
   257  	ast.Walk(visitFn(func(n ast.Node) {
   258  		sel, ok := n.(*ast.SelectorExpr)
   259  		if ok && isTopName(sel.X, name) {
   260  			used = true
   261  		}
   262  	}), f)
   263  
   264  	return
   265  }
   266  
   267  type visitFn func(node ast.Node)
   268  
   269  func (fn visitFn) Visit(node ast.Node) ast.Visitor {
   270  	fn(node)
   271  	return fn
   272  }
   273  
   274  // imports returns true if f imports path.
   275  func imports(f *ast.File, path string) bool {
   276  	return importSpec(f, path) != nil
   277  }
   278  
   279  // importSpec returns the import spec if f imports path,
   280  // or nil otherwise.
   281  func importSpec(f *ast.File, path string) *ast.ImportSpec {
   282  	for _, s := range f.Imports {
   283  		if importPath(s) == path {
   284  			return s
   285  		}
   286  	}
   287  	return nil
   288  }
   289  
   290  // importPath returns the unquoted import path of s,
   291  // or "" if the path is not properly quoted.
   292  func importPath(s *ast.ImportSpec) string {
   293  	t, err := strconv.Unquote(s.Path.Value)
   294  	if err == nil {
   295  		return t
   296  	}
   297  	return ""
   298  }
   299  
   300  // declImports reports whether gen contains an import of path.
   301  func declImports(gen *ast.GenDecl, path string) bool {
   302  	if gen.Tok != token.IMPORT {
   303  		return false
   304  	}
   305  	for _, spec := range gen.Specs {
   306  		impspec := spec.(*ast.ImportSpec)
   307  		if importPath(impspec) == path {
   308  			return true
   309  		}
   310  	}
   311  	return false
   312  }
   313  
   314  // matchLen returns the length of the longest path segment prefix shared by x and y.
   315  func matchLen(x, y string) int {
   316  	n := 0
   317  	for i := 0; i < len(x) && i < len(y) && x[i] == y[i]; i++ {
   318  		if x[i] == '/' {
   319  			n++
   320  		}
   321  	}
   322  	return n
   323  }
   324  
   325  // isTopName returns true if n is a top-level unresolved identifier with the given name.
   326  func isTopName(n ast.Expr, name string) bool {
   327  	id, ok := n.(*ast.Ident)
   328  	return ok && id.Name == name && id.Obj == nil
   329  }
   330  
   331  // Imports returns the file imports grouped by paragraph.
   332  func Imports(fset *token.FileSet, f *ast.File) [][]*ast.ImportSpec {
   333  	var groups [][]*ast.ImportSpec
   334  
   335  	for _, decl := range f.Decls {
   336  		genDecl, ok := decl.(*ast.GenDecl)
   337  		if !ok || genDecl.Tok != token.IMPORT {
   338  			break
   339  		}
   340  
   341  		group := []*ast.ImportSpec{}
   342  
   343  		var lastLine int
   344  		for _, spec := range genDecl.Specs {
   345  			importSpec := spec.(*ast.ImportSpec)
   346  			pos := importSpec.Path.ValuePos
   347  			line := fset.Position(pos).Line
   348  			if lastLine > 0 && pos > 0 && line-lastLine > 1 {
   349  				groups = append(groups, group)
   350  				group = []*ast.ImportSpec{}
   351  			}
   352  			group = append(group, importSpec)
   353  			lastLine = line
   354  		}
   355  		groups = append(groups, group)
   356  	}
   357  
   358  	return groups
   359  }