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