github.com/graybobo/golang.org-package-offline-cache@v0.0.0-20200626051047-6608995c132f/x/tools/refactor/rename/spec.go (about) 1 // Copyright 2014 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 // +build go1.5 6 7 package rename 8 9 // This file contains logic related to specifying a renaming: parsing of 10 // the flags as a form of query, and finding the object(s) it denotes. 11 // See Usage for flag details. 12 13 import ( 14 "bytes" 15 "fmt" 16 "go/ast" 17 "go/build" 18 "go/parser" 19 "go/token" 20 "go/types" 21 "log" 22 "os" 23 "path/filepath" 24 "strconv" 25 "strings" 26 27 "golang.org/x/tools/go/buildutil" 28 "golang.org/x/tools/go/loader" 29 ) 30 31 // A spec specifies an entity to rename. 32 // 33 // It is populated from an -offset flag or -from query; 34 // see Usage for the allowed -from query forms. 35 // 36 type spec struct { 37 // pkg is the package containing the position 38 // specified by the -from or -offset flag. 39 // If filename == "", our search for the 'from' entity 40 // is restricted to this package. 41 pkg string 42 43 // The original name of the entity being renamed. 44 // If the query had a ::from component, this is that; 45 // otherwise it's the last segment, e.g. 46 // (encoding/json.Decoder).from 47 // encoding/json.from 48 fromName string 49 50 // -- The remaining fields are private to this file. All are optional. -- 51 52 // The query's ::x suffix, if any. 53 searchFor string 54 55 // e.g. "Decoder" in "(encoding/json.Decoder).fieldOrMethod" 56 // or "encoding/json.Decoder 57 pkgMember string 58 59 // e.g. fieldOrMethod in "(encoding/json.Decoder).fieldOrMethod" 60 typeMember string 61 62 // Restricts the query to this file. 63 // Implied by -from="file.go::x" and -offset flags. 64 filename string 65 66 // Byte offset of the 'from' identifier within the file named 'filename'. 67 // -offset mode only. 68 offset int 69 } 70 71 // parseFromFlag interprets the "-from" flag value as a renaming specification. 72 // See Usage in rename.go for valid formats. 73 func parseFromFlag(ctxt *build.Context, fromFlag string) (*spec, error) { 74 var spec spec 75 var main string // sans "::x" suffix 76 switch parts := strings.Split(fromFlag, "::"); len(parts) { 77 case 1: 78 main = parts[0] 79 case 2: 80 main = parts[0] 81 spec.searchFor = parts[1] 82 if parts[1] == "" { 83 // error 84 } 85 default: 86 return nil, fmt.Errorf("-from %q: invalid identifier specification (see -help for formats)", fromFlag) 87 } 88 89 if strings.HasSuffix(main, ".go") { 90 // main is "filename.go" 91 if spec.searchFor == "" { 92 return nil, fmt.Errorf("-from: filename %q must have a ::name suffix", main) 93 } 94 spec.filename = main 95 if !buildutil.FileExists(ctxt, spec.filename) { 96 return nil, fmt.Errorf("no such file: %s", spec.filename) 97 } 98 99 bp, err := buildutil.ContainingPackage(ctxt, wd, spec.filename) 100 if err != nil { 101 return nil, err 102 } 103 spec.pkg = bp.ImportPath 104 105 } else { 106 // main is one of: 107 // "importpath" 108 // "importpath".member 109 // (*"importpath".type).fieldormethod (parens and star optional) 110 if err := parseObjectSpec(&spec, main); err != nil { 111 return nil, err 112 } 113 } 114 115 if spec.searchFor != "" { 116 spec.fromName = spec.searchFor 117 } 118 119 // Sanitize the package. 120 // TODO(adonovan): test with relative packages. May need loader changes. 121 bp, err := ctxt.Import(spec.pkg, ".", build.FindOnly) 122 if err != nil { 123 return nil, fmt.Errorf("can't find package %q", spec.pkg) 124 } 125 spec.pkg = bp.ImportPath 126 127 if !isValidIdentifier(spec.fromName) { 128 return nil, fmt.Errorf("-from: invalid identifier %q", spec.fromName) 129 } 130 131 if Verbose { 132 log.Printf("-from spec: %+v", spec) 133 } 134 135 return &spec, nil 136 } 137 138 // parseObjectSpec parses main as one of the non-filename forms of 139 // object specification. 140 func parseObjectSpec(spec *spec, main string) error { 141 // Parse main as a Go expression, albeit a strange one. 142 e, _ := parser.ParseExpr(main) 143 144 if pkg := parseImportPath(e); pkg != "" { 145 // e.g. bytes or "encoding/json": a package 146 spec.pkg = pkg 147 if spec.searchFor == "" { 148 return fmt.Errorf("-from %q: package import path %q must have a ::name suffix", 149 main, main) 150 } 151 return nil 152 } 153 154 if e, ok := e.(*ast.SelectorExpr); ok { 155 x := unparen(e.X) 156 157 // Strip off star constructor, if any. 158 if star, ok := x.(*ast.StarExpr); ok { 159 x = star.X 160 } 161 162 if pkg := parseImportPath(x); pkg != "" { 163 // package member e.g. "encoding/json".HTMLEscape 164 spec.pkg = pkg // e.g. "encoding/json" 165 spec.pkgMember = e.Sel.Name // e.g. "HTMLEscape" 166 spec.fromName = e.Sel.Name 167 return nil 168 } 169 170 if x, ok := x.(*ast.SelectorExpr); ok { 171 // field/method of type e.g. ("encoding/json".Decoder).Decode 172 y := unparen(x.X) 173 if pkg := parseImportPath(y); pkg != "" { 174 spec.pkg = pkg // e.g. "encoding/json" 175 spec.pkgMember = x.Sel.Name // e.g. "Decoder" 176 spec.typeMember = e.Sel.Name // e.g. "Decode" 177 spec.fromName = e.Sel.Name 178 return nil 179 } 180 } 181 } 182 183 return fmt.Errorf("-from %q: invalid expression", main) 184 } 185 186 // parseImportPath returns the import path of the package denoted by e. 187 // Any import path may be represented as a string literal; 188 // single-segment import paths (e.g. "bytes") may also be represented as 189 // ast.Ident. parseImportPath returns "" for all other expressions. 190 func parseImportPath(e ast.Expr) string { 191 switch e := e.(type) { 192 case *ast.Ident: 193 return e.Name // e.g. bytes 194 195 case *ast.BasicLit: 196 if e.Kind == token.STRING { 197 pkgname, _ := strconv.Unquote(e.Value) 198 return pkgname // e.g. "encoding/json" 199 } 200 } 201 return "" 202 } 203 204 // parseOffsetFlag interprets the "-offset" flag value as a renaming specification. 205 func parseOffsetFlag(ctxt *build.Context, offsetFlag string) (*spec, error) { 206 var spec spec 207 // Validate -offset, e.g. file.go:#123 208 parts := strings.Split(offsetFlag, ":#") 209 if len(parts) != 2 { 210 return nil, fmt.Errorf("-offset %q: invalid offset specification", offsetFlag) 211 } 212 213 spec.filename = parts[0] 214 if !buildutil.FileExists(ctxt, spec.filename) { 215 return nil, fmt.Errorf("no such file: %s", spec.filename) 216 } 217 218 bp, err := buildutil.ContainingPackage(ctxt, wd, spec.filename) 219 if err != nil { 220 return nil, err 221 } 222 spec.pkg = bp.ImportPath 223 224 for _, r := range parts[1] { 225 if !isDigit(r) { 226 return nil, fmt.Errorf("-offset %q: non-numeric offset", offsetFlag) 227 } 228 } 229 spec.offset, err = strconv.Atoi(parts[1]) 230 if err != nil { 231 return nil, fmt.Errorf("-offset %q: non-numeric offset", offsetFlag) 232 } 233 234 // Parse the file and check there's an identifier at that offset. 235 fset := token.NewFileSet() 236 f, err := buildutil.ParseFile(fset, ctxt, nil, wd, spec.filename, parser.ParseComments) 237 if err != nil { 238 return nil, fmt.Errorf("-offset %q: cannot parse file: %s", offsetFlag, err) 239 } 240 241 id := identAtOffset(fset, f, spec.offset) 242 if id == nil { 243 return nil, fmt.Errorf("-offset %q: no identifier at this position", offsetFlag) 244 } 245 246 spec.fromName = id.Name 247 248 return &spec, nil 249 } 250 251 var wd = func() string { 252 wd, err := os.Getwd() 253 if err != nil { 254 panic("cannot get working directory: " + err.Error()) 255 } 256 return wd 257 }() 258 259 // For source trees built with 'go build', the -from or -offset 260 // spec identifies exactly one initial 'from' object to rename , 261 // but certain proprietary build systems allow a single file to 262 // appear in multiple packages (e.g. the test package contains a 263 // copy of its library), so there may be multiple objects for 264 // the same source entity. 265 266 func findFromObjects(iprog *loader.Program, spec *spec) ([]types.Object, error) { 267 if spec.filename != "" { 268 return findFromObjectsInFile(iprog, spec) 269 } 270 271 // Search for objects defined in specified package. 272 273 // TODO(adonovan): the iprog.ImportMap has an entry {"main": ...} 274 // for main packages, even though that's not an import path. 275 // Seems like a bug. 276 // 277 // pkgObj := iprog.ImportMap[spec.pkg] 278 // if pkgObj == nil { 279 // return fmt.Errorf("cannot find package %s", spec.pkg) // can't happen? 280 // } 281 282 // Workaround: lookup by value. 283 var pkgObj *types.Package 284 for pkg := range iprog.AllPackages { 285 if pkg.Path() == spec.pkg { 286 pkgObj = pkg 287 break 288 } 289 } 290 info := iprog.AllPackages[pkgObj] 291 292 objects, err := findObjects(info, spec) 293 if err != nil { 294 return nil, err 295 } 296 if len(objects) > 1 { 297 // ambiguous "*" scope query 298 return nil, ambiguityError(iprog.Fset, objects) 299 } 300 return objects, nil 301 } 302 303 func findFromObjectsInFile(iprog *loader.Program, spec *spec) ([]types.Object, error) { 304 var fromObjects []types.Object 305 for _, info := range iprog.AllPackages { 306 // restrict to specified filename 307 // NB: under certain proprietary build systems, a given 308 // filename may appear in multiple packages. 309 for _, f := range info.Files { 310 thisFile := iprog.Fset.File(f.Pos()) 311 if !sameFile(thisFile.Name(), spec.filename) { 312 continue 313 } 314 // This package contains the query file. 315 316 if spec.offset != 0 { 317 // Search for a specific ident by file/offset. 318 id := identAtOffset(iprog.Fset, f, spec.offset) 319 if id == nil { 320 // can't happen? 321 return nil, fmt.Errorf("identifier not found") 322 } 323 obj := info.Uses[id] 324 if obj == nil { 325 obj = info.Defs[id] 326 if obj == nil { 327 // Ident without Object. 328 329 // Package clause? 330 pos := thisFile.Pos(spec.offset) 331 _, path, _ := iprog.PathEnclosingInterval(pos, pos) 332 if len(path) == 2 { // [Ident File] 333 // TODO(adonovan): support this case. 334 return nil, fmt.Errorf("cannot rename %q: renaming package clauses is not yet supported", 335 path[1].(*ast.File).Name.Name) 336 } 337 338 // Implicit y in "switch y := x.(type) {"? 339 if obj := typeSwitchVar(&info.Info, path); obj != nil { 340 return []types.Object{obj}, nil 341 } 342 343 // Probably a type error. 344 return nil, fmt.Errorf("cannot find object for %q", id.Name) 345 } 346 } 347 if obj.Pkg() == nil { 348 return nil, fmt.Errorf("cannot rename predeclared identifiers (%s)", obj) 349 350 } 351 352 fromObjects = append(fromObjects, obj) 353 } else { 354 // do a package-wide query 355 objects, err := findObjects(info, spec) 356 if err != nil { 357 return nil, err 358 } 359 360 // filter results: only objects defined in thisFile 361 var filtered []types.Object 362 for _, obj := range objects { 363 if iprog.Fset.File(obj.Pos()) == thisFile { 364 filtered = append(filtered, obj) 365 } 366 } 367 if len(filtered) == 0 { 368 return nil, fmt.Errorf("no object %q declared in file %s", 369 spec.fromName, spec.filename) 370 } else if len(filtered) > 1 { 371 return nil, ambiguityError(iprog.Fset, filtered) 372 } 373 fromObjects = append(fromObjects, filtered[0]) 374 } 375 break 376 } 377 } 378 if len(fromObjects) == 0 { 379 // can't happen? 380 return nil, fmt.Errorf("file %s was not part of the loaded program", spec.filename) 381 } 382 return fromObjects, nil 383 } 384 385 func typeSwitchVar(info *types.Info, path []ast.Node) types.Object { 386 if len(path) > 3 { 387 // [Ident AssignStmt TypeSwitchStmt...] 388 if sw, ok := path[2].(*ast.TypeSwitchStmt); ok { 389 // choose the first case. 390 if len(sw.Body.List) > 0 { 391 obj := info.Implicits[sw.Body.List[0].(*ast.CaseClause)] 392 if obj != nil { 393 return obj 394 } 395 } 396 } 397 } 398 return nil 399 } 400 401 // On success, findObjects returns the list of objects named 402 // spec.fromName matching the spec. On success, the result has exactly 403 // one element unless spec.searchFor!="", in which case it has at least one 404 // element. 405 // 406 func findObjects(info *loader.PackageInfo, spec *spec) ([]types.Object, error) { 407 if spec.pkgMember == "" { 408 if spec.searchFor == "" { 409 panic(spec) 410 } 411 objects := searchDefs(&info.Info, spec.searchFor) 412 if objects == nil { 413 return nil, fmt.Errorf("no object %q declared in package %q", 414 spec.searchFor, info.Pkg.Path()) 415 } 416 return objects, nil 417 } 418 419 pkgMember := info.Pkg.Scope().Lookup(spec.pkgMember) 420 if pkgMember == nil { 421 return nil, fmt.Errorf("package %q has no member %q", 422 info.Pkg.Path(), spec.pkgMember) 423 } 424 425 var searchFunc *types.Func 426 if spec.typeMember == "" { 427 // package member 428 if spec.searchFor == "" { 429 return []types.Object{pkgMember}, nil 430 } 431 432 // Search within pkgMember, which must be a function. 433 searchFunc, _ = pkgMember.(*types.Func) 434 if searchFunc == nil { 435 return nil, fmt.Errorf("cannot search for %q within %s %q", 436 spec.searchFor, objectKind(pkgMember), pkgMember) 437 } 438 } else { 439 // field/method of type 440 // e.g. (encoding/json.Decoder).Decode 441 // or ::x within it. 442 443 tName, _ := pkgMember.(*types.TypeName) 444 if tName == nil { 445 return nil, fmt.Errorf("%s.%s is a %s, not a type", 446 info.Pkg.Path(), pkgMember.Name(), objectKind(pkgMember)) 447 } 448 449 // search within named type. 450 obj, _, _ := types.LookupFieldOrMethod(tName.Type(), true, info.Pkg, spec.typeMember) 451 if obj == nil { 452 return nil, fmt.Errorf("cannot find field or method %q of %s %s.%s", 453 spec.typeMember, typeKind(tName.Type()), info.Pkg.Path(), tName.Name()) 454 } 455 456 if spec.searchFor == "" { 457 // If it is an embedded field, return the type of the field. 458 if v, ok := obj.(*types.Var); ok && v.Anonymous() { 459 switch t := v.Type().(type) { 460 case *types.Pointer: 461 return []types.Object{t.Elem().(*types.Named).Obj()}, nil 462 case *types.Named: 463 return []types.Object{t.Obj()}, nil 464 } 465 } 466 return []types.Object{obj}, nil 467 } 468 469 searchFunc, _ = obj.(*types.Func) 470 if searchFunc == nil { 471 return nil, fmt.Errorf("cannot search for local name %q within %s (%s.%s).%s; need a function", 472 spec.searchFor, objectKind(obj), info.Pkg.Path(), tName.Name(), 473 obj.Name()) 474 } 475 if isInterface(tName.Type()) { 476 return nil, fmt.Errorf("cannot search for local name %q within abstract method (%s.%s).%s", 477 spec.searchFor, info.Pkg.Path(), tName.Name(), searchFunc.Name()) 478 } 479 } 480 481 // -- search within function or method -- 482 483 decl := funcDecl(info, searchFunc) 484 if decl == nil { 485 return nil, fmt.Errorf("cannot find syntax for %s", searchFunc) // can't happen? 486 } 487 488 var objects []types.Object 489 for _, obj := range searchDefs(&info.Info, spec.searchFor) { 490 // We use positions, not scopes, to determine whether 491 // the obj is within searchFunc. This is clumsy, but the 492 // alternative, using the types.Scope tree, doesn't 493 // account for non-lexical objects like fields and 494 // interface methods. 495 if decl.Pos() <= obj.Pos() && obj.Pos() < decl.End() && obj != searchFunc { 496 objects = append(objects, obj) 497 } 498 } 499 if objects == nil { 500 return nil, fmt.Errorf("no local definition of %q within %s", 501 spec.searchFor, searchFunc) 502 } 503 return objects, nil 504 } 505 506 func funcDecl(info *loader.PackageInfo, fn *types.Func) *ast.FuncDecl { 507 for _, f := range info.Files { 508 for _, d := range f.Decls { 509 if d, ok := d.(*ast.FuncDecl); ok && info.Defs[d.Name] == fn { 510 return d 511 } 512 } 513 } 514 return nil 515 } 516 517 func searchDefs(info *types.Info, name string) []types.Object { 518 var objects []types.Object 519 for id, obj := range info.Defs { 520 if obj == nil { 521 // e.g. blank ident. 522 // TODO(adonovan): but also implicit y in 523 // switch y := x.(type) 524 // Needs some thought. 525 continue 526 } 527 if id.Name == name { 528 objects = append(objects, obj) 529 } 530 } 531 return objects 532 } 533 534 func identAtOffset(fset *token.FileSet, f *ast.File, offset int) *ast.Ident { 535 var found *ast.Ident 536 ast.Inspect(f, func(n ast.Node) bool { 537 if id, ok := n.(*ast.Ident); ok { 538 idpos := fset.Position(id.Pos()).Offset 539 if idpos <= offset && offset < idpos+len(id.Name) { 540 found = id 541 } 542 } 543 return found == nil // keep traversing only until found 544 }) 545 return found 546 } 547 548 // ambiguityError returns an error describing an ambiguous "*" scope query. 549 func ambiguityError(fset *token.FileSet, objects []types.Object) error { 550 var buf bytes.Buffer 551 for i, obj := range objects { 552 if i > 0 { 553 buf.WriteString(", ") 554 } 555 posn := fset.Position(obj.Pos()) 556 fmt.Fprintf(&buf, "%s at %s:%d", 557 objectKind(obj), filepath.Base(posn.Filename), posn.Column) 558 } 559 return fmt.Errorf("ambiguous specifier %s matches %s", 560 objects[0].Name(), buf.String()) 561 }