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