Skip to content

Commit b5a64bb

Browse files
adonovangopherbot
authored andcommitted
go/analysis/internal/checker: be silent with -fix
Just apply the fixes without listing the diagnostics. Also: be verbose with -v. The -v flag exists for historical reasons to do with the vet CLI, but it had no effect. This change makes it effectively an alias for -debug=v, which no-one can remember how to spell. Also, list the number of fixes and fixes updated when -fix and -v. Change-Id: Ic2342ad4868b6c8649077bf13c48e8727dbeba37 Reviewed-on: https://go-review.googlesource.com/c/tools/+/647698 LUCI-TryBot-Result: Go LUCI <[email protected]> Auto-Submit: Alan Donovan <[email protected]> Reviewed-by: Robert Findley <[email protected]>
1 parent b752317 commit b5a64bb

File tree

10 files changed

+85
-46
lines changed

10 files changed

+85
-46
lines changed

go/analysis/internal/checker/checker.go

Lines changed: 48 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,36 @@ func RegisterFlags() {
8686
// It provides most of the logic for the main functions of both the
8787
// singlechecker and the multi-analysis commands.
8888
// It returns the appropriate exit code.
89-
func Run(args []string, analyzers []*analysis.Analyzer) int {
89+
//
90+
// TODO(adonovan): tests should not call this function directly;
91+
// fiddling with global variables (flags) is error-prone and hostile
92+
// to parallelism. Instead, use unit tests of the actual units (e.g.
93+
// checker.Analyze) and integration tests (e.g. TestScript) of whole
94+
// executables.
95+
func Run(args []string, analyzers []*analysis.Analyzer) (exitcode int) {
96+
// Instead of returning a code directly,
97+
// call this function to monotonically increase the exit code.
98+
// This allows us to keep going in the face of some errors
99+
// without having to remember what code to return.
100+
//
101+
// TODO(adonovan): interpreting exit codes is like reading tea-leaves.
102+
// Insted of wasting effort trying to encode a multidimensional result
103+
// into 7 bits we should just emit structured JSON output, and
104+
// an exit code of 0 or 1 for success or failure.
105+
exitAtLeast := func(code int) {
106+
exitcode = max(code, exitcode)
107+
}
108+
109+
// When analysisflags is linked in (for {single,multi}checker),
110+
// then the -v flag is registered for complex legacy reasons
111+
// related to cmd/vet CLI.
112+
// Treat it as an undocumented alias for -debug=v.
113+
if v := flag.CommandLine.Lookup("v"); v != nil &&
114+
v.Value.(flag.Getter).Get() == true &&
115+
!strings.Contains(Debug, "v") {
116+
Debug += "v"
117+
}
118+
90119
if CPUProfile != "" {
91120
f, err := os.Create(CPUProfile)
92121
if err != nil {
@@ -142,17 +171,14 @@ func Run(args []string, analyzers []*analysis.Analyzer) int {
142171
initial, err := load(args, allSyntax)
143172
if err != nil {
144173
log.Print(err)
145-
return 1
174+
exitAtLeast(1)
175+
return
146176
}
147177

148-
// TODO(adonovan): simplify exit code logic by using a single
149-
// exit code variable and applying "code = max(code, X)" each
150-
// time an error of code X occurs.
151-
pkgsExitCode := 0
152178
// Print package and module errors regardless of RunDespiteErrors.
153179
// Do not exit if there are errors, yet.
154180
if n := packages.PrintErrors(initial); n > 0 {
155-
pkgsExitCode = 1
181+
exitAtLeast(1)
156182
}
157183

158184
var factLog io.Writer
@@ -172,7 +198,8 @@ func Run(args []string, analyzers []*analysis.Analyzer) int {
172198
graph, err := checker.Analyze(analyzers, initial, opts)
173199
if err != nil {
174200
log.Print(err)
175-
return 1
201+
exitAtLeast(1)
202+
return
176203
}
177204

178205
// Don't print the diagnostics,
@@ -181,22 +208,22 @@ func Run(args []string, analyzers []*analysis.Analyzer) int {
181208
if err := applyFixes(graph.Roots, Diff); err != nil {
182209
// Fail when applying fixes failed.
183210
log.Print(err)
184-
return 1
211+
exitAtLeast(1)
212+
return
185213
}
186-
// TODO(adonovan): don't proceed to print the text or JSON output
187-
// if we applied fixes; stop here.
188-
//
189-
// return pkgsExitCode
214+
// Don't proceed to print text/JSON,
215+
// and don't report an error
216+
// just because there were diagnostics.
217+
return
190218
}
191219

192220
// Print the results. If !RunDespiteErrors and there
193221
// are errors in the packages, this will have 0 exit
194222
// code. Otherwise, we prefer to return exit code
195223
// indicating diagnostics.
196-
if diagExitCode := printDiagnostics(graph); diagExitCode != 0 {
197-
return diagExitCode // there were diagnostics
198-
}
199-
return pkgsExitCode // package errors but no diagnostics
224+
exitAtLeast(printDiagnostics(graph))
225+
226+
return
200227
}
201228

202229
// printDiagnostics prints diagnostics in text or JSON form
@@ -541,6 +568,10 @@ fixloop:
541568
}
542569
}
543570

571+
if dbg('v') {
572+
log.Printf("applied %d fixes, updated %d files", len(fixes), filesUpdated)
573+
}
574+
544575
return nil
545576
}
546577

go/analysis/internal/checker/checker_test.go

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,10 @@ func Foo() {
4949
t.Fatal(err)
5050
}
5151
path := filepath.Join(testdata, "src/rename/test.go")
52+
5253
checker.Fix = true
5354
checker.Run([]string{"file=" + path}, []*analysis.Analyzer{renameAnalyzer})
55+
checker.Fix = false
5456

5557
contents, err := os.ReadFile(path)
5658
if err != nil {
@@ -138,31 +140,33 @@ func NewT1() *T1 { return &T1{T} }
138140
// package from source. For the rest, it asks 'go list' for export data,
139141
// which fails because the compiler encounters the type error. Since the
140142
// errors come from 'go list', the driver doesn't run the analyzer.
141-
{name: "despite-error", pattern: []string{rderrFile}, analyzers: []*analysis.Analyzer{noop}, code: 1},
143+
{name: "despite-error", pattern: []string{rderrFile}, analyzers: []*analysis.Analyzer{noop}, code: exitCodeFailed},
142144
// The noopfact analyzer does use facts, so the driver loads source for
143145
// all dependencies, does type checking itself, recognizes the error as a
144146
// type error, and runs the analyzer.
145-
{name: "despite-error-fact", pattern: []string{rderrFile}, analyzers: []*analysis.Analyzer{noopWithFact}, code: 1},
147+
{name: "despite-error-fact", pattern: []string{rderrFile}, analyzers: []*analysis.Analyzer{noopWithFact}, code: exitCodeFailed},
146148
// combination of parse/type errors and no errors
147-
{name: "despite-error-and-no-error", pattern: []string{rderrFile, "sort"}, analyzers: []*analysis.Analyzer{renameAnalyzer, noop}, code: 1},
149+
{name: "despite-error-and-no-error", pattern: []string{rderrFile, "sort"}, analyzers: []*analysis.Analyzer{renameAnalyzer, noop}, code: exitCodeFailed},
148150
// non-existing package error
149-
{name: "no-package", pattern: []string{"xyz"}, analyzers: []*analysis.Analyzer{renameAnalyzer}, code: 1},
150-
{name: "no-package-despite-error", pattern: []string{"abc"}, analyzers: []*analysis.Analyzer{noop}, code: 1},
151-
{name: "no-multi-package-despite-error", pattern: []string{"xyz", "abc"}, analyzers: []*analysis.Analyzer{noop}, code: 1},
151+
{name: "no-package", pattern: []string{"xyz"}, analyzers: []*analysis.Analyzer{renameAnalyzer}, code: exitCodeFailed},
152+
{name: "no-package-despite-error", pattern: []string{"abc"}, analyzers: []*analysis.Analyzer{noop}, code: exitCodeFailed},
153+
{name: "no-multi-package-despite-error", pattern: []string{"xyz", "abc"}, analyzers: []*analysis.Analyzer{noop}, code: exitCodeFailed},
152154
// combination of type/parsing and different errors
153-
{name: "different-errors", pattern: []string{rderrFile, "xyz"}, analyzers: []*analysis.Analyzer{renameAnalyzer, noop}, code: 1},
155+
{name: "different-errors", pattern: []string{rderrFile, "xyz"}, analyzers: []*analysis.Analyzer{renameAnalyzer, noop}, code: exitCodeFailed},
154156
// non existing dir error
155-
{name: "no-match-dir", pattern: []string{"file=non/existing/dir"}, analyzers: []*analysis.Analyzer{renameAnalyzer, noop}, code: 1},
157+
{name: "no-match-dir", pattern: []string{"file=non/existing/dir"}, analyzers: []*analysis.Analyzer{renameAnalyzer, noop}, code: exitCodeFailed},
156158
// no errors
157-
{name: "no-errors", pattern: []string{"sort"}, analyzers: []*analysis.Analyzer{renameAnalyzer, noop}, code: 0},
159+
{name: "no-errors", pattern: []string{"sort"}, analyzers: []*analysis.Analyzer{renameAnalyzer, noop}, code: exitCodeSuccess},
158160
// duplicate list error with no findings
159-
{name: "list-error", pattern: []string{cperrFile}, analyzers: []*analysis.Analyzer{noop}, code: 1},
161+
{name: "list-error", pattern: []string{cperrFile}, analyzers: []*analysis.Analyzer{noop}, code: exitCodeFailed},
160162
// duplicate list errors with findings (issue #67790)
161-
{name: "list-error-findings", pattern: []string{cperrFile}, analyzers: []*analysis.Analyzer{renameAnalyzer}, code: 3},
163+
{name: "list-error-findings", pattern: []string{cperrFile}, analyzers: []*analysis.Analyzer{renameAnalyzer}, code: exitCodeDiagnostics},
162164
} {
163-
if got := checker.Run(test.pattern, test.analyzers); got != test.code {
164-
t.Errorf("got incorrect exit code %d for test %s; want %d", got, test.name, test.code)
165-
}
165+
t.Run(test.name, func(t *testing.T) {
166+
if got := checker.Run(test.pattern, test.analyzers); got != test.code {
167+
t.Errorf("got incorrect exit code %d for test %s; want %d", got, test.name, test.code)
168+
}
169+
})
166170
}
167171
}
168172

go/analysis/internal/checker/fix_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,9 @@ func TestMain(m *testing.M) {
5252
}
5353

5454
const (
55-
exitCodeSuccess = 0 // success (no diagnostics)
55+
exitCodeSuccess = 0 // success (no diagnostics, or successful -fix)
5656
exitCodeFailed = 1 // analysis failed to run
57-
exitCodeDiagnostics = 3 // diagnostics were reported
57+
exitCodeDiagnostics = 3 // diagnostics were reported (and no -fix)
5858
)
5959

6060
// TestReportInvalidDiagnostic tests that a call to pass.Report with

go/analysis/internal/checker/start_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ package comment
4040
path := filepath.Join(testdata, "src/comment/doc.go")
4141
checker.Fix = true
4242
checker.Run([]string{"file=" + path}, []*analysis.Analyzer{commentAnalyzer})
43+
checker.Fix = false
4344

4445
contents, err := os.ReadFile(path)
4546
if err != nil {

go/analysis/internal/checker/testdata/diff.txt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,7 @@
88

99
skip GOOS=windows
1010
checker -rename -fix -diff example.com/p
11-
exit 3
12-
stderr renaming "bar" to "baz"
11+
exit 0
1312

1413
-- go.mod --
1514
module example.com

go/analysis/internal/checker/testdata/fixes.txt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22
# particular when processing duplicate fixes for overlapping packages
33
# in the same directory ("p", "p [p.test]", "p_test [p.test]").
44

5-
checker -rename -fix example.com/p
6-
exit 3
7-
stderr renaming "bar" to "baz"
5+
checker -rename -fix -v example.com/p
6+
stderr applied 8 fixes, updated 3 files
7+
exit 0
88

99
-- go.mod --
1010
module example.com

go/analysis/internal/checker/testdata/importdup.txt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
# Test that duplicate imports--and, more generally, duplicate
22
# identical insertions--are coalesced.
33

4-
checker -marker -fix example.com/a
5-
exit 3
4+
checker -marker -fix -v example.com/a
5+
stderr applied 2 fixes, updated 1 files
6+
exit 0
67

78
-- go.mod --
89
module example.com

go/analysis/internal/checker/testdata/importdup2.txt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,9 @@
1919
# In more complex examples, the result
2020
# may be more subtly order-dependent.
2121

22-
checker -marker -fix example.com/a example.com/b
23-
exit 3
22+
checker -marker -fix -v example.com/a example.com/b
23+
stderr applied 6 fixes, updated 2 files
24+
exit 0
2425

2526
-- go.mod --
2627
module example.com

go/analysis/internal/checker/testdata/noend.txt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,7 @@
22
# interpreted as if equal to SuggestedFix.Pos (see issue #64199).
33

44
checker -noend -fix example.com/a
5-
exit 3
6-
stderr say hello
5+
exit 0
76

87
-- go.mod --
98
module example.com

go/analysis/internal/checker/testdata/overlap.txt

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,12 @@
1515
# (This is a pretty unlikely situation, but it corresponds
1616
# to a historical test, TestOther, that used to check for
1717
# a conflict, and it seemed wrong to delete it without explanation.)
18+
#
19+
# The fixes are silently and successfully applied.
1820

19-
checker -rename -marker -fix example.com/a
20-
exit 3
21+
checker -rename -marker -fix -v example.com/a
22+
stderr applied 2 fixes, updated 1 file
23+
exit 0
2124

2225
-- go.mod --
2326
module example.com

0 commit comments

Comments
 (0)