Skip to content

Commit 0eebaab

Browse files
committed
go/analysis: allow for identical SuggestedFixes
Stop treating identical SuggestedFixes as overlapping text edits. Packages can be loaded in multiple ways due to testing, e.g. "p" and "p [p.test]". Currently SuggestedFixes from both are considered overlapping and so are not applied. Updates applyFixes() to report errors in more situations. Fixes golang/go#54740 Change-Id: I73acb3b73d88535144cfae5161faabb0615a1774 Reviewed-on: https://go-review.googlesource.com/c/tools/+/426734 Reviewed-by: Abirdcfly Abirdcfly <[email protected]> Run-TryBot: Tim King <[email protected]> Reviewed-by: Alan Donovan <[email protected]> TryBot-Result: Gopher Robot <[email protected]> gopls-CI: kokoro <[email protected]>
1 parent eeaf4eb commit 0eebaab

File tree

3 files changed

+175
-11
lines changed

3 files changed

+175
-11
lines changed

go/analysis/internal/checker/checker.go

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,11 @@ func Run(args []string, analyzers []*analysis.Analyzer) (exitcode int) {
147147
roots := analyze(initial, analyzers)
148148

149149
if Fix {
150-
applyFixes(roots)
150+
if err := applyFixes(roots); err != nil {
151+
// Fail when applying fixes failed.
152+
log.Print(err)
153+
return 1
154+
}
151155
}
152156
return printDiagnostics(roots)
153157
}
@@ -305,15 +309,17 @@ func analyze(pkgs []*packages.Package, analyzers []*analysis.Analyzer) []*action
305309
return roots
306310
}
307311

