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 }