github.com/llvm-mirror/llgo@v0.0.0-20190322182713-bf6f0a60fce1/third_party/gotools/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 "llvm.org/llgo/third_party/gotools/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 newImport.Path.ValuePos = pos 125 newImport.EndPos = pos 126 127 // Clean up parens. impDecl contains at least one spec. 128 if len(impDecl.Specs) == 1 { 129 // Remove unneeded parens. 130 impDecl.Lparen = token.NoPos 131 } else if !impDecl.Lparen.IsValid() { 132 // impDecl needs parens added. 133 impDecl.Lparen = impDecl.Specs[0].Pos() 134 } 135 136 f.Imports = append(f.Imports, newImport) 137 return true 138 } 139 140 // DeleteImport deletes the import path from the file f, if present. 141 func DeleteImport(fset *token.FileSet, f *ast.File, path string) (deleted bool) { 142 var delspecs []*ast.ImportSpec 143 144 // Find the import nodes that import path, if any. 145 for i := 0; i < len(f.Decls); i++ { 146 decl := f.Decls[i] 147 gen, ok := decl.(*ast.GenDecl) 148 if !ok || gen.Tok != token.IMPORT { 149 continue 150 } 151 for j := 0; j < len(gen.Specs); j++ { 152 spec := gen.Specs[j] 153 impspec := spec.(*ast.ImportSpec) 154 if importPath(impspec) != path { 155 continue 156 } 157 158 // We found an import spec that imports path. 159 // Delete it. 160 delspecs = append(delspecs, impspec) 161 deleted = true 162 copy(gen.Specs[j:], gen.Specs[j+1:]) 163 gen.Specs = gen.Specs[:len(gen.Specs)-1] 164 165 // If this was the last import spec in this decl, 166 // delete the decl, too. 167 if len(gen.Specs) == 0 { 168 copy(f.Decls[i:], f.Decls[i+1:]) 169 f.Decls = f.Decls[:len(f.Decls)-1] 170 i-- 171 break 172 } else if len(gen.Specs) == 1 { 173 gen.Lparen = token.NoPos // drop parens 174 } 175 if j > 0 { 176 lastImpspec := gen.Specs[j-1].(*ast.ImportSpec) 177 lastLine := fset.Position(lastImpspec.Path.ValuePos).Line 178 line := fset.Position(impspec.Path.ValuePos).Line 179 180 // We deleted an entry but now there may be 181 // a blank line-sized hole where the import was. 182 if line-lastLine > 1 { 183 // There was a blank line immediately preceding the deleted import, 184 // so there's no need to close the hole. 185 // Do nothing. 186 } else { 187 // There was no blank line. Close the hole. 188 fset.File(gen.Rparen).MergeLine(line) 189 } 190 } 191 j-- 192 } 193 } 194 195 // Delete them from f.Imports. 196 for i := 0; i < len(f.Imports); i++ { 197 imp := f.Imports[i] 198 for j, del := range delspecs { 199 if imp == del { 200 copy(f.Imports[i:], f.Imports[i+1:]) 201 f.Imports = f.Imports[:len(f.Imports)-1] 202 copy(delspecs[j:], delspecs[j+1:]) 203 delspecs = delspecs[:len(delspecs)-1] 204 i-- 205 break 206 } 207 } 208 } 209 210 if len(delspecs) > 0 { 211 panic(fmt.Sprintf("deleted specs from Decls but not Imports: %v", delspecs)) 212 } 213 214 return 215 } 216 217 // RewriteImport rewrites any import of path oldPath to path newPath. 218 func RewriteImport(fset *token.FileSet, f *ast.File, oldPath, newPath string) (rewrote bool) { 219 for _, imp := range f.Imports { 220 if importPath(imp) == oldPath { 221 rewrote = true 222 // record old End, because the default is to compute 223 // it using the length of imp.Path.Value. 224 imp.EndPos = imp.End() 225 imp.Path.Value = strconv.Quote(newPath) 226 } 227 } 228 return 229 } 230 231 // UsesImport reports whether a given import is used. 232 func UsesImport(f *ast.File, path string) (used bool) { 233 spec := importSpec(f, path) 234 if spec == nil { 235 return 236 } 237 238 name := spec.Name.String() 239 switch name { 240 case "<nil>": 241 // If the package name is not explicitly specified, 242 // make an educated guess. This is not guaranteed to be correct. 243 lastSlash := strings.LastIndex(path, "/") 244 if lastSlash == -1 { 245 name = path 246 } else { 247 name = path[lastSlash+1:] 248 } 249 case "_", ".": 250 // Not sure if this import is used - err on the side of caution. 251 return true 252 } 253 254 ast.Walk(visitFn(func(n ast.Node) { 255 sel, ok := n.(*ast.SelectorExpr) 256 if ok && isTopName(sel.X, name) { 257 used = true 258 } 259 }), f) 260 261 return 262 } 263 264 type visitFn func(node ast.Node) 265 266 func (fn visitFn) Visit(node ast.Node) ast.Visitor { 267 fn(node) 268 return fn 269 } 270 271 // imports returns true if f imports path. 272 func imports(f *ast.File, path string) bool { 273 return importSpec(f, path) != nil 274 } 275 276 // importSpec returns the import spec if f imports path, 277 // or nil otherwise. 278 func importSpec(f *ast.File, path string) *ast.ImportSpec { 279 for _, s := range f.Imports { 280 if importPath(s) == path { 281 return s 282 } 283 } 284 return nil 285 } 286 287 // importPath returns the unquoted import path of s, 288 // or "" if the path is not properly quoted. 289 func importPath(s *ast.ImportSpec) string { 290 t, err := strconv.Unquote(s.Path.Value) 291 if err == nil { 292 return t 293 } 294 return "" 295 } 296 297 // declImports reports whether gen contains an import of path. 298 func declImports(gen *ast.GenDecl, path string) bool { 299 if gen.Tok != token.IMPORT { 300 return false 301 } 302 for _, spec := range gen.Specs { 303 impspec := spec.(*ast.ImportSpec) 304 if importPath(impspec) == path { 305 return true 306 } 307 } 308 return false 309 } 310 311 // matchLen returns the length of the longest path segment prefix shared by x and y. 312 func matchLen(x, y string) int { 313 n := 0 314 for i := 0; i < len(x) && i < len(y) && x[i] == y[i]; i++ { 315 if x[i] == '/' { 316 n++ 317 } 318 } 319 return n 320 } 321 322 // isTopName returns true if n is a top-level unresolved identifier with the given name. 323 func isTopName(n ast.Expr, name string) bool { 324 id, ok := n.(*ast.Ident) 325 return ok && id.Name == name && id.Obj == nil 326 } 327 328 // Imports returns the file imports grouped by paragraph. 329 func Imports(fset *token.FileSet, f *ast.File) [][]*ast.ImportSpec { 330 var groups [][]*ast.ImportSpec 331 332 for _, decl := range f.Decls { 333 genDecl, ok := decl.(*ast.GenDecl) 334 if !ok || genDecl.Tok != token.IMPORT { 335 break 336 } 337 338 group := []*ast.ImportSpec{} 339 340 var lastLine int 341 for _, spec := range genDecl.Specs { 342 importSpec := spec.(*ast.ImportSpec) 343 pos := importSpec.Path.ValuePos 344 line := fset.Position(pos).Line 345 if lastLine > 0 && pos > 0 && line-lastLine > 1 { 346 groups = append(groups, group) 347 group = []*ast.ImportSpec{} 348 } 349 group = append(group, importSpec) 350 lastLine = line 351 } 352 groups = append(groups, group) 353 } 354 355 return groups 356 }