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  }