Skip to content

Commit 3e909b6

Browse files
adonovangopherbot
authored andcommitted
gopls/internal/analysis/unusedfunc: use refactor.Delete* operators
This CL causes unusedfunc to make use of the new library of deletion operators, making it simpler while also handling a variety of edge cases such as n:n and n:1 assignments, side effects on the RHS, and trailing comments. Change-Id: I13be5092f294684b9434ae77fa5764a96d42bacf Reviewed-on: https://go-review.googlesource.com/c/tools/+/711360 Reviewed-by: Peter Weinberger <pjw@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Auto-Submit: Alan Donovan <adonovan@google.com>
1 parent f811b39 commit 3e909b6

File tree

5 files changed

+76
-64
lines changed

5 files changed

+76
-64
lines changed

gopls/internal/analysis/unusedfunc/testdata/basic.txtar

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -77,8 +77,27 @@ const (
7777
const (
7878
constOne = 1
7979
unusedConstTwo = constOne // want `const "unusedConstTwo" is unused`
80+
_, unusedConstThree = 0, 3 // want `const "unusedConstThree" is unused`
81+
unusedConstFour, _ = 4, 0 // want `const "unusedConstFour" is unused`
82+
unusedConstFive, UsedConst6 = 4, 6 // want `const "unusedConstFive" is unused`
8083
)
8184

85+
// -- vars --
86+
87+
var unusedVar = 1 // want `var "unusedVar" is unused`
88+
89+
var (
90+
unusedVar2 = 1 // want `var "unusedVar2" is unused`
91+
)
92+
93+
var (
94+
usedVar int
95+
unusedVar3 = usedVar // want `var "unusedVar3" is unused`
96+
_, unusedVar4, _ = Triple() // want `var "unusedVar4" is unused`
97+
)
98+
99+
func Triple() (int, int, int)
100+
82101
-- a/a.go.golden --
83102
package a
84103

@@ -112,8 +131,6 @@ type _ interface{ dynamic() }
112131

113132
type ExportedType2 int
114133

115-
// want `type "unusedUnexportedType2" is unused`
116-
117134
type (
118135
one int
119136
)
@@ -122,16 +139,22 @@ type (
122139

123140
type g[T any] int
124141

125-
// want `method "method" is unused`
126-
127142
// -- constants --
128143

129-
// want `const "unusedConst" is unused`
130-
131144
const (
132145
unusedEnum = iota
133146
)
134147

135148
const (
136149
constOne = 1
150+
UsedConst6 = 6 // want `const "unusedConstFive" is unused`
137151
)
152+
153+
// -- vars --
154+
155+
var (
156+
usedVar int
157+
_, _, _ = Triple() // want `var "unusedVar4" is unused`
158+
)
159+
160+
func Triple() (int, int, int)

gopls/internal/analysis/unusedfunc/unusedfunc.go

Lines changed: 17 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
typeindexanalyzer "golang.org/x/tools/internal/analysisinternal/typeindex"
2121
"golang.org/x/tools/internal/astutil"
2222
"golang.org/x/tools/internal/packagepath"
23+
"golang.org/x/tools/internal/refactor"
2324
"golang.org/x/tools/internal/typesinternal/typeindex"
2425
)
2526

@@ -98,7 +99,7 @@ func run(pass *analysis.Pass) (any, error) {
9899

99100
// checkUnused reports a diagnostic if the object declared at id
100101
// is unexported and unused. References within curSelf are ignored.
101-
checkUnused := func(noun string, id *ast.Ident, node ast.Node, curSelf inspector.Cursor) {
102+
checkUnused := func(noun string, id *ast.Ident, curSelf inspector.Cursor, delete func() []analysis.TextEdit) {
102103
// Exported functions may be called from other packages.
103104
if id.IsExported() {
104105
return
@@ -118,28 +119,13 @@ func run(pass *analysis.Pass) (any, error) {
118119
}
119120
}
120121

121-
// Expand to include leading doc comment.
122-
pos := node.Pos()
123-
if doc := astutil.DocComment(node); doc != nil {
124-
pos = doc.Pos()
125-
}
126-
127-
// Expand to include trailing line comment.
128-
end := node.End()
129-
if doc := eolComment(node); doc != nil {
130-
end = doc.End()
131-
}
132-
133122
pass.Report(analysis.Diagnostic{
134123
Pos: id.Pos(),
135124
End: id.End(),
136125
Message: fmt.Sprintf("%s %q is unused", noun, id.Name),
137126
SuggestedFixes: []analysis.SuggestedFix{{
138-
Message: fmt.Sprintf("Delete %s %q", noun, id.Name),
139-
TextEdits: []analysis.TextEdit{{
140-
Pos: pos,
141-
End: end,
142-
}},
127+
Message: fmt.Sprintf("Delete %s %q", noun, id.Name),
128+
TextEdits: delete(),
143129
}},
144130
})
145131
}
@@ -159,6 +145,7 @@ func run(pass *analysis.Pass) (any, error) {
159145
if ast.IsGenerated(file) {
160146
continue // skip generated files
161147
}
148+
tokFile := pass.Fset.File(file.Pos())
162149

163150
nextDecl:
164151
for i := range file.Decls {
@@ -197,22 +184,22 @@ func run(pass *analysis.Pass) (any, error) {
197184
}
198185

199186
noun := cond(decl.Recv == nil, "function", "method")
200-
checkUnused(noun, decl.Name, decl, curDecl)
187+
checkUnused(noun, decl.Name, curDecl, func() []analysis.TextEdit {
188+
return refactor.DeleteDecl(tokFile, curDecl)
189+
})
201190

202191
case *ast.GenDecl:
203-
// Instead of deleting a spec in a singleton decl,
204-
// delete the whole decl.
205-
singleton := len(decl.Specs) == 1
206-
207192
switch decl.Tok {
208193
case token.TYPE:
209194
for i, spec := range decl.Specs {
210195
var (
211196
spec = spec.(*ast.TypeSpec)
212197
id = spec.Name
213-
curSelf = curDecl.ChildAt(edge.GenDecl_Specs, i)
198+
curSpec = curDecl.ChildAt(edge.GenDecl_Specs, i)
214199
)
215-
checkUnused("type", id, cond[ast.Node](singleton, decl, spec), curSelf)
200+
checkUnused("type", id, curSpec, func() []analysis.TextEdit {
201+
return refactor.DeleteSpec(tokFile, curSpec)
202+
})
216203
}
217204

218205
case token.CONST, token.VAR:
@@ -224,13 +211,12 @@ func run(pass *analysis.Pass) (any, error) {
224211
spec := spec.(*ast.ValueSpec)
225212
curSpec := curDecl.ChildAt(edge.GenDecl_Specs, i)
226213

227-
// Ignore n:n and n:1 assignments for now.
228-
// TODO(adonovan): support these cases.
229-
if len(spec.Names) != 1 {
230-
continue
214+
for j, id := range spec.Names {
215+
checkUnused(decl.Tok.String(), id, curSpec, func() []analysis.TextEdit {
216+
curId := curSpec.ChildAt(edge.ValueSpec_Names, j)
217+
return refactor.DeleteVar(tokFile, pass.TypesInfo, curId)
218+
})
231219
}
232-
id := spec.Names[0]
233-
checkUnused(decl.Tok.String(), id, cond[ast.Node](singleton, decl, spec), curSpec)
234220
}
235221
}
236222
}
@@ -240,22 +226,6 @@ func run(pass *analysis.Pass) (any, error) {
240226
return nil, nil
241227
}
242228

243-
func eolComment(n ast.Node) *ast.CommentGroup {
244-
// TODO(adonovan): support:
245-
// func f() {...} // comment
246-
switch n := n.(type) {
247-
case *ast.GenDecl:
248-
if !n.TokPos.IsValid() && len(n.Specs) == 1 {
249-
return eolComment(n.Specs[0])
250-
}
251-
case *ast.ValueSpec:
252-
return n.Comment
253-
case *ast.TypeSpec:
254-
return n.Comment
255-
}
256-
return nil
257-
}
258-
259229
func cond[T any](cond bool, t, f T) T {
260230
if cond {
261231
return t

gopls/internal/test/integration/diagnostics/analysis_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ package main
108108
import _ "embed"
109109
110110
//go:embed foo.txt
111-
var foo, bar string
111+
var foo, Bar string
112112
113113
func main() {
114114
_ = foo

gopls/internal/test/marker/testdata/mcptools/file_diagnostics.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ Fix:
2929
package main
3030

3131
-func foo() {} //@loc(foo, "foo")
32-
+ //@loc(foo, "foo")
32+
+
3333

3434
//@mcptool("go_file_diagnostics", `{"file":"$WORKDIR/a/main.go"}`, output=unused)
3535
//@diag(foo, re"unused")

internal/refactor/delete.go

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ import (
2121
"golang.org/x/tools/internal/typesinternal/typeindex"
2222
)
2323

24-
// DeleteVar returns edits to delete the declaration of a variable
25-
// whose defining identifier is curId.
24+
// DeleteVar returns edits to delete the declaration of a variable or
25+
// constant whose defining identifier is curId.
2626
//
2727
// It handles variants including:
2828
// - GenDecl > ValueSpec versus AssignStmt;
@@ -46,6 +46,9 @@ func DeleteVar(tokFile *token.File, info *types.Info, curId inspector.Cursor) []
4646
return nil
4747
}
4848

49+
// deleteVarFromValueSpec returns edits to delete the declaration of a
50+
// variable or constant within a ValueSpec.
51+
//
4952
// Precondition: curId is Ident beneath ValueSpec.Names beneath GenDecl.
5053
//
5154
// See also [deleteVarFromAssignStmt], which has parallel structure.
@@ -98,7 +101,7 @@ func deleteVarFromValueSpec(tokFile *token.File, info *types.Info, curIdent insp
98101
}}
99102
}
100103

101-
// If the assignment is 1:1 and the RHS has no effects,
104+
// If the assignment is n:n and the RHS has no effects,
102105
// we can delete the LHS and its corresponding RHS.
103106
if len(spec.Names) == len(spec.Values) &&
104107
typesinternal.NoEffects(info, spec.Values[index]) {
@@ -240,12 +243,12 @@ func deleteVarFromAssignStmt(tokFile *token.File, info *types.Info, curIdent ins
240243
return edits
241244
}
242245

243-
// DeleteSpec returns edits to delete the ValueSpec identified by curSpec.
246+
// DeleteSpec returns edits to delete the {Type,Value}Spec identified by curSpec.
244247
//
245248
// TODO(adonovan): add test suite. Test for consts as well.
246249
func DeleteSpec(tokFile *token.File, curSpec inspector.Cursor) []analysis.TextEdit {
247250
var (
248-
spec = curSpec.Node().(*ast.ValueSpec)
251+
spec = curSpec.Node().(ast.Spec)
249252
curDecl = curSpec.Parent()
250253
decl = curDecl.Node().(*ast.GenDecl)
251254
)
@@ -259,14 +262,14 @@ func DeleteSpec(tokFile *token.File, curSpec inspector.Cursor) []analysis.TextEd
259262
// Delete the spec and its comments.
260263
_, index := curSpec.ParentEdge() // index of ValueSpec within GenDecl.Specs
261264
pos, end := spec.Pos(), spec.End()
262-
if spec.Doc != nil {
263-
pos = spec.Doc.Pos() // leading comment
265+
if doc := astutil.DocComment(spec); doc != nil {
266+
pos = doc.Pos() // leading comment
264267
}
265268
if index == len(decl.Specs)-1 {
266269
// Delete final spec.
267-
if spec.Comment != nil {
270+
if c := eolComment(spec); c != nil {
268271
// var (v int // comment \n)
269-
end = spec.Comment.End()
272+
end = c.End()
270273
}
271274
} else {
272275
// Delete non-final spec.
@@ -463,3 +466,19 @@ func DeleteUnusedVars(index *typeindex.Index, info *types.Info, tokFile *token.F
463466
}
464467
return edits
465468
}
469+
470+
func eolComment(n ast.Node) *ast.CommentGroup {
471+
// TODO(adonovan): support:
472+
// func f() {...} // comment
473+
switch n := n.(type) {
474+
case *ast.GenDecl:
475+
if !n.TokPos.IsValid() && len(n.Specs) == 1 {
476+
return eolComment(n.Specs[0])
477+
}
478+
case *ast.ValueSpec:
479+
return n.Comment
480+
case *ast.TypeSpec:
481+
return n.Comment
482+
}
483+
return nil
484+
}

0 commit comments

Comments
 (0)