Skip to content

Commit 28655ca

Browse files
committed
feat: suggested fixes for context.Background and context.TODO
1 parent a271631 commit 28655ca

File tree

8 files changed

+1192
-16
lines changed

8 files changed

+1192
-16
lines changed

report.go

Lines changed: 30 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
package usetesting
22

33
import (
4+
"fmt"
45
"go/ast"
56
"go/token"
67
"slices"
8+
"strings"
79

810
"golang.org/x/tools/go/analysis"
911
)
@@ -70,7 +72,7 @@ func (a *analyzer) reportSelector(pass *analysis.Pass, se *ast.SelectorExpr, fnI
7072
return
7173
}
7274

73-
a.report(pass, se.Pos(), ident.Name, se.Sel.Name, fnInfo)
75+
a.report(pass, se, ident.Name, se.Sel.Name, fnInfo)
7476
}
7577

7678
func (a *analyzer) reportIdent(pass *analysis.Pass, ident *ast.Ident, fnInfo *FuncInfo) {
@@ -84,38 +86,51 @@ func (a *analyzer) reportIdent(pass *analysis.Pass, ident *ast.Ident, fnInfo *Fu
8486

8587
pkgName := getPkgNameFromType(pass, ident)
8688

87-
a.report(pass, ident.Pos(), pkgName, ident.Name, fnInfo)
89+
a.report(pass, ident, pkgName, ident.Name, fnInfo)
8890
}
8991

9092
//nolint:gocyclo // The complexity is expected by the number of cases to check.
91-
func (a *analyzer) report(pass *analysis.Pass, pos token.Pos, origPkgName, origName string, fnInfo *FuncInfo) {
93+
func (a *analyzer) report(pass *analysis.Pass, rg analysis.Range, origPkgName, origName string, fnInfo *FuncInfo) {
9294
switch {
9395
case a.osMkdirTemp && origPkgName == osPkgName && origName == mkdirTempName:
94-
report(pass, pos, origPkgName, origName, tempDirName, fnInfo)
96+
report(pass, rg, origPkgName, origName, tempDirName, fnInfo)
9597

9698
case a.osTempDir && origPkgName == osPkgName && origName == tempDirName:
97-
report(pass, pos, origPkgName, origName, tempDirName, fnInfo)
99+
report(pass, rg, origPkgName, origName, tempDirName, fnInfo)
98100

99101
case a.osSetenv && origPkgName == osPkgName && origName == setenvName:
100-
report(pass, pos, origPkgName, origName, setenvName, fnInfo)
102+
report(pass, rg, origPkgName, origName, setenvName, fnInfo)
101103

102104
case a.geGo124 && a.osChdir && origPkgName == osPkgName && origName == chdirName:
103-
report(pass, pos, origPkgName, origName, chdirName, fnInfo)
105+
report(pass, rg, origPkgName, origName, chdirName, fnInfo)
104106

105107
case a.geGo124 && a.contextBackground && origPkgName == contextPkgName && origName == backgroundName:
106-
report(pass, pos, origPkgName, origName, contextName, fnInfo)
108+
report(pass, rg, origPkgName, origName, contextName, fnInfo)
107109

108110
case a.geGo124 && a.contextTodo && origPkgName == contextPkgName && origName == todoName:
109-
report(pass, pos, origPkgName, origName, contextName, fnInfo)
111+
report(pass, rg, origPkgName, origName, contextName, fnInfo)
110112
}
111113
}
112114

113-
func report(pass *analysis.Pass, pos token.Pos, origPkgName, origName, expectName string, fnInfo *FuncInfo) {
114-
pass.Reportf(
115-
pos,
116-
"%s.%s() could be replaced by %s.%s() in %s",
117-
origPkgName, origName, fnInfo.ArgName, expectName, fnInfo.Name,
118-
)
115+
func report(pass *analysis.Pass, rg analysis.Range, origPkgName, origName, expectName string, fnInfo *FuncInfo) {
116+
diagnostic := analysis.Diagnostic{
117+
Pos: rg.Pos(),
118+
Message: fmt.Sprintf("%s.%s() could be replaced by %s.%s() in %s",
119+
origPkgName, origName, fnInfo.ArgName, expectName, fnInfo.Name,
120+
),
121+
}
122+
123+
if !strings.Contains(fnInfo.ArgName, "<") && origPkgName == contextPkgName {
124+
diagnostic.SuggestedFixes = append(diagnostic.SuggestedFixes, analysis.SuggestedFix{
125+
TextEdits: []analysis.TextEdit{{
126+
Pos: rg.Pos(),
127+
End: rg.End(),
128+
NewText: []byte(fmt.Sprintf("%s.%s", fnInfo.ArgName, expectName)),
129+
}},
130+
})
131+
}
132+
133+
pass.Report(diagnostic)
119134
}
120135

121136
func isFirstArgEmptyString(ce *ast.CallExpr) bool {
Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
package basic
2+
3+
import (
4+
"context"
5+
"errors"
6+
"fmt"
7+
"runtime"
8+
"strings"
9+
"testing"
10+
)
11+
12+
func Test_NoName(_ *testing.T) {
13+
context.Background() // want `context\.Background\(\) could be replaced by <t/b>\.Context\(\) in .+`
14+
}
15+
16+
func Benchmark_ExprStmt(b *testing.B) {
17+
b.Context() // want `context\.Background\(\) could be replaced by b\.Context\(\) in .+`
18+
}
19+
20+
func Test_ExprStmt(t *testing.T) {
21+
t.Context() // want `context\.Background\(\) could be replaced by t\.Context\(\) in .+`
22+
}
23+
24+
func Test_AssignStmt(t *testing.T) {
25+
ctx := t.Context() // want `context\.Background\(\) could be replaced by t\.Context\(\) in .+`
26+
_ = ctx
27+
}
28+
29+
func Test_AssignStmt_ignore_return(t *testing.T) {
30+
_ = t.Context() // want `context\.Background\(\) could be replaced by t\.Context\(\) in .+`
31+
}
32+
33+
func Test_IfStmt(t *testing.T) {
34+
if ctx := t.Context(); ctx != nil { // want `context\.Background\(\) could be replaced by t\.Context\(\) in .+`
35+
// foo
36+
}
37+
}
38+
39+
func TestName_RangeStmt(t *testing.T) {
40+
for range 5 {
41+
t.Context() // want `context\.Background\(\) could be replaced by t\.Context\(\) in .+`
42+
}
43+
}
44+
45+
func Test_ForStmt(t *testing.T) {
46+
for i := 0; i < 3; i++ {
47+
t.Context() // want `context\.Background\(\) could be replaced by t\.Context\(\) in .+`
48+
}
49+
}
50+
51+
func Test_DeferStmt(t *testing.T) {
52+
defer t.Context() // want `context\.Background\(\) could be replaced by t\.Context\(\) in .+`
53+
}
54+
55+
func Test_CallExpr(t *testing.T) {
56+
t.Log(t.Context()) // want `context\.Background\(\) could be replaced by t\.Context\(\) in .+`
57+
}
58+
59+
func Test_CallExpr_deep(t *testing.T) {
60+
t.Log(
61+
fmt.Sprintf("here: %s, %s",
62+
strings.TrimSuffix(
63+
strings.TrimPrefix(
64+
fmt.Sprintf("%s",
65+
t.Context(), // want `context\.Background\(\) could be replaced by t\.Context\(\) in .+`
66+
),
67+
"a",
68+
),
69+
"b",
70+
),
71+
"c",
72+
),
73+
)
74+
}
75+
76+
func Test_GoStmt(t *testing.T) {
77+
go func() {
78+
t.Context() // want `context\.Background\(\) could be replaced by t\.Context\(\) in .+`
79+
}()
80+
}
81+
82+
func Test_GoStmt_arg(t *testing.T) {
83+
go func(ctx context.Context) {}(t.Context()) // want `context\.Background\(\) could be replaced by t\.Context\(\) in .+`
84+
}
85+
86+
func Test_CallExpr_recursive(t *testing.T) {
87+
foo(t, "")
88+
}
89+
90+
func foo(t *testing.T, s string) error {
91+
return foo(t, fmt.Sprintf("%s %s", s, t.Context())) // want `context\.Background\(\) could be replaced by t\.Context\(\) in .+`
92+
}
93+
94+
func Test_FuncLit_ExprStmt(t *testing.T) {
95+
testCases := []struct {
96+
desc string
97+
}{
98+
{desc: ""},
99+
}
100+
101+
for _, test := range testCases {
102+
t.Run(test.desc, func(t *testing.T) {
103+
t.Context() // want `context\.Background\(\) could be replaced by t\.Context\(\) in .+`
104+
})
105+
}
106+
}
107+
108+
func Test_SwitchStmt(t *testing.T) {
109+
switch {
110+
case runtime.GOOS == "linux":
111+
t.Context() // want `context\.Background\(\) could be replaced by t\.Context\(\) in .+`
112+
}
113+
}
114+
115+
func Test_SwitchStmt_case(t *testing.T) {
116+
switch {
117+
case t.Context() == nil: // want `context\.Background\(\) could be replaced by t\.Context\(\) in .+`
118+
// noop
119+
}
120+
}
121+
122+
func Test_DeclStmt(t *testing.T) {
123+
var ctx context.Context = t.Context() // want `context\.Background\(\) could be replaced by t\.Context\(\) in .+`
124+
_ = ctx
125+
}
126+
127+
func Test_DeclStmt_tuple(t *testing.T) {
128+
var err, ctx any = errors.New(""), t.Context() // want `context\.Background\(\) could be replaced by t\.Context\(\) in .+`
129+
_ = err
130+
_ = ctx
131+
}
132+
133+
func Test_SelectStmt(t *testing.T) {
134+
doneCh := make(chan struct{})
135+
136+
go func() {
137+
for {
138+
select {
139+
case <-doneCh:
140+
t.Context() // want `context\.Background\(\) could be replaced by t\.Context\(\) in .+`
141+
}
142+
}
143+
}()
144+
}
145+
146+
func Test_DeferStmt_wrap(t *testing.T) {
147+
defer func() {
148+
t.Context() // want `context\.Background\(\) could be replaced by t\.Context\(\) in .+`
149+
}()
150+
}
151+
152+
func Test_SelectStmt_anon_func(t *testing.T) {
153+
doneCh := make(chan struct{})
154+
155+
go func() {
156+
for {
157+
select {
158+
case <-doneCh:
159+
func() {
160+
t.Context() // want `context\.Background\(\) could be replaced by t\.Context\(\) in .+`
161+
}()
162+
}
163+
}
164+
}()
165+
}
166+
167+
func Test_BlockStmt(t *testing.T) {
168+
{
169+
t.Context() // want `context\.Background\(\) could be replaced by t\.Context\(\) in .+`
170+
}
171+
}
172+
173+
func Test_TypeSwitchStmt(t *testing.T) {
174+
t.Context() // want `context\.Background\(\) could be replaced by t\.Context\(\) in .+`
175+
}
176+
177+
func Test_TypeSwitchStmt_AssignStmt(t *testing.T) {
178+
switch v := t.Context().(type) { // want `context\.Background\(\) could be replaced by t\.Context\(\) in .+`
179+
case error:
180+
_ = v
181+
}
182+
}
183+
184+
func Test_SwitchStmt_Tag(t *testing.T) {
185+
switch t.Context() { // want `context\.Background\(\) could be replaced by t\.Context\(\) in .+`
186+
case nil:
187+
}
188+
}
189+
190+
func foobar() {
191+
context.Background()
192+
}

0 commit comments

Comments
 (0)