@@ -13,10 +13,12 @@ import (
13
13
"go/token"
14
14
"go/types"
15
15
"regexp"
16
+ "slices"
16
17
"strings"
17
18
18
19
"golang.org/x/tools/go/analysis"
19
20
"golang.org/x/tools/go/ast/astutil"
21
+ "golang.org/x/tools/gopls/internal/util/bug"
20
22
"golang.org/x/tools/gopls/internal/util/safetoken"
21
23
)
22
24
@@ -165,16 +167,13 @@ func removeVariableFromSpec(pass *analysis.Pass, path []ast.Node, stmt *ast.Valu
165
167
// Find parent DeclStmt and delete it
166
168
for _ , node := range path {
167
169
if declStmt , ok := node .(* ast.DeclStmt ); ok {
168
- edits := deleteStmtFromBlock (pass .Fset , path , declStmt )
169
- if len (edits ) == 0 {
170
- return nil // can this happen?
171
- }
172
- return []analysis.SuggestedFix {
173
- {
170
+ if edits := deleteStmtFromBlock (pass .Fset , path , declStmt ); len (edits ) > 0 {
171
+ return []analysis.SuggestedFix {{
174
172
Message : suggestedFixMessage (ident .Name ),
175
173
TextEdits : edits ,
176
- },
174
+ }}
177
175
}
176
+ return nil
178
177
}
179
178
}
180
179
}
@@ -222,16 +221,13 @@ func removeVariableFromAssignment(fset *token.FileSet, path []ast.Node, stmt *as
222
221
}
223
222
224
223
// RHS does not have any side effects, delete the whole statement
225
- edits := deleteStmtFromBlock (fset , path , stmt )
226
- if len (edits ) == 0 {
227
- return nil // can this happen?
228
- }
229
- return []analysis.SuggestedFix {
230
- {
224
+ if edits := deleteStmtFromBlock (fset , path , stmt ); len (edits ) > 0 {
225
+ return []analysis.SuggestedFix {{
231
226
Message : suggestedFixMessage (ident .Name ),
232
227
TextEdits : edits ,
233
- },
228
+ }}
234
229
}
230
+ return nil
235
231
}
236
232
237
233
// Otherwise replace ident with `_`
@@ -253,34 +249,48 @@ func suggestedFixMessage(name string) string {
253
249
return fmt .Sprintf ("Remove variable %s" , name )
254
250
}
255
251
252
+ // deleteStmtFromBlock returns the edits to remove stmt if its parent is a BlockStmt.
253
+ // (stmt is not necessarily the leaf, path[0].)
254
+ //
255
+ // It returns nil if the parent is not a block, as in these examples:
256
+ //
257
+ // switch STMT; {}
258
+ // switch { default: STMT }
259
+ // select { default: STMT }
260
+ //
261
+ // TODO(adonovan): handle these cases too.
256
262
func deleteStmtFromBlock (fset * token.FileSet , path []ast.Node , stmt ast.Stmt ) []analysis.TextEdit {
257
- // Find innermost enclosing BlockStmt.
258
- var block * ast.BlockStmt
259
- for i := range path {
260
- if blockStmt , ok := path [i ].(* ast.BlockStmt ); ok {
261
- block = blockStmt
262
- break
263
- }
263
+ // TODO(adonovan): simplify using Cursor API.
264
+ i := slices .Index (path , ast .Node (stmt )) // must be present
265
+ block , ok := path [i + 1 ].(* ast.BlockStmt )
266
+ if ! ok {
267
+ return nil // parent is not a BlockStmt
264
268
}
265
269
266
- nodeIndex := - 1
267
- for i , blockStmt := range block .List {
268
- if blockStmt == stmt {
269
- nodeIndex = i
270
- break
271
- }
270
+ nodeIndex := slices .Index (block .List , stmt )
271
+ if nodeIndex == - 1 {
272
+ bug .Reportf ("%s: Stmt not found in BlockStmt.List" , safetoken .StartPosition (fset , stmt .Pos ())) // refine #71812
273
+ return nil
272
274
}
273
275
274
- // The statement we need to delete was not found in BlockStmt
275
- if nodeIndex == - 1 {
276
+ if ! stmt . Pos (). IsValid () {
277
+ bug . Reportf ( "%s: invalid Stmt.Pos" , safetoken . StartPosition ( fset , stmt . Pos ())) // refine #71812
276
278
return nil
277
279
}
278
280
279
281
// Delete until the end of the block unless there is another statement after
280
282
// the one we are trying to delete
281
283
end := block .Rbrace
284
+ if ! end .IsValid () {
285
+ bug .Reportf ("%s: BlockStmt has no Rbrace" , safetoken .StartPosition (fset , block .Pos ())) // refine #71812
286
+ return nil
287
+ }
282
288
if nodeIndex < len (block .List )- 1 {
283
289
end = block .List [nodeIndex + 1 ].Pos ()
290
+ if end < stmt .Pos () {
291
+ bug .Reportf ("%s: BlockStmt.List[last].Pos > BlockStmt.Rbrace" , safetoken .StartPosition (fset , block .Pos ())) // refine #71812
292
+ return nil
293
+ }
284
294
}
285
295
286
296
// Account for comments within the block containing the statement
@@ -298,7 +308,7 @@ outer:
298
308
// If a comment exists within the current block, after the unused variable statement,
299
309
// and before the next statement, we shouldn't delete it.
300
310
if coLine > stmtEndLine {
301
- end = co .Pos ()
311
+ end = co .Pos () // preserves invariant stmt.Pos <= end (#71812)
302
312
break outer
303
313
}
304
314
if co .Pos () > end {
@@ -308,12 +318,11 @@ outer:
308
318
}
309
319
}
310
320
311
- return []analysis.TextEdit {
312
- {
313
- Pos : stmt .Pos (),
314
- End : end ,
315
- },
316
- }
321
+ // Delete statement and optional following comment.
322
+ return []analysis.TextEdit {{
323
+ Pos : stmt .Pos (),
324
+ End : end ,
325
+ }}
317
326
}
318
327
319
328
// exprMayHaveSideEffects reports whether the expression may have side effects
0 commit comments