1
2
3
4
5 package refactor
6
7
8
9 import (
10 "fmt"
11 "go/ast"
12 "go/token"
13 "go/types"
14 "slices"
15
16 "golang.org/x/tools/go/ast/edge"
17 "golang.org/x/tools/go/ast/inspector"
18 "golang.org/x/tools/internal/astutil"
19 "golang.org/x/tools/internal/typesinternal"
20 "golang.org/x/tools/internal/typesinternal/typeindex"
21 )
22
23
24
25
26
27
28
29
30
31
32
33
34 func DeleteVar(tokFile *token.File, info *types.Info, curId inspector.Cursor) []Edit {
35 switch ek, _ := curId.ParentEdge(); ek {
36 case edge.ValueSpec_Names:
37 return deleteVarFromValueSpec(tokFile, info, curId)
38
39 case edge.AssignStmt_Lhs:
40 return deleteVarFromAssignStmt(tokFile, info, curId)
41 }
42
43
44
45 return nil
46 }
47
48
49
50
51
52
53
54 func deleteVarFromValueSpec(tokFile *token.File, info *types.Info, curIdent inspector.Cursor) []Edit {
55 var (
56 id = curIdent.Node().(*ast.Ident)
57 curSpec = curIdent.Parent()
58 spec = curSpec.Node().(*ast.ValueSpec)
59 )
60
61 declaresOtherNames := slices.ContainsFunc(spec.Names, func(name *ast.Ident) bool {
62 return name != id && name.Name != "_"
63 })
64 noRHSEffects := !slices.ContainsFunc(spec.Values, func(rhs ast.Expr) bool {
65 return !typesinternal.NoEffects(info, rhs)
66 })
67 if !declaresOtherNames && noRHSEffects {
68
69
70 return DeleteSpec(tokFile, curSpec)
71 }
72
73
74
75
76
77 _, index := curIdent.ParentEdge()
78
79
80 if len(spec.Values) == 0 {
81 var pos, end token.Pos
82 if index == len(spec.Names)-1 {
83
84
85
86
87 pos = spec.Names[index-1].End()
88 end = spec.Names[index].End()
89 } else {
90
91
92
93
94 pos = spec.Names[index].Pos()
95 end = spec.Names[index+1].Pos()
96 }
97 return []Edit{{
98 Pos: pos,
99 End: end,
100 }}
101 }
102
103
104
105 if len(spec.Names) == len(spec.Values) &&
106 typesinternal.NoEffects(info, spec.Values[index]) {
107
108 if index == len(spec.Names)-1 {
109
110
111
112
113 return []Edit{
114 {
115 Pos: spec.Names[index-1].End(),
116 End: spec.Names[index].End(),
117 },
118 {
119 Pos: spec.Values[index-1].End(),
120 End: spec.Values[index].End(),
121 },
122 }
123 } else {
124
125
126
127
128 return []Edit{
129 {
130 Pos: spec.Names[index].Pos(),
131 End: spec.Names[index+1].Pos(),
132 },
133 {
134 Pos: spec.Values[index].Pos(),
135 End: spec.Values[index+1].Pos(),
136 },
137 }
138 }
139 }
140
141
142
143 return []Edit{{
144 Pos: id.Pos(),
145 End: id.End(),
146 NewText: []byte("_"),
147 }}
148 }
149
150
151
152
153 func deleteVarFromAssignStmt(tokFile *token.File, info *types.Info, curIdent inspector.Cursor) []Edit {
154 var (
155 id = curIdent.Node().(*ast.Ident)
156 curStmt = curIdent.Parent()
157 assign = curStmt.Node().(*ast.AssignStmt)
158 )
159
160 declaresOtherNames := slices.ContainsFunc(assign.Lhs, func(lhs ast.Expr) bool {
161 lhsId, ok := lhs.(*ast.Ident)
162 return ok && lhsId != id && lhsId.Name != "_"
163 })
164 noRHSEffects := !slices.ContainsFunc(assign.Rhs, func(rhs ast.Expr) bool {
165 return !typesinternal.NoEffects(info, rhs)
166 })
167 if !declaresOtherNames && noRHSEffects {
168
169
170 if edits := DeleteStmt(tokFile, curStmt); edits != nil {
171 return edits
172 }
173
174
175 }
176
177
178
179
180
181
182
183
184 _, index := curIdent.ParentEdge()
185 if len(assign.Lhs) > 1 &&
186 len(assign.Lhs) == len(assign.Rhs) &&
187 typesinternal.NoEffects(info, assign.Rhs[index]) {
188
189 if index == len(assign.Lhs)-1 {
190
191
192
193
194 return []Edit{
195 {
196 Pos: assign.Lhs[index-1].End(),
197 End: assign.Lhs[index].End(),
198 },
199 {
200 Pos: assign.Rhs[index-1].End(),
201 End: assign.Rhs[index].End(),
202 },
203 }
204 } else {
205
206
207
208
209 return []Edit{
210 {
211 Pos: assign.Lhs[index].Pos(),
212 End: assign.Lhs[index+1].Pos(),
213 },
214 {
215 Pos: assign.Rhs[index].Pos(),
216 End: assign.Rhs[index+1].Pos(),
217 },
218 }
219 }
220 }
221
222
223
224 edits := []Edit{{
225 Pos: id.Pos(),
226 End: id.End(),
227 NewText: []byte("_"),
228 }}
229
230
231
232
233
234 if !declaresOtherNames {
235 edits = append(edits, Edit{
236 Pos: assign.TokPos,
237 End: assign.TokPos + token.Pos(len(":=")),
238 NewText: []byte("="),
239 })
240 }
241
242 return edits
243 }
244
245
246
247
248 func DeleteSpec(tokFile *token.File, curSpec inspector.Cursor) []Edit {
249 var (
250 spec = curSpec.Node().(ast.Spec)
251 curDecl = curSpec.Parent()
252 decl = curDecl.Node().(*ast.GenDecl)
253 )
254
255
256
257 if len(decl.Specs) == 1 {
258 return DeleteDecl(tokFile, curDecl)
259 }
260
261
262 _, index := curSpec.ParentEdge()
263 pos, end := spec.Pos(), spec.End()
264 if doc := astutil.DocComment(spec); doc != nil {
265 pos = doc.Pos()
266 }
267 if index == len(decl.Specs)-1 {
268
269 if c := eolComment(spec); c != nil {
270
271 end = c.End()
272 }
273 } else {
274
275
276
277 end = decl.Specs[index+1].Pos()
278 }
279 return []Edit{{
280 Pos: pos,
281 End: end,
282 }}
283 }
284
285
286
287
288 func DeleteDecl(tokFile *token.File, curDecl inspector.Cursor) []Edit {
289 decl := curDecl.Node().(ast.Decl)
290
291 ek, _ := curDecl.ParentEdge()
292 switch ek {
293 case edge.DeclStmt_Decl:
294 return DeleteStmt(tokFile, curDecl.Parent())
295
296 case edge.File_Decls:
297 pos, end := decl.Pos(), decl.End()
298 if doc := astutil.DocComment(decl); doc != nil {
299 pos = doc.Pos()
300 }
301
302
303
304 var (
305 file = curDecl.Parent().Node().(*ast.File)
306 lineOf = tokFile.Line
307 declEndLine = lineOf(decl.End())
308 )
309 for _, cg := range file.Comments {
310 for _, c := range cg.List {
311 if c.Pos() < end {
312 continue
313 }
314 commentEndLine := lineOf(c.End())
315 if commentEndLine > declEndLine {
316 break
317 } else if lineOf(c.Pos()) == declEndLine && commentEndLine == declEndLine {
318 end = c.End()
319 }
320 }
321 }
322
323 return []Edit{{
324 Pos: pos,
325 End: end,
326 }}
327
328 default:
329 panic(fmt.Sprintf("Decl parent is %v, want DeclStmt or File", ek))
330 }
331 }
332
333
334 func filterPos(nds []*ast.Comment, start, end token.Pos) (token.Pos, token.Pos, bool) {
335 l, r := end, token.NoPos
336 ok := false
337 for _, n := range nds {
338 if n.Pos() > start && n.Pos() < l {
339 l = n.Pos()
340 ok = true
341 }
342 if n.End() <= end && n.End() > r {
343 r = n.End()
344 ok = true
345 }
346 }
347 return l, r, ok
348 }
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368 func DeleteStmt(file *token.File, curStmt inspector.Cursor) []Edit {
369
370
371
372
373
374
375
376
377
378
379
380
381 var (
382 stmt = curStmt.Node().(ast.Stmt)
383 tokFile = file
384 lineOf = tokFile.Line
385 stmtStartLine = lineOf(stmt.Pos())
386 stmtEndLine = lineOf(stmt.End())
387
388 leftSyntax, rightSyntax token.Pos
389 leftComments, rightComments []*ast.Comment
390 )
391
392
393 use := func(left, right token.Pos) {
394 if lineOf(left) == stmtStartLine {
395 leftSyntax = left
396 }
397 if lineOf(right) == stmtEndLine {
398 rightSyntax = right
399 }
400 }
401
402
403 Big:
404 for _, cg := range astutil.EnclosingFile(curStmt).Comments {
405 for _, co := range cg.List {
406 if lineOf(co.End()) < stmtStartLine {
407 continue
408 } else if lineOf(co.Pos()) > stmtEndLine {
409 break Big
410 }
411 if lineOf(co.End()) == stmtStartLine && co.End() <= stmt.Pos() {
412
413 leftComments = append(leftComments, co)
414 } else if lineOf(co.Pos()) == stmtEndLine && co.Pos() >= stmt.End() {
415
416 rightComments = append(rightComments, co)
417 }
418 }
419 }
420
421
422 var (
423 leftStmt, rightStmt token.Pos
424 inStmtList = false
425 curParent = curStmt.Parent()
426 )
427 switch parent := curParent.Node().(type) {
428 case *ast.BlockStmt:
429 use(parent.Lbrace, parent.Rbrace)
430 inStmtList = true
431 case *ast.CaseClause:
432 use(parent.Colon, curStmt.Parent().Parent().Node().(*ast.BlockStmt).Rbrace)
433 inStmtList = true
434 case *ast.CommClause:
435 if parent.Comm == stmt {
436 return nil
437 }
438 use(parent.Colon, curStmt.Parent().Parent().Node().(*ast.BlockStmt).Rbrace)
439 inStmtList = true
440 case *ast.ForStmt:
441 use(parent.For, parent.Body.Lbrace)
442
443 if parent.Init != nil && parent.Cond != nil && stmt == parent.Init && lineOf(parent.Cond.Pos()) == lineOf(stmt.End()) {
444 rightStmt = parent.Cond.Pos()
445 } else if parent.Post != nil && parent.Cond != nil && stmt == parent.Post && lineOf(parent.Cond.End()) == lineOf(stmt.Pos()) {
446 leftStmt = parent.Cond.End()
447 }
448 case *ast.IfStmt:
449 switch stmt {
450 case parent.Init:
451 use(parent.If, parent.Body.Lbrace)
452 case parent.Else:
453
454
455
456 return nil
457 }
458 case *ast.SwitchStmt:
459 use(parent.Switch, parent.Body.Lbrace)
460 case *ast.TypeSwitchStmt:
461 if stmt == parent.Assign {
462 return nil
463 }
464 use(parent.Switch, parent.Body.Lbrace)
465 default:
466 return nil
467 }
468
469 if inStmtList {
470
471 if prev, found := curStmt.PrevSibling(); found && lineOf(prev.Node().End()) == stmtStartLine {
472 if _, ok := prev.Node().(ast.Stmt); ok {
473 leftStmt = prev.Node().End()
474 }
475 }
476 if next, found := curStmt.NextSibling(); found && lineOf(next.Node().Pos()) == stmtEndLine {
477 rightStmt = next.Node().Pos()
478 }
479 }
480
481
482 var leftEdit, rightEdit token.Pos
483 if leftStmt.IsValid() {
484 leftEdit = stmt.Pos()
485 } else if leftSyntax.IsValid() {
486
487 if a, _, ok := filterPos(leftComments, leftSyntax, stmt.Pos()); ok {
488 leftEdit = a
489 } else {
490 leftEdit = stmt.Pos()
491 }
492 } else {
493 for leftEdit = stmt.Pos(); lineOf(leftEdit) == stmtStartLine; leftEdit-- {
494 }
495 if leftEdit < stmt.Pos() {
496 leftEdit++
497 }
498 }
499 if rightStmt.IsValid() {
500 rightEdit = stmt.End()
501 } else if rightSyntax.IsValid() {
502
503 if _, b, ok := filterPos(rightComments, stmt.End(), rightSyntax); ok {
504 rightEdit = b
505 } else {
506 rightEdit = stmt.End()
507 }
508 } else {
509 fend := token.Pos(file.Base()) + token.Pos(file.Size())
510 for rightEdit = stmt.End(); fend >= rightEdit && lineOf(rightEdit) == stmtEndLine; rightEdit++ {
511 }
512
513 if leftSyntax.IsValid() || leftStmt.IsValid() {
514 rightEdit--
515 }
516 }
517
518 return []Edit{{Pos: leftEdit, End: rightEdit}}
519 }
520
521
522
523
524 func DeleteUnusedVars(index *typeindex.Index, info *types.Info, tokFile *token.File, curDelend inspector.Cursor) []Edit {
525
526
527
528
529
530
531 delcount := make(map[*types.Var]int)
532 for curId := range curDelend.Preorder((*ast.Ident)(nil)) {
533 id := curId.Node().(*ast.Ident)
534 if v, ok := info.Uses[id].(*types.Var); ok &&
535 typesinternal.GetVarKind(v) == typesinternal.LocalVar {
536 delcount[v]++
537 }
538 }
539
540
541 var edits []Edit
542 for v, count := range delcount {
543 if len(slices.Collect(index.Uses(v))) == count {
544 if curDefId, ok := index.Def(v); ok {
545 edits = append(edits, DeleteVar(tokFile, info, curDefId)...)
546 }
547 }
548 }
549 return edits
550 }
551
552 func eolComment(n ast.Node) *ast.CommentGroup {
553
554
555 switch n := n.(type) {
556 case *ast.GenDecl:
557 if !n.TokPos.IsValid() && len(n.Specs) == 1 {
558 return eolComment(n.Specs[0])
559 }
560 case *ast.ValueSpec:
561 return n.Comment
562 case *ast.TypeSpec:
563 return n.Comment
564 }
565 return nil
566 }
567
View as plain text