@@ -13,10 +13,12 @@ import (
1313 "go/token"
1414 "go/types"
1515 "regexp"
16+ "slices"
1617 "strings"
1718
1819 "golang.org/x/tools/go/analysis"
1920 "golang.org/x/tools/go/ast/astutil"
21+ "golang.org/x/tools/gopls/internal/util/bug"
2022 "golang.org/x/tools/gopls/internal/util/safetoken"
2123)
2224
@@ -165,16 +167,13 @@ func removeVariableFromSpec(pass *analysis.Pass, path []ast.Node, stmt *ast.Valu
165167 // Find parent DeclStmt and delete it
166168 for _ , node := range path {
167169 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 {{
174172 Message : suggestedFixMessage (ident .Name ),
175173 TextEdits : edits ,
176- },
174+ }}
177175 }
176+ return nil
178177 }
179178 }
180179 }
@@ -222,16 +221,13 @@ func removeVariableFromAssignment(fset *token.FileSet, path []ast.Node, stmt *as
222221 }
223222
224223 // 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 {{
231226 Message : suggestedFixMessage (ident .Name ),
232227 TextEdits : edits ,
233- },
228+ }}
234229 }
230+ return nil
235231 }
236232
237233 // Otherwise replace ident with `_`
@@ -253,34 +249,48 @@ func suggestedFixMessage(name string) string {
253249 return fmt .Sprintf ("Remove variable %s" , name )
254250}
255251
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.
256262func 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
264268 }
265269
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
272274 }
273275
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
276278 return nil
277279 }
278280
279281 // Delete until the end of the block unless there is another statement after
280282 // the one we are trying to delete
281283 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+ }
282288 if nodeIndex < len (block .List )- 1 {
283289 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+ }
284294 }
285295
286296 // Account for comments within the block containing the statement
@@ -298,7 +308,7 @@ outer:
298308 // If a comment exists within the current block, after the unused variable statement,
299309 // and before the next statement, we shouldn't delete it.
300310 if coLine > stmtEndLine {
301- end = co .Pos ()
311+ end = co .Pos () // preserves invariant stmt.Pos <= end (#71812)
302312 break outer
303313 }
304314 if co .Pos () > end {
@@ -308,12 +318,11 @@ outer:
308318 }
309319 }
310320
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+ }}
317326}
318327
319328// exprMayHaveSideEffects reports whether the expression may have side effects
0 commit comments