569 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			569 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			Go
		
	
	
	
| // Copyright 2014 The Go Authors. All rights reserved.
 | |
| // Use of this source code is governed by a BSD-style
 | |
| // license that can be found in the LICENSE file.
 | |
| 
 | |
| // +build go1.5
 | |
| 
 | |
| package rename
 | |
| 
 | |
| // This file contains logic related to specifying a renaming: parsing of
 | |
| // the flags as a form of query, and finding the object(s) it denotes.
 | |
| // See Usage for flag details.
 | |
| 
 | |
| import (
 | |
| 	"bytes"
 | |
| 	"fmt"
 | |
| 	"go/ast"
 | |
| 	"go/build"
 | |
| 	"go/parser"
 | |
| 	"go/token"
 | |
| 	"go/types"
 | |
| 	"log"
 | |
| 	"os"
 | |
| 	"path/filepath"
 | |
| 	"strconv"
 | |
| 	"strings"
 | |
| 
 | |
| 	"golang.org/x/tools/go/buildutil"
 | |
| 	"golang.org/x/tools/go/loader"
 | |
| )
 | |
| 
 | |
| // A spec specifies an entity to rename.
 | |
| //
 | |
| // It is populated from an -offset flag or -from query;
 | |
| // see Usage for the allowed -from query forms.
 | |
| //
 | |
| type spec struct {
 | |
| 	// pkg is the package containing the position
 | |
| 	// specified by the -from or -offset flag.
 | |
| 	// If filename == "", our search for the 'from' entity
 | |
| 	// is restricted to this package.
 | |
| 	pkg string
 | |
| 
 | |
| 	// The original name of the entity being renamed.
 | |
| 	// If the query had a ::from component, this is that;
 | |
| 	// otherwise it's the last segment, e.g.
 | |
| 	//   (encoding/json.Decoder).from
 | |
| 	//   encoding/json.from
 | |
| 	fromName string
 | |
| 
 | |
| 	// -- The remaining fields are private to this file.  All are optional. --
 | |
| 
 | |
| 	// The query's ::x suffix, if any.
 | |
| 	searchFor string
 | |
| 
 | |
| 	// e.g. "Decoder" in "(encoding/json.Decoder).fieldOrMethod"
 | |
| 	//                or "encoding/json.Decoder
 | |
| 	pkgMember string
 | |
| 
 | |
| 	// e.g. fieldOrMethod in "(encoding/json.Decoder).fieldOrMethod"
 | |
| 	typeMember string
 | |
| 
 | |
| 	// Restricts the query to this file.
 | |
| 	// Implied by -from="file.go::x" and -offset flags.
 | |
| 	filename string
 | |
| 
 | |
| 	// Byte offset of the 'from' identifier within the file named 'filename'.
 | |
| 	// -offset mode only.
 | |
| 	offset int
 | |
| }
 | |
| 
 | |
| // parseFromFlag interprets the "-from" flag value as a renaming specification.
 | |
| // See Usage in rename.go for valid formats.
 | |
