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 }