Source file src/cmd/vendor/golang.org/x/tools/internal/refactor/imports.go

     1  // Copyright 2025 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 refactor
     6  
     7  // This file defines operations for computing edits to imports.
     8  
     9  import (
    10  	"go/ast"
    11  	"go/token"
    12  	"go/types"
    13  	pathpkg "path"
    14  	"strconv"
    15  
    16  	"golang.org/x/tools/internal/packagepath"
    17  )
    18  
    19  // AddImport returns the prefix (either "pkg." or "") that should be
    20  // used to qualify references to the desired symbol (member) imported
    21  // from the specified package, plus any necessary edits to the file's
    22  // import declaration to add a new import.
    23  //
    24  // If the import already exists, and is accessible at pos, AddImport
    25  // returns the existing name and no edits. (If the existing import is
    26  // a dot import, the prefix is "".)
    27  //
    28  // Otherwise, it adds a new import, using a local name derived from
    29  // the preferred name. To request a blank import, use a preferredName
    30  // of "_", and discard the prefix result; member is ignored in this
    31  // case.
    32  //
    33  // AddImport accepts the caller's implicit claim that the imported
    34  // package declares member.
    35  //
    36  // AddImport does not mutate its arguments.
    37  func AddImport(info *types.Info, file *ast.File, preferredName, pkgpath, member string, pos token.Pos) (prefix string, edits []Edit) {
    38  	// Find innermost enclosing lexical block.
    39  	scope := info.Scopes[file].Innermost(pos)
    40  	if scope == nil {
    41  		panic("no enclosing lexical block")
    42  	}
    43  
    44  	// Is there an existing import of this package?
    45  	// If so, are we in its scope? (not shadowed)
    46  	for _, spec := range file.Imports {
    47  		pkgname := info.PkgNameOf(spec)
    48  		if pkgname != nil && pkgname.Imported().Path() == pkgpath {
    49  			name := pkgname.Name()
    50  			if preferredName == "_" {
    51  				// Request for blank import; any existing import will do.
    52  				return "", nil
    53  			}
    54  			if name == "." {
    55  				// The scope of ident must be the file scope.
    56  				if s, _ := scope.LookupParent(member, pos); s == info.Scopes[file] {
    57  					return "", nil
    58  				}
    59  			} else if _, obj := scope.LookupParent(name, pos); obj == pkgname {
    60  				return name + ".", nil
    61  			}
    62  		}
    63  	}
    64  
    65  	// We must add a new import.
    66  
    67  	// Ensure we have a fresh name.
    68  	newName := preferredName
    69  	if preferredName != "_" {
    70  		newName = FreshName(scope, pos, preferredName)
    71  		prefix = newName + "."
    72  	}
    73  
    74  	// Use a renaming import whenever the preferred name is not
    75  	// available, or the chosen name does not match the last
    76  	// segment of its path.
    77  	if newName == preferredName && newName == pathpkg.Base(pkgpath) {
    78  		newName = ""
    79  	}
    80  
    81  	return prefix, AddImportEdits(file, newName, pkgpath)
    82  }
    83  
    84  // AddImportEdits returns the edits to add an import of the specified
    85  // package, without any analysis of whether this is necessary or safe.
    86  // If name is nonempty, it is used as an explicit [ImportSpec.Name].
    87  //
    88  // A sequence of calls to AddImportEdits that each add the file's
    89  // first import (or in a file that does not have a grouped import) may
    90  // result in multiple import declarations, rather than a single one
    91  // with multiple ImportSpecs. However, a subsequent run of
    92  // x/tools/cmd/goimports ([imports.Process]) will combine them.
    93  //
    94  // AddImportEdits does not mutate the AST.
    95  func AddImportEdits(file *ast.File, name, pkgpath string) []Edit {
    96  	newText := strconv.Quote(pkgpath)
    97  	if name != "" {
    98  		newText = name + " " + newText
    99  	}
   100  
   101  	// Create a new import declaration either before the first existing
   102  	// declaration (which must exist), including its comments; or
   103  	// inside the declaration, if it is an import group.
   104  	decl0 := file.Decls[0]
   105  	before := decl0.Pos()
   106  	switch decl0 := decl0.(type) {
   107  	case *ast.GenDecl:
   108  		if decl0.Doc != nil {
   109  			before = decl0.Doc.Pos()
   110  		}
   111  	case *ast.FuncDecl:
   112  		if decl0.Doc != nil {
   113  			before = decl0.Doc.Pos()
   114  		}
   115  	}
   116  	var pos token.Pos
   117  	if gd, ok := decl0.(*ast.GenDecl); ok && gd.Tok == token.IMPORT && gd.Rparen.IsValid() {
   118  		// Have existing grouped import ( ... ) decl.
   119  		if packagepath.IsStdPackage(pkgpath) && len(gd.Specs) > 0 {
   120  			// Add spec for a std package before
   121  			// first existing spec, followed by
   122  			// a blank line if the next one is non-std.
   123  			first := gd.Specs[0].(*ast.ImportSpec)
   124  			pos = first.Pos()
   125  			if !packagepath.IsStdPackage(first.Path.Value) {
   126  				newText += "\n"
   127  			}
   128  			newText += "\n\t"
   129  		} else {
   130  			// Add spec at end of group.
   131  			pos = gd.Rparen
   132  			newText = "\t" + newText + "\n"
   133  		}
   134  	} else {
   135  		// No import decl, or non-grouped import.
   136  		// Add a new import decl before first decl.
   137  		// (gofmt will merge multiple import decls.)
   138  		//
   139  		// TODO(adonovan): do better here; plunder the
   140  		// mergeImports logic from [imports.Process].
   141  		pos = before
   142  		newText = "import " + newText + "\n\n"
   143  	}
   144  	return []Edit{{
   145  		Pos:     pos,
   146  		End:     pos,
   147  		NewText: []byte(newText),
   148  	}}
   149  }
   150  

View as plain text