blob: eb1fdce622f1ae0a7bc9173101e3048745ab2dad [file] [log] [blame]
Robert Findleyb15dac22022-08-30 14:40:12 -04001// Copyright 2019 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 source
6
Alan Donovan64f9d622023-01-30 12:32:11 -05007// TODO(adonovan):
8//
9// - method of generic concrete type -> arbitrary instances of same
10//
11// - make satisfy work across packages.
12//
13// - tests, tests, tests:
14// - play with renamings in the k8s tree.
15// - generics
16// - error cases (e.g. conflicts)
17// - renaming a symbol declared in the module cache
18// (currently proceeds with half of the renaming!)
19// - make sure all tests have both a local and a cross-package analogue.
20// - look at coverage
21// - special cases: embedded fields, interfaces, test variants,
22// function-local things with uppercase names;
23// packages with type errors (currently 'satisfy' rejects them),
Alan Donovanb71392a2023-06-13 11:47:20 -040024// package with missing imports;
Alan Donovan64f9d622023-01-30 12:32:11 -050025//
26// - measure performance in k8s.
27//
28// - The original gorename tool assumed well-typedness, but the gopls feature
29// does no such check (which actually makes it much more useful).
30// Audit to ensure it is safe on ill-typed code.
31//
32// - Generics support was no doubt buggy before but incrementalization
33// may have exacerbated it. If the problem were just about objects,
34// defs and uses it would be fairly simple, but type assignability
35// comes into play in the 'satisfy' check for method renamings.
36// De-instantiating Vector[int] to Vector[T] changes its type.
37// We need to come up with a theory for the satisfy check that
38// works with generics, and across packages. We currently have no
39// simple way to pass types between packages (think: objectpath for
40// types), though presumably exportdata could be pressed into service.
41//
42// - FileID-based de-duplication of edits to different URIs for the same file.
43
Robert Findleyb15dac22022-08-30 14:40:12 -040044import (
Robert Findleyb15dac22022-08-30 14:40:12 -040045 "context"
46 "errors"
47 "fmt"
48 "go/ast"
Robert Findleyb15dac22022-08-30 14:40:12 -040049 "go/token"
50 "go/types"
Dylan Le40dabfa2022-08-03 14:45:51 -040051 "path"
Dung Le1a08d012022-12-27 23:06:42 +070052 "path/filepath"
Robert Findleyb15dac22022-08-30 14:40:12 -040053 "regexp"
Dylan Le40dabfa2022-08-03 14:45:51 -040054 "sort"
55 "strconv"
Robert Findleyb15dac22022-08-30 14:40:12 -040056 "strings"
57
Dung Le1a08d012022-12-27 23:06:42 +070058 "golang.org/x/mod/modfile"
Robert Findleyb15a5bc2023-01-31 17:38:50 -050059 "golang.org/x/tools/go/ast/astutil"
Alan Donovan64f9d622023-01-30 12:32:11 -050060 "golang.org/x/tools/go/types/objectpath"
Robert Findleyb15dac22022-08-30 14:40:12 -040061 "golang.org/x/tools/go/types/typeutil"
Alan Donovan4baa3dc2023-04-25 10:21:06 -040062 "golang.org/x/tools/gopls/internal/bug"
Robert Findleyb15dac22022-08-30 14:40:12 -040063 "golang.org/x/tools/gopls/internal/lsp/protocol"
Alan Donovand96b2382022-09-30 21:58:21 -040064 "golang.org/x/tools/gopls/internal/lsp/safetoken"
Alan Donovan26a95e62022-10-07 10:40:32 -040065 "golang.org/x/tools/gopls/internal/span"
Alan Donovan6782af02022-09-19 15:18:43 -040066 "golang.org/x/tools/internal/diff"
67 "golang.org/x/tools/internal/event"
Alan Donovan64f9d622023-01-30 12:32:11 -050068 "golang.org/x/tools/internal/typeparams"
Robert Findleyb15dac22022-08-30 14:40:12 -040069 "golang.org/x/tools/refactor/satisfy"
70)
71
Alan Donovan64f9d622023-01-30 12:32:11 -050072// A renamer holds state of a single call to renameObj, which renames
73// an object (or several coupled objects) within a single type-checked
74// syntax package.
Robert Findleyb15dac22022-08-30 14:40:12 -040075type renamer struct {
Alan Donovan64f9d622023-01-30 12:32:11 -050076 pkg Package // the syntax package in which the renaming is applied
77 objsToUpdate map[types.Object]bool // records progress of calls to check
78 hadConflicts bool
Alan Donovan80afb092023-02-07 15:42:30 -050079 conflicts []string
Robert Findleyb15dac22022-08-30 14:40:12 -040080 from, to string
81 satisfyConstraints map[satisfy.Constraint]bool
Robert Findleyb15dac22022-08-30 14:40:12 -040082 msets typeutil.MethodSetCache
83 changeMethods bool
84}
85
Alan Donovan64f9d622023-01-30 12:32:11 -050086// A PrepareItem holds the result of a "prepare rename" operation:
87// the source range and value of a selected identifier.
Robert Findleyb15dac22022-08-30 14:40:12 -040088type PrepareItem struct {
89 Range protocol.Range
90 Text string
91}
92
93// PrepareRename searches for a valid renaming at position pp.
94//
95// The returned usererr is intended to be displayed to the user to explain why
96// the prepare fails. Probably we could eliminate the redundancy in returning
97// two errors, but for now this is done defensively.
98func PrepareRename(ctx context.Context, snapshot Snapshot, f FileHandle, pp protocol.Position) (_ *PrepareItem, usererr, err error) {
Dylan Le40dabfa2022-08-03 14:45:51 -040099 ctx, done := event.Start(ctx, "source.PrepareRename")
100 defer done()
Robert Findleyb15dac22022-08-30 14:40:12 -0400101
Alan Donovand7dfffd2022-12-08 00:37:50 -0500102 // Is the cursor within the package name declaration?
Alan Donovan1aa7e722023-01-27 11:02:04 -0500103 if pgf, inPackageName, err := parsePackageNameDecl(ctx, snapshot, f, pp); err != nil {
Alan Donovand7dfffd2022-12-08 00:37:50 -0500104 return nil, err, err
Alan Donovan1aa7e722023-01-27 11:02:04 -0500105 } else if inPackageName {
106 item, err := prepareRenamePackageName(ctx, snapshot, pgf)
107 return item, err, err
Dylan Le40dabfa2022-08-03 14:45:51 -0400108 }
Robert Findleyb15dac22022-08-30 14:40:12 -0400109
Alan Donovan1aa7e722023-01-27 11:02:04 -0500110 // Ordinary (non-package) renaming.
111 //
112 // Type-check the current package, locate the reference at the position,
113 // validate the object, and report its name and range.
114 //
115 // TODO(adonovan): in all cases below, we return usererr=nil,
116 // which means we return (nil, nil) at the protocol
117 // layer. This seems like a bug, or at best an exploitation of
118 // knowledge of VSCode-specific behavior. Can we avoid that?
Alan Donovanb35949e2023-04-20 14:53:41 -0400119 pkg, pgf, err := NarrowestPackageForFile(ctx, snapshot, f.URI())
Robert Findleyb15dac22022-08-30 14:40:12 -0400120 if err != nil {
Alan Donovan1aa7e722023-01-27 11:02:04 -0500121 return nil, nil, err
Robert Findleyb15dac22022-08-30 14:40:12 -0400122 }
Alan Donovan1aa7e722023-01-27 11:02:04 -0500123 pos, err := pgf.PositionPos(pp)
124 if err != nil {
125 return nil, nil, err
126 }
127 targets, node, err := objectsAt(pkg.GetTypesInfo(), pgf.File, pos)
128 if err != nil {
129 return nil, nil, err
130 }
131 var obj types.Object
132 for obj = range targets {
133 break // pick one arbitrarily
134 }
Robert Findleyb15dac22022-08-30 14:40:12 -0400135 if err := checkRenamable(obj); err != nil {
Alan Donovan1aa7e722023-01-27 11:02:04 -0500136 return nil, nil, err
Robert Findleyb15dac22022-08-30 14:40:12 -0400137 }
Alan Donovan1aa7e722023-01-27 11:02:04 -0500138 rng, err := pgf.NodeRange(node)
Robert Findleyb15dac22022-08-30 14:40:12 -0400139 if err != nil {
Alan Donovan1aa7e722023-01-27 11:02:04 -0500140 return nil, nil, err
Robert Findleyb15dac22022-08-30 14:40:12 -0400141 }
Robert Findleyb15dac22022-08-30 14:40:12 -0400142 if _, isImport := node.(*ast.ImportSpec); isImport {
143 // We're not really renaming the import path.
144 rng.End = rng.Start
145 }
146 return &PrepareItem{
147 Range: rng,
Alan Donovan1aa7e722023-01-27 11:02:04 -0500148 Text: obj.Name(),
149 }, nil, nil
150}
151
152func prepareRenamePackageName(ctx context.Context, snapshot Snapshot, pgf *ParsedGoFile) (*PrepareItem, error) {
153 // Does the client support file renaming?
154 fileRenameSupported := false
155 for _, op := range snapshot.View().Options().SupportedResourceOperations {
156 if op == protocol.Rename {
157 fileRenameSupported = true
158 break
159 }
160 }
161 if !fileRenameSupported {
162 return nil, errors.New("can't rename package: LSP client does not support file renaming")
163 }
164
165 // Check validity of the metadata for the file's containing package.
Alan Donovanb35949e2023-04-20 14:53:41 -0400166 meta, err := NarrowestMetadataForFile(ctx, snapshot, pgf.URI)
Alan Donovan1aa7e722023-01-27 11:02:04 -0500167 if err != nil {
168 return nil, err
169 }
Alan Donovan1aa7e722023-01-27 11:02:04 -0500170 if meta.Name == "main" {
171 return nil, fmt.Errorf("can't rename package \"main\"")
172 }
173 if strings.HasSuffix(string(meta.Name), "_test") {
174 return nil, fmt.Errorf("can't rename x_test packages")
175 }
176 if meta.Module == nil {
177 return nil, fmt.Errorf("can't rename package: missing module information for package %q", meta.PkgPath)
178 }
179 if meta.Module.Path == string(meta.PkgPath) {
180 return nil, fmt.Errorf("can't rename package: package path %q is the same as module path %q", meta.PkgPath, meta.Module.Path)
181 }
182
183 // Return the location of the package declaration.
184 rng, err := pgf.NodeRange(pgf.File.Name)
185 if err != nil {
186 return nil, err
187 }
188 return &PrepareItem{
189 Range: rng,
190 Text: string(meta.Name),
Dylan Le40dabfa2022-08-03 14:45:51 -0400191 }, nil
Robert Findleyb15dac22022-08-30 14:40:12 -0400192}
193
Robert Findleyb15dac22022-08-30 14:40:12 -0400194func checkRenamable(obj types.Object) error {
Alan Donovan64f9d622023-01-30 12:32:11 -0500195 switch obj := obj.(type) {
196 case *types.Var:
197 if obj.Embedded() {
198 return fmt.Errorf("can't rename embedded fields: rename the type directly or name the field")
199 }
200 case *types.Builtin, *types.Nil:
201 return fmt.Errorf("%s is built in and cannot be renamed", obj.Name())
202 }
203 if obj.Pkg() == nil || obj.Pkg().Path() == "unsafe" {
204 // e.g. error.Error, unsafe.Pointer
205 return fmt.Errorf("%s is built in and cannot be renamed", obj.Name())
Robert Findleyb15dac22022-08-30 14:40:12 -0400206 }
207 if obj.Name() == "_" {
208 return errors.New("can't rename \"_\"")
209 }
210 return nil
211}
212
213// Rename returns a map of TextEdits for each file modified when renaming a
Dylan Le40dabfa2022-08-03 14:45:51 -0400214// given identifier within a package and a boolean value of true for renaming
215// package and false otherwise.
Alan Donovan64f9d622023-01-30 12:32:11 -0500216func Rename(ctx context.Context, snapshot Snapshot, f FileHandle, pp protocol.Position, newName string) (map[span.URI][]protocol.TextEdit, bool, error) {
Robert Findleyb15dac22022-08-30 14:40:12 -0400217 ctx, done := event.Start(ctx, "source.Rename")
218 defer done()
219
Alan Donovan5ed33df2023-01-26 22:08:04 -0500220 if !isValidIdentifier(newName) {
221 return nil, false, fmt.Errorf("invalid identifier to rename: %q", newName)
222 }
Alan Donovan64f9d622023-01-30 12:32:11 -0500223
224 // Cursor within package name declaration?
225 _, inPackageName, err := parsePackageNameDecl(ctx, snapshot, f, pp)
Dylan Le40dabfa2022-08-03 14:45:51 -0400226 if err != nil {
227 return nil, false, err
228 }
229
Alan Donovan64f9d622023-01-30 12:32:11 -0500230 var editMap map[span.URI][]diff.Edit
231 if inPackageName {
232 editMap, err = renamePackageName(ctx, snapshot, f, PackageName(newName))
233 } else {
234 editMap, err = renameOrdinary(ctx, snapshot, f, pp, newName)
235 }
236 if err != nil {
237 return nil, false, err
238 }
239
240 // Convert edits to protocol form.
241 result := make(map[span.URI][]protocol.TextEdit)
242 for uri, edits := range editMap {
243 // Sort and de-duplicate edits.
244 //
245 // Overlapping edits may arise in local renamings (due
246 // to type switch implicits) and globals ones (due to
247 // processing multiple package variants).
248 //
249 // We assume renaming produces diffs that are all
250 // replacements (no adjacent insertions that might
251 // become reordered) and that are either identical or
252 // non-overlapping.
253 diff.SortEdits(edits)
254 filtered := edits[:0]
255 for i, edit := range edits {
256 if i == 0 || edit != filtered[len(filtered)-1] {
257 filtered = append(filtered, edit)
258 }
259 }
260 edits = filtered
261
262 // TODO(adonovan): the logic above handles repeat edits to the
263 // same file URI (e.g. as a member of package p and p_test) but
264 // is not sufficient to handle file-system level aliasing arising
265 // from symbolic or hard links. For that, we should use a
266 // robustio-FileID-keyed map.
267 // See https://go.dev/cl/457615 for example.
268 // This really occurs in practice, e.g. kubernetes has
269 // vendor/k8s.io/kubectl -> ../../staging/src/k8s.io/kubectl.
Alan Donovan36ed0b12023-03-13 14:20:23 -0400270 fh, err := snapshot.ReadFile(ctx, uri)
Alan Donovan64f9d622023-01-30 12:32:11 -0500271 if err != nil {
272 return nil, false, err
273 }
Alan Donovan786752b2023-03-07 12:14:28 -0500274 data, err := fh.Content()
Alan Donovan64f9d622023-01-30 12:32:11 -0500275 if err != nil {
276 return nil, false, err
277 }
278 m := protocol.NewMapper(uri, data)
279 protocolEdits, err := ToProtocolEdits(m, edits)
280 if err != nil {
281 return nil, false, err
282 }
283 result[uri] = protocolEdits
284 }
285
286 return result, inPackageName, nil
Dylan Le40dabfa2022-08-03 14:45:51 -0400287}
288
Alan Donovan64f9d622023-01-30 12:32:11 -0500289// renameOrdinary renames an ordinary (non-package) name throughout the workspace.
290func renameOrdinary(ctx context.Context, snapshot Snapshot, f FileHandle, pp protocol.Position, newName string) (map[span.URI][]diff.Edit, error) {
291 // Type-check the referring package and locate the object(s).
Alan Donovanb35949e2023-04-20 14:53:41 -0400292 //
Alan Donovan660614b2023-04-20 16:11:13 -0400293 // Unlike NarrowestPackageForFile, this operation prefers the
294 // widest variant as, for non-exported identifiers, it is the
295 // only package we need. (In case you're wondering why
296 // 'references' doesn't also want the widest variant: it
297 // computes the union across all variants.)
Alan Donovanb35949e2023-04-20 14:53:41 -0400298 var targets map[types.Object]ast.Node
299 var pkg Package
300 {
301 metas, err := snapshot.MetadataForFile(ctx, f.URI())
302 if err != nil {
303 return nil, err
304 }
Alan Donovan660614b2023-04-20 16:11:13 -0400305 RemoveIntermediateTestVariants(&metas)
Alan Donovanb35949e2023-04-20 14:53:41 -0400306 if len(metas) == 0 {
307 return nil, fmt.Errorf("no package metadata for file %s", f.URI())
308 }
309 widest := metas[len(metas)-1] // widest variant may include _test.go files
310 pkgs, err := snapshot.TypeCheck(ctx, widest.ID)
311 if err != nil {
312 return nil, err
313 }
314 pkg = pkgs[0]
315 pgf, err := pkg.File(f.URI())
316 if err != nil {
317 return nil, err // "can't happen"
318 }
319 pos, err := pgf.PositionPos(pp)
320 if err != nil {
321 return nil, err
322 }
323 objects, _, err := objectsAt(pkg.GetTypesInfo(), pgf.File, pos)
324 if err != nil {
325 return nil, err
326 }
327 targets = objects
Alan Donovan5ed33df2023-01-26 22:08:04 -0500328 }
329
Alan Donovan64f9d622023-01-30 12:32:11 -0500330 // Pick a representative object arbitrarily.
331 // (All share the same name, pos, and kind.)
332 var obj types.Object
333 for obj = range targets {
334 break
335 }
336 if obj.Name() == newName {
337 return nil, fmt.Errorf("old and new names are the same: %s", newName)
338 }
339 if err := checkRenamable(obj); err != nil {
340 return nil, err
Alan Donovan5ed33df2023-01-26 22:08:04 -0500341 }
342
Alan Donovan64f9d622023-01-30 12:32:11 -0500343 // Find objectpath, if object is exported ("" otherwise).
344 var declObjPath objectpath.Path
345 if obj.Exported() {
Rob Findley15610602023-07-28 10:34:23 -0400346 // objectpath.For requires the origin of a generic function or type, not an
347 // instantiation (a bug?). Unfortunately we can't call Func.Origin as this
348 // is not available in go/types@go1.18. So we take a scenic route.
349 //
350 // Note that unlike Funcs, TypeNames are always canonical (they are "left"
351 // of the type parameters, unlike methods).
Alan Donovan64f9d622023-01-30 12:32:11 -0500352 switch obj.(type) { // avoid "obj :=" since cases reassign the var
353 case *types.TypeName:
Rob Findley15610602023-07-28 10:34:23 -0400354 if _, ok := obj.Type().(*typeparams.TypeParam); ok {
Rob Findleyfe58b072023-07-28 10:19:12 -0400355 // As with capitalized function parameters below, type parameters are
356 // local.
357 goto skipObjectPath
Alan Donovan64f9d622023-01-30 12:32:11 -0500358 }
359 case *types.Func:
360 obj = funcOrigin(obj.(*types.Func))
361 case *types.Var:
362 // TODO(adonovan): do vars need the origin treatment too? (issue #58462)
Alan Donovanc6de5f52023-07-14 13:06:23 -0400363
364 // Function parameter and result vars that are (unusually)
365 // capitalized are technically exported, even though they
366 // cannot be referenced, because they may affect downstream
367 // error messages. But we can safely treat them as local.
368 //
369 // This is not merely an optimization: the renameExported
370 // operation gets confused by such vars. It finds them from
371 // objectpath, the classifies them as local vars, but as
372 // they came from export data they lack syntax and the
373 // correct scope tree (issue #61294).
374 if !obj.(*types.Var).IsField() && !isPackageLevel(obj) {
375 goto skipObjectPath
376 }
Alan Donovan64f9d622023-01-30 12:32:11 -0500377 }
378 if path, err := objectpath.For(obj); err == nil {
379 declObjPath = path
380 }
Alan Donovanc6de5f52023-07-14 13:06:23 -0400381 skipObjectPath:
Alan Donovan64f9d622023-01-30 12:32:11 -0500382 }
383
384 // Nonexported? Search locally.
385 if declObjPath == "" {
386 var objects []types.Object
387 for obj := range targets {
388 objects = append(objects, obj)
389 }
390 editMap, _, err := renameObjects(ctx, snapshot, newName, pkg, objects...)
391 return editMap, err
392 }
393
394 // Exported: search globally.
Alan Donovan5ed33df2023-01-26 22:08:04 -0500395 //
Alan Donovan64f9d622023-01-30 12:32:11 -0500396 // For exported package-level var/const/func/type objects, the
397 // search scope is just the direct importers.
398 //
399 // For exported fields and methods, the scope is the
400 // transitive rdeps. (The exportedness of the field's struct
401 // or method's receiver is irrelevant.)
402 transitive := false
403 switch obj.(type) {
404 case *types.TypeName:
405 // Renaming an exported package-level type
406 // requires us to inspect all transitive rdeps
407 // in the event that the type is embedded.
408 //
409 // TODO(adonovan): opt: this is conservative
410 // but inefficient. Instead, expand the scope
411 // of the search only if we actually encounter
412 // an embedding of the type, and only then to
413 // the rdeps of the embedding package.
414 if obj.Parent() == obj.Pkg().Scope() {
415 transitive = true
416 }
417
418 case *types.Var:
419 if obj.(*types.Var).IsField() {
420 transitive = true // field
421 }
422
423 // TODO(adonovan): opt: process only packages that
424 // contain a reference (xrefs) to the target field.
425
426 case *types.Func:
427 if obj.Type().(*types.Signature).Recv() != nil {
428 transitive = true // method
429 }
430
431 // It's tempting to optimize by skipping
432 // packages that don't contain a reference to
433 // the method in the xrefs index, but we still
434 // need to apply the satisfy check to those
435 // packages to find assignment statements that
436 // might expands the scope of the renaming.
Alan Donovan5ed33df2023-01-26 22:08:04 -0500437 }
438
Alan Donovan64f9d622023-01-30 12:32:11 -0500439 // Type-check all the packages to inspect.
440 declURI := span.URIFromPath(pkg.FileSet().File(obj.Pos()).Name())
441 pkgs, err := typeCheckReverseDependencies(ctx, snapshot, declURI, transitive)
Alan Donovan5ed33df2023-01-26 22:08:04 -0500442 if err != nil {
Alan Donovan64f9d622023-01-30 12:32:11 -0500443 return nil, err
Alan Donovan5ed33df2023-01-26 22:08:04 -0500444 }
445
Alan Donovan64f9d622023-01-30 12:32:11 -0500446 // Apply the renaming to the (initial) object.
447 declPkgPath := PackagePath(obj.Pkg().Path())
448 return renameExported(ctx, snapshot, pkgs, declPkgPath, declObjPath, newName)
449}
450
451// funcOrigin is a go1.18-portable implementation of (*types.Func).Origin.
452func funcOrigin(fn *types.Func) *types.Func {
453 // Method?
454 if fn.Type().(*types.Signature).Recv() != nil {
455 return typeparams.OriginMethod(fn)
456 }
457
458 // Package-level function?
459 // (Assume the origin has the same position.)
460 gen := fn.Pkg().Scope().Lookup(fn.Name())
461 if gen != nil && gen.Pos() == fn.Pos() {
462 return gen.(*types.Func)
463 }
464
465 return fn
466}
467
468// typeCheckReverseDependencies returns the type-checked packages for
469// the reverse dependencies of all packages variants containing
470// file declURI. The packages are in some topological order.
471//
472// It includes all variants (even intermediate test variants) for the
473// purposes of computing reverse dependencies, but discards ITVs for
474// the actual renaming work.
475//
476// (This neglects obscure edge cases where a _test.go file changes the
477// selectors used only in an ITV, but life is short. Also sin must be
478// punished.)
479func typeCheckReverseDependencies(ctx context.Context, snapshot Snapshot, declURI span.URI, transitive bool) ([]Package, error) {
480 variants, err := snapshot.MetadataForFile(ctx, declURI)
Alan Donovan5ed33df2023-01-26 22:08:04 -0500481 if err != nil {
Alan Donovan64f9d622023-01-30 12:32:11 -0500482 return nil, err
483 }
Alan Donovanb35949e2023-04-20 14:53:41 -0400484 // variants must include ITVs for the reverse dependency
485 // computation, but they are filtered out before we typecheck.
Alan Donovan64f9d622023-01-30 12:32:11 -0500486 allRdeps := make(map[PackageID]*Metadata)
487 for _, variant := range variants {
488 rdeps, err := snapshot.ReverseDependencies(ctx, variant.ID, transitive)
489 if err != nil {
490 return nil, err
491 }
492 allRdeps[variant.ID] = variant // include self
493 for id, meta := range rdeps {
494 allRdeps[id] = meta
495 }
496 }
497 var ids []PackageID
498 for id, meta := range allRdeps {
499 if meta.IsIntermediateTestVariant() {
500 continue
501 }
502 ids = append(ids, id)
Alan Donovan5ed33df2023-01-26 22:08:04 -0500503 }
504
Robert Findley21d22562023-02-21 12:26:27 -0500505 // Sort the packages into some topological order of the
506 // (unfiltered) metadata graph.
507 SortPostOrder(snapshot, ids)
508
Alan Donovan64f9d622023-01-30 12:32:11 -0500509 // Dependencies must be visited first since they can expand
510 // the search set. Ideally we would process the (filtered) set
511 // of packages in the parallel postorder of the snapshot's
512 // (unfiltered) metadata graph, but this is quite tricky
513 // without a good graph abstraction.
514 //
515 // For now, we visit packages sequentially in order of
516 // ascending height, like an inverted breadth-first search.
517 //
518 // Type checking is by far the dominant cost, so
519 // overlapping it with renaming may not be worthwhile.
Robert Findley21d22562023-02-21 12:26:27 -0500520 return snapshot.TypeCheck(ctx, ids...)
521}
Alan Donovan5ed33df2023-01-26 22:08:04 -0500522
Robert Findley21d22562023-02-21 12:26:27 -0500523// SortPostOrder sorts the IDs so that if x depends on y, then y appears before x.
524func SortPostOrder(meta MetadataSource, ids []PackageID) {
Alan Donovan64f9d622023-01-30 12:32:11 -0500525 postorder := make(map[PackageID]int)
Robert Findley21d22562023-02-21 12:26:27 -0500526 order := 0
Alan Donovan64f9d622023-01-30 12:32:11 -0500527 var visit func(PackageID)
528 visit = func(id PackageID) {
529 if _, ok := postorder[id]; !ok {
530 postorder[id] = -1 // break recursion
Robert Findley21d22562023-02-21 12:26:27 -0500531 if m := meta.Metadata(id); m != nil {
Alan Donovan64f9d622023-01-30 12:32:11 -0500532 for _, depID := range m.DepsByPkgPath {
533 visit(depID)
534 }
535 }
Robert Findley21d22562023-02-21 12:26:27 -0500536 order++
537 postorder[id] = order
Alan Donovan64f9d622023-01-30 12:32:11 -0500538 }
539 }
540 for _, id := range ids {
541 visit(id)
542 }
Robert Findley21d22562023-02-21 12:26:27 -0500543 sort.Slice(ids, func(i, j int) bool {
544 return postorder[ids[i]] < postorder[ids[j]]
Alan Donovan64f9d622023-01-30 12:32:11 -0500545 })
Alan Donovan64f9d622023-01-30 12:32:11 -0500546}
547
548// renameExported renames the object denoted by (pkgPath, objPath)
549// within the specified packages, along with any other objects that
550// must be renamed as a consequence. The slice of packages must be
551// topologically ordered.
552func renameExported(ctx context.Context, snapshot Snapshot, pkgs []Package, declPkgPath PackagePath, declObjPath objectpath.Path, newName string) (map[span.URI][]diff.Edit, error) {
553
554 // A target is a name for an object that is stable across types.Packages.
555 type target struct {
556 pkg PackagePath
557 obj objectpath.Path
558 }
559
560 // Populate the initial set of target objects.
561 // This set may grow as we discover the consequences of each renaming.
562 //
563 // TODO(adonovan): strictly, each cone of reverse dependencies
564 // of a single variant should have its own target map that
565 // monotonically expands as we go up the import graph, because
566 // declarations in test files can alter the set of
567 // package-level names and change the meaning of field and
568 // method selectors. So if we parallelize the graph
569 // visitation (see above), we should also compute the targets
570 // as a union of dependencies.
571 //
572 // Or we could decide that the logic below is fast enough not
573 // to need parallelism. In small measurements so far the
574 // type-checking step is about 95% and the renaming only 5%.
575 targets := map[target]bool{{declPkgPath, declObjPath}: true}
576
577 // Apply the renaming operation to each package.
578 allEdits := make(map[span.URI][]diff.Edit)
579 for _, pkg := range pkgs {
580
581 // Resolved target objects within package pkg.
582 var objects []types.Object
583 for t := range targets {
584 p := pkg.DependencyTypes(t.pkg)
585 if p == nil {
586 continue // indirect dependency of no consequence
587 }
588 obj, err := objectpath.Object(p, t.obj)
589 if err != nil {
Alan Donovana8cf6652023-06-30 17:56:46 -0400590 // Possibly a method or an unexported type
591 // that is not reachable through export data?
592 // See https://github.com/golang/go/issues/60789.
593 //
594 // TODO(adonovan): it seems unsatisfactory that Object
595 // should return an error for a "valid" path. Perhaps
596 // we should define such paths as invalid and make
597 // objectpath.For compute reachability?
598 // Would that be a compatible change?
Alan Donovan64f9d622023-01-30 12:32:11 -0500599 continue
600 }
601 objects = append(objects, obj)
602 }
603 if len(objects) == 0 {
604 continue // no targets of consequence to this package
605 }
606
607 // Apply the renaming.
608 editMap, moreObjects, err := renameObjects(ctx, snapshot, newName, pkg, objects...)
609 if err != nil {
610 return nil, err
611 }
612
613 // It is safe to concatenate the edits as they are non-overlapping
614 // (or identical, in which case they will be de-duped by Rename).
615 for uri, edits := range editMap {
616 allEdits[uri] = append(allEdits[uri], edits...)
617 }
618
619 // Expand the search set?
620 for obj := range moreObjects {
621 objpath, err := objectpath.For(obj)
622 if err != nil {
623 continue // not exported
624 }
625 target := target{PackagePath(obj.Pkg().Path()), objpath}
626 targets[target] = true
627
628 // TODO(adonovan): methods requires dynamic
629 // programming of the product targets x
630 // packages as any package might add a new
631 // target (from a foward dep) as a
632 // consequence, and any target might imply a
633 // new set of rdeps. See golang/go#58461.
634 }
635 }
636
637 return allEdits, nil
638}
639
640// renamePackageName renames package declarations, imports, and go.mod files.
641func renamePackageName(ctx context.Context, s Snapshot, f FileHandle, newName PackageName) (map[span.URI][]diff.Edit, error) {
642 // Rename the package decl and all imports.
643 renamingEdits, err := renamePackage(ctx, s, f, newName)
644 if err != nil {
645 return nil, err
646 }
647
648 // Update the last component of the file's enclosing directory.
649 oldBase := filepath.Dir(f.URI().Filename())
650 newPkgDir := filepath.Join(filepath.Dir(oldBase), string(newName))
651
652 // Update any affected replace directives in go.mod files.
653 // TODO(adonovan): extract into its own function.
654 //
Alan Donovane8f417a2023-04-21 14:54:28 -0400655 // Get all workspace modules.
656 // TODO(adonovan): should this operate on all go.mod files,
657 // irrespective of whether they are included in the workspace?
Alan Donovan5ed33df2023-01-26 22:08:04 -0500658 modFiles := s.ModFiles()
659 for _, m := range modFiles {
Alan Donovan36ed0b12023-03-13 14:20:23 -0400660 fh, err := s.ReadFile(ctx, m)
Alan Donovan5ed33df2023-01-26 22:08:04 -0500661 if err != nil {
Alan Donovan64f9d622023-01-30 12:32:11 -0500662 return nil, err
Alan Donovan5ed33df2023-01-26 22:08:04 -0500663 }
664 pm, err := s.ParseMod(ctx, fh)
665 if err != nil {
Alan Donovan64f9d622023-01-30 12:32:11 -0500666 return nil, err
Alan Donovan5ed33df2023-01-26 22:08:04 -0500667 }
668
669 modFileDir := filepath.Dir(pm.URI.Filename())
670 affectedReplaces := []*modfile.Replace{}
671
672 // Check if any replace directives need to be fixed
673 for _, r := range pm.File.Replace {
674 if !strings.HasPrefix(r.New.Path, "/") && !strings.HasPrefix(r.New.Path, "./") && !strings.HasPrefix(r.New.Path, "../") {
675 continue
676 }
677
678 replacedPath := r.New.Path
679 if strings.HasPrefix(r.New.Path, "./") || strings.HasPrefix(r.New.Path, "../") {
680 replacedPath = filepath.Join(modFileDir, r.New.Path)
681 }
682
683 // TODO: Is there a risk of converting a '\' delimited replacement to a '/' delimited replacement?
684 if !strings.HasPrefix(filepath.ToSlash(replacedPath)+"/", filepath.ToSlash(oldBase)+"/") {
685 continue // not affected by the package renaming
686 }
687
688 affectedReplaces = append(affectedReplaces, r)
689 }
690
691 if len(affectedReplaces) == 0 {
692 continue
693 }
694 copied, err := modfile.Parse("", pm.Mapper.Content, nil)
695 if err != nil {
Alan Donovan64f9d622023-01-30 12:32:11 -0500696 return nil, err
Alan Donovan5ed33df2023-01-26 22:08:04 -0500697 }
698
699 for _, r := range affectedReplaces {
700 replacedPath := r.New.Path
701 if strings.HasPrefix(r.New.Path, "./") || strings.HasPrefix(r.New.Path, "../") {
702 replacedPath = filepath.Join(modFileDir, r.New.Path)
703 }
704
705 suffix := strings.TrimPrefix(replacedPath, string(oldBase))
706
707 newReplacedPath, err := filepath.Rel(modFileDir, newPkgDir+suffix)
708 if err != nil {
Alan Donovan64f9d622023-01-30 12:32:11 -0500709 return nil, err
Alan Donovan5ed33df2023-01-26 22:08:04 -0500710 }
711
712 newReplacedPath = filepath.ToSlash(newReplacedPath)
713
714 if !strings.HasPrefix(newReplacedPath, "/") && !strings.HasPrefix(newReplacedPath, "../") {
715 newReplacedPath = "./" + newReplacedPath
716 }
717
718 if err := copied.AddReplace(r.Old.Path, "", newReplacedPath, ""); err != nil {
Alan Donovan64f9d622023-01-30 12:32:11 -0500719 return nil, err
Alan Donovan5ed33df2023-01-26 22:08:04 -0500720 }
721 }
722
723 copied.Cleanup()
724 newContent, err := copied.Format()
725 if err != nil {
Alan Donovan64f9d622023-01-30 12:32:11 -0500726 return nil, err
Alan Donovan5ed33df2023-01-26 22:08:04 -0500727 }
728
729 // Calculate the edits to be made due to the change.
Alan Donovan64f9d622023-01-30 12:32:11 -0500730 edits := s.View().Options().ComputeEdits(string(pm.Mapper.Content), string(newContent))
731 renamingEdits[pm.URI] = append(renamingEdits[pm.URI], edits...)
Alan Donovan5ed33df2023-01-26 22:08:04 -0500732 }
733
Alan Donovan64f9d622023-01-30 12:32:11 -0500734 return renamingEdits, nil
Alan Donovan5ed33df2023-01-26 22:08:04 -0500735}
736
Robert Findley61280302022-10-17 16:35:50 -0400737// renamePackage computes all workspace edits required to rename the package
738// described by the given metadata, to newName, by renaming its package
739// directory.
740//
741// It updates package clauses and import paths for the renamed package as well
Alan Donovan0809ec22023-05-08 17:47:09 -0400742// as any other packages affected by the directory renaming among all packages
743// known to the snapshot.
Alan Donovan64f9d622023-01-30 12:32:11 -0500744func renamePackage(ctx context.Context, s Snapshot, f FileHandle, newName PackageName) (map[span.URI][]diff.Edit, error) {
745 if strings.HasSuffix(string(newName), "_test") {
746 return nil, fmt.Errorf("cannot rename to _test package")
747 }
748
749 // We need metadata for the relevant package and module paths.
750 // These should be the same for all packages containing the file.
Alan Donovanb35949e2023-04-20 14:53:41 -0400751 meta, err := NarrowestMetadataForFile(ctx, s, f.URI())
Alan Donovan64f9d622023-01-30 12:32:11 -0500752 if err != nil {
753 return nil, err
754 }
Alan Donovan64f9d622023-01-30 12:32:11 -0500755
756 oldPkgPath := meta.PkgPath
757 if meta.Module == nil {
758 return nil, fmt.Errorf("cannot rename package: missing module information for package %q", meta.PkgPath)
759 }
760 modulePath := PackagePath(meta.Module.Path)
761 if modulePath == oldPkgPath {
Robert Findley61280302022-10-17 16:35:50 -0400762 return nil, fmt.Errorf("cannot rename package: module path %q is the same as the package path, so renaming the package directory would have no effect", modulePath)
763 }
764
Alan Donovan64f9d622023-01-30 12:32:11 -0500765 newPathPrefix := path.Join(path.Dir(string(oldPkgPath)), string(newName))
Robert Findley61280302022-10-17 16:35:50 -0400766
Alan Donovan64f9d622023-01-30 12:32:11 -0500767 // We must inspect all packages, not just direct importers,
768 // because we also rename subpackages, which may be unrelated.
769 // (If the renamed package imports a subpackage it may require
770 // edits to both its package and import decls.)
771 allMetadata, err := s.AllMetadata(ctx)
772 if err != nil {
773 return nil, err
774 }
Robert Findley61280302022-10-17 16:35:50 -0400775
Alan Donovan64f9d622023-01-30 12:32:11 -0500776 // Rename package and import declarations in all relevant packages.
777 edits := make(map[span.URI][]diff.Edit)
Robert Findley61280302022-10-17 16:35:50 -0400778 for _, m := range allMetadata {
779 // Special case: x_test packages for the renamed package will not have the
cui fliter9161e3a2023-07-14 14:55:17 +0800780 // package path as a dir prefix, but still need their package clauses
Robert Findley61280302022-10-17 16:35:50 -0400781 // renamed.
Alan Donovan64f9d622023-01-30 12:32:11 -0500782 if m.PkgPath == oldPkgPath+"_test" {
783 if err := renamePackageClause(ctx, m, s, newName+"_test", edits); err != nil {
Robert Findley61280302022-10-17 16:35:50 -0400784 return nil, err
785 }
786 continue
Dylan Le40dabfa2022-08-03 14:45:51 -0400787 }
788
Robert Findley61280302022-10-17 16:35:50 -0400789 // Subtle: check this condition before checking for valid module info
790 // below, because we should not fail this operation if unrelated packages
791 // lack module info.
Alan Donovan64f9d622023-01-30 12:32:11 -0500792 if !strings.HasPrefix(string(m.PkgPath)+"/", string(oldPkgPath)+"/") {
Robert Findley61280302022-10-17 16:35:50 -0400793 continue // not affected by the package renaming
Dylan Le40dabfa2022-08-03 14:45:51 -0400794 }
795
Alan Donovan85bf7a82022-11-18 12:03:11 -0500796 if m.Module == nil {
Alan Donovan44395ff2022-12-21 12:13:36 -0500797 // This check will always fail under Bazel.
Alan Donovan85bf7a82022-11-18 12:03:11 -0500798 return nil, fmt.Errorf("cannot rename package: missing module information for package %q", m.PkgPath)
Dylan Le40dabfa2022-08-03 14:45:51 -0400799 }
800
Alan Donovan85bf7a82022-11-18 12:03:11 -0500801 if modulePath != PackagePath(m.Module.Path) {
Robert Findley61280302022-10-17 16:35:50 -0400802 continue // don't edit imports if nested package and renaming package have different module paths
803 }
804
805 // Renaming a package consists of changing its import path and package name.
Alan Donovan64f9d622023-01-30 12:32:11 -0500806 suffix := strings.TrimPrefix(string(m.PkgPath), string(oldPkgPath))
Robert Findley61280302022-10-17 16:35:50 -0400807 newPath := newPathPrefix + suffix
808
Alan Donovan85bf7a82022-11-18 12:03:11 -0500809 pkgName := m.Name
Alan Donovan64f9d622023-01-30 12:32:11 -0500810 if m.PkgPath == oldPkgPath {
811 pkgName = PackageName(newName)
Robert Findley61280302022-10-17 16:35:50 -0400812
Alan Donovan64f9d622023-01-30 12:32:11 -0500813 if err := renamePackageClause(ctx, m, s, newName, edits); err != nil {
Robert Findley61280302022-10-17 16:35:50 -0400814 return nil, err
815 }
816 }
817
Alan Donovan3c3713e2022-11-10 13:02:38 -0500818 imp := ImportPath(newPath) // TODO(adonovan): what if newPath has vendor/ prefix?
Alan Donovan64f9d622023-01-30 12:32:11 -0500819 if err := renameImports(ctx, s, m, imp, pkgName, edits); err != nil {
Dylan Le40dabfa2022-08-03 14:45:51 -0400820 return nil, err
821 }
Dylan Le40dabfa2022-08-03 14:45:51 -0400822 }
823
Robert Findley61280302022-10-17 16:35:50 -0400824 return edits, nil
Dylan Le40dabfa2022-08-03 14:45:51 -0400825}
826
Robert Findley61280302022-10-17 16:35:50 -0400827// renamePackageClause computes edits renaming the package clause of files in
828// the package described by the given metadata, to newName.
829//
Robert Findley61280302022-10-17 16:35:50 -0400830// Edits are written into the edits map.
Alan Donovan64f9d622023-01-30 12:32:11 -0500831func renamePackageClause(ctx context.Context, m *Metadata, snapshot Snapshot, newName PackageName, edits map[span.URI][]diff.Edit) error {
Dylan Le40dabfa2022-08-03 14:45:51 -0400832 // Rename internal references to the package in the renaming package.
Alan Donovand7dfffd2022-12-08 00:37:50 -0500833 for _, uri := range m.CompiledGoFiles {
Alan Donovan36ed0b12023-03-13 14:20:23 -0400834 fh, err := snapshot.ReadFile(ctx, uri)
Alan Donovand7dfffd2022-12-08 00:37:50 -0500835 if err != nil {
836 return err
837 }
838 f, err := snapshot.ParseGo(ctx, fh, ParseHeader)
839 if err != nil {
840 return err
841 }
Dylan Le40dabfa2022-08-03 14:45:51 -0400842 if f.File.Name == nil {
Alan Donovand7dfffd2022-12-08 00:37:50 -0500843 continue // no package declaration
Dylan Le40dabfa2022-08-03 14:45:51 -0400844 }
Alan Donovan64f9d622023-01-30 12:32:11 -0500845
846 edit, err := posEdit(f.Tok, f.File.Name.Pos(), f.File.Name.End(), string(newName))
Dylan Le40dabfa2022-08-03 14:45:51 -0400847 if err != nil {
Robert Findley61280302022-10-17 16:35:50 -0400848 return err
Dylan Le40dabfa2022-08-03 14:45:51 -0400849 }
Alan Donovan64f9d622023-01-30 12:32:11 -0500850 edits[f.URI] = append(edits[f.URI], edit)
Dylan Le40dabfa2022-08-03 14:45:51 -0400851 }
852
Robert Findley61280302022-10-17 16:35:50 -0400853 return nil
Dylan Le40dabfa2022-08-03 14:45:51 -0400854}
855
Robert Findley61280302022-10-17 16:35:50 -0400856// renameImports computes the set of edits to imports resulting from renaming
857// the package described by the given metadata, to a package with import path
858// newPath and name newName.
859//
860// Edits are written into the edits map.
Alan Donovan64f9d622023-01-30 12:32:11 -0500861func renameImports(ctx context.Context, snapshot Snapshot, m *Metadata, newPath ImportPath, newName PackageName, allEdits map[span.URI][]diff.Edit) error {
Alan Donovan44395ff2022-12-21 12:13:36 -0500862 rdeps, err := snapshot.ReverseDependencies(ctx, m.ID, false) // find direct importers
Dylan Le40dabfa2022-08-03 14:45:51 -0400863 if err != nil {
Robert Findley61280302022-10-17 16:35:50 -0400864 return err
Robert Findleyb15dac22022-08-30 14:40:12 -0400865 }
866
Alan Donovanb2b9dc32022-12-12 11:31:59 -0500867 // Pass 1: rename import paths in import declarations.
868 needsTypeCheck := make(map[PackageID][]span.URI)
869 for _, rdep := range rdeps {
870 if rdep.IsIntermediateTestVariant() {
871 continue // for renaming, these variants are redundant
872 }
873
Alan Donovanb2b9dc32022-12-12 11:31:59 -0500874 for _, uri := range rdep.CompiledGoFiles {
Alan Donovan36ed0b12023-03-13 14:20:23 -0400875 fh, err := snapshot.ReadFile(ctx, uri)
Alan Donovanb2b9dc32022-12-12 11:31:59 -0500876 if err != nil {
877 return err
878 }
879 f, err := snapshot.ParseGo(ctx, fh, ParseHeader)
880 if err != nil {
881 return err
882 }
883 if f.File.Name == nil {
884 continue // no package declaration
885 }
Dylan Le40dabfa2022-08-03 14:45:51 -0400886 for _, imp := range f.File.Imports {
Alan Donovanb2b9dc32022-12-12 11:31:59 -0500887 if rdep.DepsByImpPath[UnquoteImportPath(imp)] != m.ID {
Robert Findley61280302022-10-17 16:35:50 -0400888 continue // not the import we're looking for
Dylan Le40dabfa2022-08-03 14:45:51 -0400889 }
890
Alan Donovanb2b9dc32022-12-12 11:31:59 -0500891 // If the import does not explicitly specify
892 // a local name, then we need to invoke the
893 // type checker to locate references to update.
Alan Donovan5ed33df2023-01-26 22:08:04 -0500894 //
895 // TODO(adonovan): is this actually true?
896 // Renaming an import with a local name can still
897 // cause conflicts: shadowing of built-ins, or of
898 // package-level decls in the same or another file.
Alan Donovanb2b9dc32022-12-12 11:31:59 -0500899 if imp.Name == nil {
900 needsTypeCheck[rdep.ID] = append(needsTypeCheck[rdep.ID], uri)
901 }
902
Dylan Le40dabfa2022-08-03 14:45:51 -0400903 // Create text edit for the import path (string literal).
Alan Donovan64f9d622023-01-30 12:32:11 -0500904 edit, err := posEdit(f.Tok, imp.Path.Pos(), imp.Path.End(), strconv.Quote(string(newPath)))
Dylan Le40dabfa2022-08-03 14:45:51 -0400905 if err != nil {
Robert Findley61280302022-10-17 16:35:50 -0400906 return err
Dylan Le40dabfa2022-08-03 14:45:51 -0400907 }
Alan Donovan64f9d622023-01-30 12:32:11 -0500908 allEdits[uri] = append(allEdits[uri], edit)
Alan Donovanb2b9dc32022-12-12 11:31:59 -0500909 }
910 }
911 }
Dylan Le40dabfa2022-08-03 14:45:51 -0400912
Alan Donovanb2b9dc32022-12-12 11:31:59 -0500913 // If the imported package's name hasn't changed,
914 // we don't need to rename references within each file.
915 if newName == m.Name {
916 return nil
917 }
918
919 // Pass 2: rename local name (types.PkgName) of imported
920 // package throughout one or more files of the package.
921 ids := make([]PackageID, 0, len(needsTypeCheck))
922 for id := range needsTypeCheck {
923 ids = append(ids, id)
924 }
Robert Findley21d22562023-02-21 12:26:27 -0500925 pkgs, err := snapshot.TypeCheck(ctx, ids...)
Alan Donovanb2b9dc32022-12-12 11:31:59 -0500926 if err != nil {
927 return err
928 }
929 for i, id := range ids {
930 pkg := pkgs[i]
931 for _, uri := range needsTypeCheck[id] {
932 f, err := pkg.File(uri)
933 if err != nil {
934 return err
935 }
936 for _, imp := range f.File.Imports {
937 if imp.Name != nil {
938 continue // has explicit local name
939 }
940 if rdeps[id].DepsByImpPath[UnquoteImportPath(imp)] != m.ID {
941 continue // not the import we're looking for
Dylan Le40dabfa2022-08-03 14:45:51 -0400942 }
943
Alan Donovanb2b9dc32022-12-12 11:31:59 -0500944 pkgname := pkg.GetTypesInfo().Implicits[imp].(*types.PkgName)
Dylan Le40dabfa2022-08-03 14:45:51 -0400945
Alan Donovanb2b9dc32022-12-12 11:31:59 -0500946 pkgScope := pkg.GetTypes().Scope()
947 fileScope := pkg.GetTypesInfo().Scopes[f.File]
Dylan Le40dabfa2022-08-03 14:45:51 -0400948
Alan Donovan3c3713e2022-11-10 13:02:38 -0500949 localName := string(newName)
Dylan Le40dabfa2022-08-03 14:45:51 -0400950 try := 0
Robert Findley61280302022-10-17 16:35:50 -0400951
Dylan Le40dabfa2022-08-03 14:45:51 -0400952 // Keep trying with fresh names until one succeeds.
Alan Donovan64f9d622023-01-30 12:32:11 -0500953 //
954 // TODO(adonovan): fix: this loop is not sufficient to choose a name
955 // that is guaranteed to be conflict-free; renameObj may still fail.
956 // So the retry loop should be around renameObj, and we shouldn't
957 // bother with scopes here.
Dylan Le40dabfa2022-08-03 14:45:51 -0400958 for fileScope.Lookup(localName) != nil || pkgScope.Lookup(localName) != nil {
959 try++
960 localName = fmt.Sprintf("%s%d", newName, try)
961 }
Alan Donovane0659d12023-01-26 17:55:24 -0500962
963 // renameObj detects various conflicts, including:
964 // - new name conflicts with a package-level decl in this file;
965 // - new name hides a package-level decl in another file that
966 // is actually referenced in this file;
967 // - new name hides a built-in that is actually referenced
968 // in this file;
969 // - a reference in this file to the old package name would
970 // become shadowed by an intervening declaration that
971 // uses the new name.
972 // It returns the edits if no conflict was detected.
Alan Donovan64f9d622023-01-30 12:32:11 -0500973 editMap, _, err := renameObjects(ctx, snapshot, localName, pkg, pkgname)
Dylan Le40dabfa2022-08-03 14:45:51 -0400974 if err != nil {
Robert Findley61280302022-10-17 16:35:50 -0400975 return err
Dylan Le40dabfa2022-08-03 14:45:51 -0400976 }
Robert Findley61280302022-10-17 16:35:50 -0400977
Alan Donovanb2b9dc32022-12-12 11:31:59 -0500978 // If the chosen local package name matches the package's
979 // new name, delete the change that would have inserted
980 // an explicit local name, which is always the lexically
981 // first change.
Alan Donovan3c3713e2022-11-10 13:02:38 -0500982 if localName == string(newName) {
Alan Donovan64f9d622023-01-30 12:32:11 -0500983 edits, ok := editMap[uri]
984 if !ok {
985 return fmt.Errorf("internal error: no changes for %s", uri)
986 }
987 diff.SortEdits(edits)
988 editMap[uri] = edits[1:]
Dylan Le40dabfa2022-08-03 14:45:51 -0400989 }
Alan Donovan64f9d622023-01-30 12:32:11 -0500990 for uri, edits := range editMap {
991 allEdits[uri] = append(allEdits[uri], edits...)
Dylan Le40dabfa2022-08-03 14:45:51 -0400992 }
993 }
994 }
995 }
Robert Findley61280302022-10-17 16:35:50 -0400996 return nil
Dylan Le40dabfa2022-08-03 14:45:51 -0400997}
998
Alan Donovan64f9d622023-01-30 12:32:11 -0500999// renameObjects computes the edits to the type-checked syntax package pkg
1000// required to rename a set of target objects to newName.
1001//
1002// It also returns the set of objects that were found (due to
1003// corresponding methods and embedded fields) to require renaming as a
1004// consequence of the requested renamings.
1005//
1006// It returns an error if the renaming would cause a conflict.
1007func renameObjects(ctx context.Context, snapshot Snapshot, newName string, pkg Package, targets ...types.Object) (map[span.URI][]diff.Edit, map[types.Object]bool, error) {
Robert Findleyb15dac22022-08-30 14:40:12 -04001008 r := renamer{
Alan Donovan64f9d622023-01-30 12:32:11 -05001009 pkg: pkg,
Robert Findleyb15dac22022-08-30 14:40:12 -04001010 objsToUpdate: make(map[types.Object]bool),
Alan Donovan64f9d622023-01-30 12:32:11 -05001011 from: targets[0].Name(),
Robert Findleyb15dac22022-08-30 14:40:12 -04001012 to: newName,
Robert Findleyb15dac22022-08-30 14:40:12 -04001013 }
1014
1015 // A renaming initiated at an interface method indicates the
1016 // intention to rename abstract and concrete methods as needed
1017 // to preserve assignability.
Alan Donovan64f9d622023-01-30 12:32:11 -05001018 // TODO(adonovan): pull this into the caller.
1019 for _, obj := range targets {
1020 if obj, ok := obj.(*types.Func); ok {
Robert Findleyb15dac22022-08-30 14:40:12 -04001021 recv := obj.Type().(*types.Signature).Recv()
Alan Donovan9682b0d2023-01-18 10:02:11 -05001022 if recv != nil && types.IsInterface(recv.Type().Underlying()) {
Robert Findleyb15dac22022-08-30 14:40:12 -04001023 r.changeMethods = true
1024 break
1025 }
1026 }
1027 }
Robert Findleyb15dac22022-08-30 14:40:12 -04001028
1029 // Check that the renaming of the identifier is ok.
Alan Donovan64f9d622023-01-30 12:32:11 -05001030 for _, obj := range targets {
1031 r.check(obj)
Alan Donovan80afb092023-02-07 15:42:30 -05001032 if len(r.conflicts) > 0 {
1033 // Stop at first error.
Alan Donovan64f9d622023-01-30 12:32:11 -05001034 return nil, nil, fmt.Errorf("%s", strings.Join(r.conflicts, "\n"))
Robert Findleyb15dac22022-08-30 14:40:12 -04001035 }
1036 }
Robert Findleyb15dac22022-08-30 14:40:12 -04001037
Alan Donovan64f9d622023-01-30 12:32:11 -05001038 editMap, err := r.update()
Robert Findleyb15dac22022-08-30 14:40:12 -04001039 if err != nil {
Alan Donovan64f9d622023-01-30 12:32:11 -05001040 return nil, nil, err
Robert Findleyb15dac22022-08-30 14:40:12 -04001041 }
1042
Alan Donovan64f9d622023-01-30 12:32:11 -05001043 // Remove initial targets so that only 'consequences' remain.
1044 for _, obj := range targets {
1045 delete(r.objsToUpdate, obj)
Robert Findleyb15dac22022-08-30 14:40:12 -04001046 }
Alan Donovan64f9d622023-01-30 12:32:11 -05001047 return editMap, r.objsToUpdate, nil
Robert Findleyb15dac22022-08-30 14:40:12 -04001048}
1049
Alan Donovan64f9d622023-01-30 12:32:11 -05001050// Rename all references to the target objects.
Alan Donovand96b2382022-09-30 21:58:21 -04001051func (r *renamer) update() (map[span.URI][]diff.Edit, error) {
1052 result := make(map[span.URI][]diff.Edit)
Robert Findleyb15dac22022-08-30 14:40:12 -04001053
Alan Donovan64f9d622023-01-30 12:32:11 -05001054 // shouldUpdate reports whether obj is one of (or an
1055 // instantiation of one of) the target objects.
1056 shouldUpdate := func(obj types.Object) bool {
Robert Findley1517d1a2023-08-11 19:35:44 -04001057 return containsOrigin(r.objsToUpdate, obj)
Robert Findleyb15dac22022-08-30 14:40:12 -04001058 }
Alan Donovan64f9d622023-01-30 12:32:11 -05001059
1060 // Find all identifiers in the package that define or use a
cui fliter2ffc4dc2023-07-25 17:11:18 +08001061 // renamed object. We iterate over info as it is more efficient
Alan Donovan64f9d622023-01-30 12:32:11 -05001062 // than calling ast.Inspect for each of r.pkg.CompiledGoFiles().
1063 type item struct {
1064 node ast.Node // Ident, ImportSpec (obj=PkgName), or CaseClause (obj=Var)
1065 obj types.Object
1066 isDef bool
1067 }
1068 var items []item
1069 info := r.pkg.GetTypesInfo()
1070 for id, obj := range info.Uses {
1071 if shouldUpdate(obj) {
1072 items = append(items, item{id, obj, false})
1073 }
1074 }
1075 for id, obj := range info.Defs {
1076 if shouldUpdate(obj) {
1077 items = append(items, item{id, obj, true})
1078 }
1079 }
1080 for node, obj := range info.Implicits {
1081 if shouldUpdate(obj) {
1082 switch node.(type) {
1083 case *ast.ImportSpec, *ast.CaseClause:
1084 items = append(items, item{node, obj, true})
1085 }
1086 }
1087 }
1088 sort.Slice(items, func(i, j int) bool {
1089 return items[i].node.Pos() < items[j].node.Pos()
1090 })
1091
1092 // Update each identifier.
1093 for _, item := range items {
1094 pgf, ok := enclosingFile(r.pkg, item.node.Pos())
1095 if !ok {
1096 bug.Reportf("edit does not belong to syntax of package %q", r.pkg)
Robert Findleyb15dac22022-08-30 14:40:12 -04001097 continue
1098 }
Robert Findleyb15dac22022-08-30 14:40:12 -04001099
1100 // Renaming a types.PkgName may result in the addition or removal of an identifier,
1101 // so we deal with this separately.
Alan Donovan64f9d622023-01-30 12:32:11 -05001102 if pkgName, ok := item.obj.(*types.PkgName); ok && item.isDef {
1103 edit, err := r.updatePkgName(pgf, pkgName)
Robert Findleyb15dac22022-08-30 14:40:12 -04001104 if err != nil {
1105 return nil, err
1106 }
Alan Donovan64f9d622023-01-30 12:32:11 -05001107 result[pgf.URI] = append(result[pgf.URI], edit)
Robert Findleyb15dac22022-08-30 14:40:12 -04001108 continue
1109 }
1110
Alan Donovan64f9d622023-01-30 12:32:11 -05001111 // Workaround the unfortunate lack of a Var object
1112 // for x in "switch x := expr.(type) {}" by adjusting
1113 // the case clause to the switch ident.
1114 // This may result in duplicate edits, but we de-dup later.
1115 if _, ok := item.node.(*ast.CaseClause); ok {
1116 path, _ := astutil.PathEnclosingInterval(pgf.File, item.obj.Pos(), item.obj.Pos())
1117 item.node = path[0].(*ast.Ident)
1118 }
1119
Robert Findleyb15dac22022-08-30 14:40:12 -04001120 // Replace the identifier with r.to.
Alan Donovan64f9d622023-01-30 12:32:11 -05001121 edit, err := posEdit(pgf.Tok, item.node.Pos(), item.node.End(), r.to)
1122 if err != nil {
1123 return nil, err
Robert Findleyb15dac22022-08-30 14:40:12 -04001124 }
1125
Alan Donovan64f9d622023-01-30 12:32:11 -05001126 result[pgf.URI] = append(result[pgf.URI], edit)
Robert Findleyb15dac22022-08-30 14:40:12 -04001127
Alan Donovan64f9d622023-01-30 12:32:11 -05001128 if !item.isDef { // uses do not have doc comments to update.
Robert Findleyb15dac22022-08-30 14:40:12 -04001129 continue
1130 }
1131
Alan Donovan64f9d622023-01-30 12:32:11 -05001132 doc := docComment(pgf, item.node.(*ast.Ident))
Robert Findleyb15dac22022-08-30 14:40:12 -04001133 if doc == nil {
1134 continue
1135 }
1136
1137 // Perform the rename in doc comments declared in the original package.
1138 // go/parser strips out \r\n returns from the comment text, so go
1139 // line-by-line through the comment text to get the correct positions.
Alan Donovan64f9d622023-01-30 12:32:11 -05001140 docRegexp := regexp.MustCompile(`\b` + r.from + `\b`) // valid identifier => valid regexp
Robert Findleyb15dac22022-08-30 14:40:12 -04001141 for _, comment := range doc.List {
1142 if isDirective(comment.Text) {
1143 continue
1144 }
Alan Donovand96b2382022-09-30 21:58:21 -04001145 // TODO(adonovan): why are we looping over lines?
1146 // Just run the loop body once over the entire multiline comment.
Robert Findleyb15dac22022-08-30 14:40:12 -04001147 lines := strings.Split(comment.Text, "\n")
Alan Donovan64f9d622023-01-30 12:32:11 -05001148 tokFile := pgf.Tok
Peter Weinberger0a3e5f02023-03-30 10:54:29 -04001149 commentLine := safetoken.Line(tokFile, comment.Pos())
Alan Donovand96b2382022-09-30 21:58:21 -04001150 uri := span.URIFromPath(tokFile.Name())
Robert Findleyb15dac22022-08-30 14:40:12 -04001151 for i, line := range lines {
1152 lineStart := comment.Pos()
1153 if i > 0 {
1154 lineStart = tokFile.LineStart(commentLine + i)
1155 }
1156 for _, locs := range docRegexp.FindAllIndex([]byte(line), -1) {
Alan Donovan64f9d622023-01-30 12:32:11 -05001157 edit, err := posEdit(tokFile, lineStart+token.Pos(locs[0]), lineStart+token.Pos(locs[1]), r.to)
1158 if err != nil {
1159 return nil, err // can't happen
1160 }
1161 result[uri] = append(result[uri], edit)
Robert Findleyb15dac22022-08-30 14:40:12 -04001162 }
1163 }
1164 }
1165 }
1166
1167 return result, nil
1168}
1169
Alan Donovan64f9d622023-01-30 12:32:11 -05001170// docComment returns the doc for an identifier within the specified file.
1171func docComment(pgf *ParsedGoFile, id *ast.Ident) *ast.CommentGroup {
1172 nodes, _ := astutil.PathEnclosingInterval(pgf.File, id.Pos(), id.End())
Robert Findleyb15dac22022-08-30 14:40:12 -04001173 for _, node := range nodes {
1174 switch decl := node.(type) {
1175 case *ast.FuncDecl:
1176 return decl.Doc
1177 case *ast.Field:
1178 return decl.Doc
1179 case *ast.GenDecl:
1180 return decl.Doc
1181 // For {Type,Value}Spec, if the doc on the spec is absent,
1182 // search for the enclosing GenDecl
1183 case *ast.TypeSpec:
1184 if decl.Doc != nil {
1185 return decl.Doc
1186 }
1187 case *ast.ValueSpec:
1188 if decl.Doc != nil {
1189 return decl.Doc
1190 }
1191 case *ast.Ident:
1192 case *ast.AssignStmt:
1193 // *ast.AssignStmt doesn't have an associated comment group.
1194 // So, we try to find a comment just before the identifier.
1195
1196 // Try to find a comment group only for short variable declarations (:=).
1197 if decl.Tok != token.DEFINE {
1198 return nil
1199 }
1200
Peter Weinberger0a3e5f02023-03-30 10:54:29 -04001201 identLine := safetoken.Line(pgf.Tok, id.Pos())
Robert Findleyb15dac22022-08-30 14:40:12 -04001202 for _, comment := range nodes[len(nodes)-1].(*ast.File).Comments {
1203 if comment.Pos() > id.Pos() {
1204 // Comment is after the identifier.
1205 continue
1206 }
1207
Peter Weinberger0a3e5f02023-03-30 10:54:29 -04001208 lastCommentLine := safetoken.Line(pgf.Tok, comment.End())
Robert Findleyb15dac22022-08-30 14:40:12 -04001209 if lastCommentLine+1 == identLine {
1210 return comment
1211 }
1212 }
1213 default:
1214 return nil
1215 }
1216 }
1217 return nil
1218}
1219
Dylan Le40dabfa2022-08-03 14:45:51 -04001220// updatePkgName returns the updates to rename a pkgName in the import spec by
1221// only modifying the package name portion of the import declaration.
Alan Donovan64f9d622023-01-30 12:32:11 -05001222func (r *renamer) updatePkgName(pgf *ParsedGoFile, pkgName *types.PkgName) (diff.Edit, error) {
Robert Findleyb15dac22022-08-30 14:40:12 -04001223 // Modify ImportSpec syntax to add or remove the Name as needed.
Alan Donovan64f9d622023-01-30 12:32:11 -05001224 path, _ := astutil.PathEnclosingInterval(pgf.File, pkgName.Pos(), pkgName.Pos())
Robert Findleyb15dac22022-08-30 14:40:12 -04001225 if len(path) < 2 {
Alan Donovan64f9d622023-01-30 12:32:11 -05001226 return diff.Edit{}, fmt.Errorf("no path enclosing interval for %s", pkgName.Name())
Robert Findleyb15dac22022-08-30 14:40:12 -04001227 }
1228 spec, ok := path[1].(*ast.ImportSpec)
1229 if !ok {
Alan Donovan64f9d622023-01-30 12:32:11 -05001230 return diff.Edit{}, fmt.Errorf("failed to update PkgName for %s", pkgName.Name())
Robert Findleyb15dac22022-08-30 14:40:12 -04001231 }
1232
Dylan Le40dabfa2022-08-03 14:45:51 -04001233 newText := ""
Robert Findleyb15dac22022-08-30 14:40:12 -04001234 if pkgName.Imported().Name() != r.to {
Dylan Le40dabfa2022-08-03 14:45:51 -04001235 newText = r.to + " "
Robert Findleyb15dac22022-08-30 14:40:12 -04001236 }
1237
Dylan Le40dabfa2022-08-03 14:45:51 -04001238 // Replace the portion (possibly empty) of the spec before the path:
1239 // local "path" or "path"
1240 // -> <- -><-
Alan Donovan64f9d622023-01-30 12:32:11 -05001241 return posEdit(pgf.Tok, spec.Pos(), spec.Path.Pos(), newText)
Alan Donovan5ed33df2023-01-26 22:08:04 -05001242}
1243
1244// parsePackageNameDecl is a convenience function that parses and
1245// returns the package name declaration of file fh, and reports
1246// whether the position ppos lies within it.
1247//
Alan Donovan537c4aa2023-03-13 17:42:29 -04001248// Note: also used by references.
Alan Donovan5ed33df2023-01-26 22:08:04 -05001249func parsePackageNameDecl(ctx context.Context, snapshot Snapshot, fh FileHandle, ppos protocol.Position) (*ParsedGoFile, bool, error) {
1250 pgf, err := snapshot.ParseGo(ctx, fh, ParseHeader)
1251 if err != nil {
1252 return nil, false, err
1253 }
1254 // Careful: because we used ParseHeader,
1255 // pgf.Pos(ppos) may be beyond EOF => (0, err).
1256 pos, _ := pgf.PositionPos(ppos)
1257 return pgf, pgf.File.Name.Pos() <= pos && pos <= pgf.File.Name.End(), nil
1258}
Alan Donovan64f9d622023-01-30 12:32:11 -05001259
1260// enclosingFile returns the CompiledGoFile of pkg that contains the specified position.
1261func enclosingFile(pkg Package, pos token.Pos) (*ParsedGoFile, bool) {
1262 for _, pgf := range pkg.CompiledGoFiles() {
1263 if pgf.File.Pos() <= pos && pos <= pgf.File.End() {
1264 return pgf, true
1265 }
1266 }
1267 return nil, false
1268}
1269
1270// posEdit returns an edit to replace the (start, end) range of tf with 'new'.
1271func posEdit(tf *token.File, start, end token.Pos, new string) (diff.Edit, error) {
1272 startOffset, endOffset, err := safetoken.Offsets(tf, start, end)
1273 if err != nil {
1274 return diff.Edit{}, err
1275 }
1276 return diff.Edit{Start: startOffset, End: endOffset, New: new}, nil
1277}