308-
func applyFixes(roots []*action) {
312+
func applyFixes(roots []*action) error {
309313
visited := make(map[*action]bool)
310314
var apply func(*action) error
311315
var visitAll func(actions []*action) error
312316
visitAll = func(actions []*action) error {
313317
for _, act := range actions {
314318
if !visited[act] {
315319
visited[act] = true
316-
visitAll(act.deps)
320+
if err := visitAll(act.deps); err != nil {
321+
return err
322+
}
317323
if err := apply(act); err != nil {
318324
return err
319325
}
@@ -332,6 +338,10 @@ func applyFixes(roots []*action) {
332338
edit offsetedit
333339
left, right *node
334340
}
341+
// Edits x and y are equivalent.
342+
equiv := func(x, y offsetedit) bool {
343+
return x.start == y.start && x.end == y.end && bytes.Equal(x.newText, y.newText)
344+
}
335345

336346
var insert func(tree **node, edit offsetedit) error
337347
insert = func(treeptr **node, edit offsetedit) error {
@@ -345,6 +355,13 @@ func applyFixes(roots []*action) {
345355
} else if edit.start >= tree.edit.end {
346356
return insert(&tree.right, edit)
347357
}
358+
if equiv(edit, tree.edit) { // equivalent edits?
359+
// We skip over equivalent edits without considering them
360+
// an error. This handles identical edits coming from the
361+
// multiple ways of loading a package into a
362+
// *go/packages.Packages for testing, e.g. packages "p" and "p [p.test]".
363+
return nil
364+
}
348365

349366
// Overlapping text edit.
350367
return fmt.Errorf("analyses applying overlapping text edits affecting pos range (%v, %v) and (%v, %v)",
@@ -384,14 +401,16 @@ func applyFixes(roots []*action) {
384401
return nil
385402
}
386403

387-
visitAll(roots)
404+
if err := visitAll(roots); err != nil {
405+
return err
406+
}
388407

389408
fset := token.NewFileSet() // Shared by parse calls below
390409
// Now we've got a set of valid edits for each file. Get the new file contents.
391410
for f, tree := range editsForFile {
392411
contents, err := ioutil.ReadFile(f.Name())
393412
if err != nil {
394-
log.Fatal(err)
413+
return err
395414
}
396415

397416
cur := 0 // current position in the file
@@ -432,8 +451,11 @@ func applyFixes(roots []*action) {
432451
}
433452
}
434453

435-
ioutil.WriteFile(f.Name(), out.Bytes(), 0644)
454+
if err := ioutil.WriteFile(f.Name(), out.Bytes(), 0644); err != nil {
455+
return err
456+
}
436457
}
458+
return nil
437459
}
438460

439461
// printDiagnostics prints the diagnostics for the root packages in either

go/analysis/internal/checker/checker_test.go

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,9 @@ import (
1919
"golang.org/x/tools/internal/testenv"
2020
)
2121

22-
var from, to string
23-
2422
func TestApplyFixes(t *testing.T) {
2523
testenv.NeedsGoPackages(t)
2624

27-
from = "bar"
28-
to = "baz"
29-
3025
files := map[string]string{
3126
"rename/test.go": `package rename
3227
@@ -75,6 +70,10 @@ var analyzer = &analysis.Analyzer{
7570
}
7671

7772
func run(pass *analysis.Pass) (interface{}, error) {
73+
const (
74+
from = "bar"
75+
to = "baz"
76+
)
7877
inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
7978
nodeFilter := []ast.Node{(*ast.Ident)(nil)}
8079
inspect.Preorder(nodeFilter, func(n ast.Node) {
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
// Copyright 2022 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package checker_test
6+
7+
import (
8+
"flag"
9+
"io/ioutil"
10+
"os"
11+
"os/exec"
12+
"path"
13+
"runtime"
14+
"testing"
15+
16+
"golang.org/x/tools/go/analysis"
17+
"golang.org/x/tools/go/analysis/analysistest"
18+
"golang.org/x/tools/go/analysis/internal/checker"
19+
"golang.org/x/tools/internal/testenv"
20+
)
21+
22+
func main() {
23+
checker.Fix = true
24+
patterns := flag.Args()
25+
26+
code := checker.Run(patterns, []*analysis.Analyzer{analyzer})
27+
os.Exit(code)
28+
}
29+
30+
// TestFixes ensures that checker.Run applies fixes correctly.
31+
// This test fork/execs the main function above.
32+
func TestFixes(t *testing.T) {
33+
oses := map[string]bool{"darwin": true, "linux": true}
34+
if !oses[runtime.GOOS] {
35+
t.Skipf("skipping fork/exec test on this platform")
36+
}
37+
38+
if os.Getenv("TESTFIXES_CHILD") == "1" {
39+
// child process
40+
41+
// replace [progname -test.run=TestFixes -- ...]
42+
// by [progname ...]
43+
os.Args = os.Args[2:]
44+
os.Args[0] = "vet"
45+
main()
46+
panic("unreachable")
47+
}
48+
49+
testenv.NeedsTool(t, "go")
50+
51+
files := map[string]string{
52+
"rename/foo.go": `package rename
53+
54+
func Foo() {
55+
bar := 12
56+
_ = bar
57+
}
58+
59+
// the end
60+
`,
61+
"rename/intestfile_test.go": `package rename
62+
63+
func InTestFile() {
64+
bar := 13
65+
_ = bar
66+
}
67+
68+
// the end
69+
`,
70+
"rename/foo_test.go": `package rename_test
71+
72+
func Foo() {
73+
bar := 14
74+
_ = bar
75+
}
76+
77+
// the end
78+
`,
79+
}
80+
fixed := map[string]string{
81+
"rename/foo.go": `package rename
82+
83+
func Foo() {
84+
baz := 12
85+
_ = baz
86+
}
87+
88+
// the end
89+
`,
90+
"rename/intestfile_test.go": `package rename
91+
92+
func InTestFile() {
93+
baz := 13
94+
_ = baz
95+
}
96+
97+
// the end
98+
`,
99+
"rename/foo_test.go": `package rename_test
100+
101+
func Foo() {
102+
baz := 14
103+
_ = baz
104+
}
105+
106+
// the end
107+
`,
108+
}
109+
dir, cleanup, err := analysistest.WriteFiles(files)
110+
if err != nil {
111+
t.Fatalf("Creating test files failed with %s", err)
112+
}
113+
defer cleanup()
114+
115+
args := []string{"-test.run=TestFixes", "--", "rename"}
116+
cmd := exec.Command(os.Args[0], args...)
117+
cmd.Env = append(os.Environ(), "TESTFIXES_CHILD=1", "GOPATH="+dir, "GO111MODULE=off", "GOPROXY=off")
118+
119+
out, err := cmd.CombinedOutput()
120+
if len(out) > 0 {
121+
t.Logf("%s: out=<<%s>>", args, out)
122+
}
123+
var exitcode int
124+
if err, ok := err.(*exec.ExitError); ok {
125+
exitcode = err.ExitCode() // requires go1.12
126+
}
127+
128+
const diagnosticsExitCode = 3
129+
if exitcode != diagnosticsExitCode {
130+
t.Errorf("%s: exited %d, want %d", args, exitcode, diagnosticsExitCode)
131+
}
132+
133+
for name, want := range fixed {
134+
path := path.Join(dir, "src", name)
135+
contents, err := ioutil.ReadFile(path)
136+
if err != nil {
137+
t.Errorf("error reading %s: %v", path, err)
138+
}
139+
if got := string(contents); got != want {
140+
t.Errorf("contents of %s file did not match expectations. got=%s, want=%s", path, got, want)
141+
}
142+
}
143+
}

0 commit comments

Comments
 (0)