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