| func parseFromFlag(ctxt *build.Context, fromFlag string) (*spec, error) {
 | |
| 	var spec spec
 | |
| 	var main string // sans "::x" suffix
 | |
| 	switch parts := strings.Split(fromFlag, "::"); len(parts) {
 | |
| 	case 1:
 | |
| 		main = parts[0]
 | |
| 	case 2:
 | |
| 		main = parts[0]
 | |
| 		spec.searchFor = parts[1]
 | |
| 		if parts[1] == "" {
 | |
| 			// error
 | |
| 		}
 | |
| 	default:
 | |
| 		return nil, fmt.Errorf("-from %q: invalid identifier specification (see -help for formats)", fromFlag)
 | |
| 	}
 | |
| 
 | |
| 	if strings.HasSuffix(main, ".go") {
 | |
| 		// main is "filename.go"
 | |
| 		if spec.searchFor == "" {
 | |
| 			return nil, fmt.Errorf("-from: filename %q must have a ::name suffix", main)
 | |
| 		}
 | |
| 		spec.filename = main
 | |
| 		if !buildutil.FileExists(ctxt, spec.filename) {
 | |
| 			return nil, fmt.Errorf("no such file: %s", spec.filename)
 | |
| 		}
 | |
| 
 | |
| 		bp, err := buildutil.ContainingPackage(ctxt, wd, spec.filename)
 | |
| 		if err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 		spec.pkg = bp.ImportPath
 | |
| 
 | |
| 	} else {
 | |
| 		// main is one of:
 | |
| 		//  "importpath"
 | |
| 		//  "importpath".member
 | |
| 		//  (*"importpath".type).fieldormethod           (parens and star optional)
 | |
| 		if err := parseObjectSpec(&spec, main); err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if spec.searchFor != "" {
 | |
| 		spec.fromName = spec.searchFor
 | |
| 	}
 | |
| 
 | |
| 	cwd, err := os.Getwd()
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	// Sanitize the package.
 | |
| 	bp, err := ctxt.Import(spec.pkg, cwd, build.FindOnly)
 | |
| 	if err != nil {
 | |
| 		return nil, fmt.Errorf("can't find package %q", spec.pkg)
 | |
| 	}
 | |
| 	spec.pkg = bp.ImportPath
 | |
| 
 | |
| 	if !isValidIdentifier(spec.fromName) {
 | |
| 		return nil, fmt.Errorf("-from: invalid identifier %q", spec.fromName)
 | |
| 	}
 | |
| 
 | |
| 	if Verbose {
 | |
| 		log.Printf("-from spec: %+v", spec)
 | |
| 	}
 | |
| 
 | |
| 	return &spec, nil
 | |
| }
 | |
| 
 | |
| // parseObjectSpec parses main as one of the non-filename forms of
 | |
| // object specification.
 | |
| func parseObjectSpec(spec *spec, main string) error {
 | |
| 	// Parse main as a Go expression, albeit a strange one.
 | |
| 	e, _ := parser.ParseExpr(main)
 | |
| 
 | |
| 	if pkg := parseImportPath(e); pkg != "" {
 | |
| 		// e.g. bytes or "encoding/json": a package
 | |
| 		spec.pkg = pkg
 | |
| 		if spec.searchFor == "" {
 | |
| 			return fmt.Errorf("-from %q: package import path %q must have a ::name suffix",
 | |
| 				main, main)
 | |
| 		}
 | |
| 		return nil
 | |
| 	}
 | |
| 
 | |
| 	if e, ok := e.(*ast.SelectorExpr); ok {
 | |
| 		x := unparen(e.X)
 | |
| 
 | |
| 		// Strip off star constructor, if any.
 | |
| 		if star, ok := x.(*ast.StarExpr); ok {
 | |
| 			x = star.X
 | |
| 		}
 | |
| 
 | |
| 		if pkg := parseImportPath(x); pkg != "" {
 | |
| 			// package member e.g. "encoding/json".HTMLEscape
 | |
| 			spec.pkg = pkg              // e.g. "encoding/json"
 | |
| 			spec.pkgMember = e.Sel.Name // e.g. "HTMLEscape"
 | |
| 			spec.fromName = e.Sel.Name
 | |
| 			return nil
 | |
| 		}
 | |
| 
 | |
| 		if x, ok := x.(*ast.SelectorExpr); ok {
 | |
| 			// field/method of type e.g. ("encoding/json".Decoder).Decode
 | |
| 			y := unparen(x.X)
 | |
| 			if pkg := parseImportPath(y); pkg != "" {
 | |
| 				spec.pkg = pkg               // e.g. "encoding/json"
 | |
| 				spec.pkgMember = x.Sel.Name  // e.g. "Decoder"
 | |
| 				spec.typeMember = e.Sel.Name // e.g. "Decode"
 | |
| 				spec.fromName = e.Sel.Name
 | |
| 				return nil
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return fmt.Errorf("-from %q: invalid expression", main)
 | |
| }
 | |
| 
 | |
| // parseImportPath returns the import path of the package denoted by e.
 | |
| // Any import path may be represented as a string literal;
 | |
| // single-segment import paths (e.g. "bytes") may also be represented as
 | |
| // ast.Ident.  parseImportPath returns "" for all other expressions.
 | |
| func parseImportPath(e ast.Expr) string {
 | |
| 	switch e := e.(type) {
 | |
| 	case *ast.Ident:
 | |
| 		return e.Name // e.g. bytes
 | |
| 
 | |
| 	case *ast.BasicLit:
 | |
| 		if e.Kind == token.STRING {
 | |
| 			pkgname, _ := strconv.Unquote(e.Value)
 | |
| 			return pkgname // e.g. "encoding/json"
 | |
| 		}
 | |
| 	}
 | |
| 	return ""
 | |
| }
 | |
| 
 | |
| // parseOffsetFlag interprets the "-offset" flag value as a renaming specification.
 | |
| func parseOffsetFlag(ctxt *build.Context, offsetFlag string) (*spec, error) {
 | |
| 	var spec spec
 | |
| 	// Validate -offset, e.g. file.go:#123
 | |
| 	parts := strings.Split(offsetFlag, ":#")
 | |
| 	if len(parts) != 2 {
 | |
| 		return nil, fmt.Errorf("-offset %q: invalid offset specification", offsetFlag)
 | |
| 	}
 | |
| 
 | |
| 	spec.filename = parts[0]
 | |
| 	if !buildutil.FileExists(ctxt, spec.filename) {
 | |
| 		return nil, fmt.Errorf("no such file: %s", spec.filename)
 | |
| 	}
 | |
| 
 | |
| 	bp, err := buildutil.ContainingPackage(ctxt, wd, spec.filename)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	spec.pkg = bp.ImportPath
 | |
| 
 | |
| 	for _, r := range parts[1] {
 | |
| 		if !isDigit(r) {
 | |
| 			return nil, fmt.Errorf("-offset %q: non-numeric offset", offsetFlag)
 | |
| 		}
 | |
| 	}
 | |
| 	spec.offset, err = strconv.Atoi(parts[1])
 | |
| 	if err != nil {
 | |
| 		return nil, fmt.Errorf("-offset %q: non-numeric offset", offsetFlag)
 | |
| 	}
 | |
| 
 | |
| 	// Parse the file and check there's an identifier at that offset.
 | |
| 	fset := token.NewFileSet()
 | |
| 	f, err := buildutil.ParseFile(fset, ctxt, nil, wd, spec.filename, parser.ParseComments)
 | |
| 	if err != nil {
 | |
| 		return nil, fmt.Errorf("-offset %q: cannot parse file: %s", offsetFlag, err)
 | |
| 	}
 | |
| 
 | |
| 	id := identAtOffset(fset, f, spec.offset)
 | |
| 	if id == nil {
 | |
| 		return nil, fmt.Errorf("-offset %q: no identifier at this position", offsetFlag)
 | |
| 	}
 | |
| 
 | |
| 	spec.fromName = id.Name
 | |
| 
 | |
| 	return &spec, nil
 | |
| }
 | |
| 
 | |
| var wd = func() string {
 | |
| 	wd, err := os.Getwd()
 | |
| 	if err != nil {
 | |
| 		panic("cannot get working directory: " + err.Error())
 | |
| 	}
 | |
| 	return wd
 | |
| }()
 | |
| 
 | |
| // For source trees built with 'go build', the -from or -offset
 | |
| // spec identifies exactly one initial 'from' object to rename ,
 | |
| // but certain proprietary build systems allow a single file to
 | |
| // appear in multiple packages (e.g. the test package contains a
 | |
| // copy of its library), so there may be multiple objects for
 | |
| // the same source entity.
 | |
| 
 | |
| func findFromObjects(iprog *loader.Program, spec *spec) ([]types.Object, error) {
 | |
| 	if spec.filename != "" {
 | |
| 		return findFromObjectsInFile(iprog, spec)
 | |
| 	}
 | |
| 
 | |
| 	// Search for objects defined in specified package.
 | |
| 
 | |
| 	// TODO(adonovan): the iprog.ImportMap has an entry {"main": ...}
 | |
| 	// for main packages, even though that's not an import path.
 | |
| 	// Seems like a bug.
 | |
| 	//
 | |
| 	// pkg := iprog.ImportMap[spec.pkg]
 | |
| 	// if pkg == nil {
 | |
| 	// 	return fmt.Errorf("cannot find package %s", spec.pkg) // can't happen?
 | |
| 	// }
 | |
| 	// info := iprog.AllPackages[pkg]
 | |
| 
 | |
| 	// Workaround: lookup by value.
 | |
| 	var info *loader.PackageInfo
 | |
| 	var pkg *types.Package
 | |
| 	for pkg, info = range iprog.AllPackages {
 | |
| 		if pkg.Path() == spec.pkg {
 | |
| 			break
 | |
| 		}
 | |
| 	}
 | |
| 	if info == nil {
 | |
| 		return nil, fmt.Errorf("package %q was not loaded", spec.pkg)
 | |
| 	}
 | |
| 
 | |
| 	objects, err := findObjects(info, spec)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	if len(objects) > 1 {
 | |
| 		// ambiguous "*" scope query
 | |
| 		return nil, ambiguityError(iprog.Fset, objects)
 | |
| 	}
 | |
| 	return objects, nil
 | |
| }
 | |
| 
 | |
| func findFromObjectsInFile(iprog *loader.Program, spec *spec) ([]types.Object, error) {
 | |
| 	var fromObjects []types.Object
 | |
| 	for _, info := range iprog.AllPackages {
 | |
| 		// restrict to specified filename
 | |
| 		// NB: under certain proprietary build systems, a given
 | |
| 		// filename may appear in multiple packages.
 | |
| 		for _, f := range info.Files {
 | |
| 			thisFile := iprog.Fset.File(f.Pos())
 | |
| 			if !sameFile(thisFile.Name(), spec.filename) {
 | |
| 				continue
 | |
| 			}
 | |
| 			// This package contains the query file.
 | |
| 
 | |
| 			if spec.offset != 0 {
 | |
| 				// Search for a specific ident by file/offset.
 | |
| 				id := identAtOffset(iprog.Fset, f, spec.offset)
 | |
| 				if id == nil {
 | |
| 					// can't happen?
 | |
| 					return nil, fmt.Errorf("identifier not found")
 | |
| 				}
 | |
| 				obj := info.Uses[id]
 | |
| 				if obj == nil {
 | |
| 					obj = info.Defs[id]
 | |
| 					if obj == nil {
 | |
| 						// Ident without Object.
 | |
| 
 | |
| 						// Package clause?
 | |
| 						pos := thisFile.Pos(spec.offset)
 | |
| 						_, path, _ := iprog.PathEnclosingInterval(pos, pos)
 | |
| 						if len(path) == 2 { // [Ident File]
 | |
| 							// TODO(adonovan): support this case.
 | |
| 							return nil, fmt.Errorf("cannot rename %q: renaming package clauses is not yet supported",
 | |
| 								path[1].(*ast.File).Name.Name)
 | |
| 						}
 | |
| 
 | |
| 						// Implicit y in "switch y := x.(type) {"?
 | |
| 						if obj := typeSwitchVar(&info.Info, path); obj != nil {
 | |
| 							return []types.Object{obj}, nil
 | |
| 						}
 | |
| 
 | |
| 						// Probably a type error.
 | |
| 						return nil, fmt.Errorf("cannot find object for %q", id.Name)
 | |
| 					}
 | |
| 				}
 | |
| 				if obj.Pkg() == nil {
 | |
| 					return nil, fmt.Errorf("cannot rename predeclared identifiers (%s)", obj)
 | |
| 
 | |
| 				}
 | |
| 
 | |
| 				fromObjects = append(fromObjects, obj)
 | |
| 			} else {
 | |
| 				// do a package-wide query
 | |
| 				objects, err := findObjects(info, spec)
 | |
| 				if err != nil {
 | |
| 					return nil, err
 | |
| 				}
 | |
| 
 | |
| 				// filter results: only objects defined in thisFile
 | |
| 				var filtered []types.Object
 | |
| 				for _, obj := range objects {
 | |
| 					if iprog.Fset.File(obj.Pos()) == thisFile {
 | |
| 						filtered = append(filtered, obj)
 | |
| 					}
 | |
| 				}
 | |
| 				if len(filtered) == 0 {
 | |
| 					return nil, fmt.Errorf("no object %q declared in file %s",
 | |
| 						spec.fromName, spec.filename)
 | |
| 				} else if len(filtered) > 1 {
 | |
| 					return nil, ambiguityError(iprog.Fset, filtered)
 | |
| 				}
 | |
| 				fromObjects = append(fromObjects, filtered[0])
 | |
| 			}
 | |
| 			break
 | |
| 		}
 | |
| 	}
 | |
| 	if len(fromObjects) == 0 {
 | |
| 		// can't happen?
 | |
| 		return nil, fmt.Errorf("file %s was not part of the loaded program", spec.filename)
 | |
| 	}
 | |
| 	return fromObjects, nil
 | |
| }
 | |
| 
 | |
| func typeSwitchVar(info *types.Info, path []ast.Node) types.Object {
 | |
| 	if len(path) > 3 {
 | |
| 		// [Ident AssignStmt TypeSwitchStmt...]
 | |
| 		if sw, ok := path[2].(*ast.TypeSwitchStmt); ok {
 | |
| 			// choose the first case.
 | |
| 			if len(sw.Body.List) > 0 {
 | |
| 				obj := info.Implicits[sw.Body.List[0].(*ast.CaseClause)]
 | |
| 				if obj != nil {
 | |
| 					return obj
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // On success, findObjects returns the list of objects named
 | |
| // spec.fromName matching the spec.  On success, the result has exactly
 | |
| // one element unless spec.searchFor!="", in which case it has at least one
 | |
| // element.
 | |
| //
 | |
| func findObjects(info *loader.PackageInfo, spec *spec) ([]types.Object, error) {
 | |
| 	if spec.pkgMember == "" {
 | |
| 		if spec.searchFor == "" {
 | |
| 			panic(spec)
 | |
| 		}
 | |
| 		objects := searchDefs(&info.Info, spec.searchFor)
 | |
| 		if objects == nil {
 | |
| 			return nil, fmt.Errorf("no object %q declared in package %q",
 | |
| 				spec.searchFor, info.Pkg.Path())
 | |
| 		}
 | |
| 		return objects, nil
 | |
| 	}
 | |
| 
 | |
| 	pkgMember := info.Pkg.Scope().Lookup(spec.pkgMember)
 | |
| 	if pkgMember == nil {
 | |
| 		return nil, fmt.Errorf("package %q has no member %q",
 | |
| 			info.Pkg.Path(), spec.pkgMember)
 | |
| 	}
 | |
| 
 | |
| 	var searchFunc *types.Func
 | |
| 	if spec.typeMember == "" {
 | |
| 		// package member
 | |
| 		if spec.searchFor == "" {
 | |
| 			return []types.Object{pkgMember}, nil
 | |
| 		}
 | |
| 
 | |
| 		// Search within pkgMember, which must be a function.
 | |
| 		searchFunc, _ = pkgMember.(*types.Func)
 | |
| 		if searchFunc == nil {
 | |
| 			return nil, fmt.Errorf("cannot search for %q within %s %q",
 | |
| 				spec.searchFor, objectKind(pkgMember), pkgMember)
 | |
| 		}
 | |
| 	} else {
 | |
| 		// field/method of type
 | |
| 		// e.g. (encoding/json.Decoder).Decode
 | |
| 		// or ::x within it.
 | |
| 
 | |
| 		tName, _ := pkgMember.(*types.TypeName)
 | |
| 		if tName == nil {
 | |
| 			return nil, fmt.Errorf("%s.%s is a %s, not a type",
 | |
| 				info.Pkg.Path(), pkgMember.Name(), objectKind(pkgMember))
 | |
| 		}
 | |
| 
 | |
| 		// search within named type.
 | |
| 		obj, _, _ := types.LookupFieldOrMethod(tName.Type(), true, info.Pkg, spec.typeMember)
 | |
| 		if obj == nil {
 | |
| 			return nil, fmt.Errorf("cannot find field or method %q of %s %s.%s",
 | |
| 				spec.typeMember, typeKind(tName.Type()), info.Pkg.Path(), tName.Name())
 | |
| 		}
 | |
| 
 | |
| 		if spec.searchFor == "" {
 | |
| 			// If it is an embedded field, return the type of the field.
 | |
| 			if v, ok := obj.(*types.Var); ok && v.Anonymous() {
 | |
| 				switch t := v.Type().(type) {
 | |
| 				case *types.Pointer:
 | |
| 					return []types.Object{t.Elem().(*types.Named).Obj()}, nil
 | |
| 				case *types.Named:
 | |
| 					return []types.Object{t.Obj()}, nil
 | |
| 				}
 | |
| 			}
 | |
| 			return []types.Object{obj}, nil
 | |
| 		}
 | |
| 
 | |
| 		searchFunc, _ = obj.(*types.Func)
 | |
| 		if searchFunc == nil {
 | |
| 			return nil, fmt.Errorf("cannot search for local name %q within %s (%s.%s).%s; need a function",
 | |
| 				spec.searchFor, objectKind(obj), info.Pkg.Path(), tName.Name(),
 | |
| 				obj.Name())
 | |
| 		}
 | |
| 		if isInterface(tName.Type()) {
 | |
| 			return nil, fmt.Errorf("cannot search for local name %q within abstract method (%s.%s).%s",
 | |
| 				spec.searchFor, info.Pkg.Path(), tName.Name(), searchFunc.Name())
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	// -- search within function or method --
 | |
| 
 | |
| 	decl := funcDecl(info, searchFunc)
 | |
| 	if decl == nil {
 | |
| 		return nil, fmt.Errorf("cannot find syntax for %s", searchFunc) // can't happen?
 | |
| 	}
 | |
| 
 | |
| 	var objects []types.Object
 | |
| 	for _, obj := range searchDefs(&info.Info, spec.searchFor) {
 | |
| 		// We use positions, not scopes, to determine whether
 | |
| 		// the obj is within searchFunc.  This is clumsy, but the
 | |
| 		// alternative, using the types.Scope tree, doesn't
 | |
| 		// account for non-lexical objects like fields and
 | |
| 		// interface methods.
 | |
| 		if decl.Pos() <= obj.Pos() && obj.Pos() < decl.End() && obj != searchFunc {
 | |
| 			objects = append(objects, obj)
 | |
| 		}
 | |
| 	}
 | |
| 	if objects == nil {
 | |
| 		return nil, fmt.Errorf("no local definition of %q within %s",
 | |
| 			spec.searchFor, searchFunc)
 | |
| 	}
 | |
| 	return objects, nil
 | |
| }
 | |
| 
 | |
| func funcDecl(info *loader.PackageInfo, fn *types.Func) *ast.FuncDecl {
 | |
| 	for _, f := range info.Files {
 | |
| 		for _, d := range f.Decls {
 | |
| 			if d, ok := d.(*ast.FuncDecl); ok && info.Defs[d.Name] == fn {
 | |
| 				return d
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func searchDefs(info *types.Info, name string) []types.Object {
 | |
| 	var objects []types.Object
 | |
| 	for id, obj := range info.Defs {
 | |
| 		if obj == nil {
 | |
| 			// e.g. blank ident.
 | |
| 			// TODO(adonovan): but also implicit y in
 | |
| 			//    switch y := x.(type)
 | |
| 			// Needs some thought.
 | |
| 			continue
 | |
| 		}
 | |
| 		if id.Name == name {
 | |
| 			objects = append(objects, obj)
 | |
| 		}
 | |
| 	}
 | |
| 	return objects
 | |
| }
 | |
| 
 | |
| func identAtOffset(fset *token.FileSet, f *ast.File, offset int) *ast.Ident {
 | |
| 	var found *ast.Ident
 | |
| 	ast.Inspect(f, func(n ast.Node) bool {
 | |
| 		if id, ok := n.(*ast.Ident); ok {
 | |
| 			idpos := fset.Position(id.Pos()).Offset
 | |
| 			if idpos <= offset && offset < idpos+len(id.Name) {
 | |
| 				found = id
 | |
| 			}
 | |
| 		}
 | |
| 		return found == nil // keep traversing only until found
 | |
| 	})
 | |
| 	return found
 | |
| }
 | |
| 
 | |
| // ambiguityError returns an error describing an ambiguous "*" scope query.
 | |
| func ambiguityError(fset *token.FileSet, objects []types.Object) error {
 | |
| 	var buf bytes.Buffer
 | |
| 	for i, obj := range objects {
 | |
| 		if i > 0 {
 | |
| 			buf.WriteString(", ")
 | |
| 		}
 | |
| 		posn := fset.Position(obj.Pos())
 | |
| 		fmt.Fprintf(&buf, "%s at %s:%d",
 | |
| 			objectKind(obj), filepath.Base(posn.Filename), posn.Column)
 | |
| 	}
 | |
| 	return fmt.Errorf("ambiguous specifier %s matches %s",
 | |
| 		objects[0].Name(), buf.String())
 | |
| }
 |