github.com/v2fly/tools@v0.100.0/internal/lsp/analysis/simplifyrange/simplifyrange.go (about) 1 // Copyright 2020 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 // Package simplifyrange defines an Analyzer that simplifies range statements. 6 // https://golang.org/cmd/gofmt/#hdr-The_simplify_command 7 // https://github.com/golang/go/blob/master/src/cmd/gofmt/simplify.go 8 package simplifyrange 9 10 import ( 11 "bytes" 12 "go/ast" 13 "go/printer" 14 "go/token" 15 16 "github.com/v2fly/tools/go/analysis" 17 "github.com/v2fly/tools/go/analysis/passes/inspect" 18 "github.com/v2fly/tools/go/ast/inspector" 19 ) 20 21 const Doc = `check for range statement simplifications 22 23 A range of the form: 24 for x, _ = range v {...} 25 will be simplified to: 26 for x = range v {...} 27 28 A range of the form: 29 for _ = range v {...} 30 will be simplified to: 31 for range v {...} 32 33 This is one of the simplifications that "gofmt -s" applies.` 34 35 var Analyzer = &analysis.Analyzer{ 36 Name: "simplifyrange", 37 Doc: Doc, 38 Requires: []*analysis.Analyzer{inspect.Analyzer}, 39 Run: run, 40 } 41 42 func run(pass *analysis.Pass) (interface{}, error) { 43 inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) 44 nodeFilter := []ast.Node{ 45 (*ast.RangeStmt)(nil), 46 } 47 inspect.Preorder(nodeFilter, func(n ast.Node) { 48 var copy *ast.RangeStmt 49 if stmt, ok := n.(*ast.RangeStmt); ok { 50 x := *stmt 51 copy = &x 52 } 53 if copy == nil { 54 return 55 } 56 end := newlineIndex(pass.Fset, copy) 57 58 // Range statements of the form: for i, _ := range x {} 59 var old ast.Expr 60 if isBlank(copy.Value) { 61 old = copy.Value 62 copy.Value = nil 63 } 64 // Range statements of the form: for _ := range x {} 65 if isBlank(copy.Key) && copy.Value == nil { 66 old = copy.Key 67 copy.Key = nil 68 } 69 // Return early if neither if condition is met. 70 if old == nil { 71 return 72 } 73 pass.Report(analysis.Diagnostic{ 74 Pos: old.Pos(), 75 End: old.End(), 76 Message: "simplify range expression", 77 SuggestedFixes: suggestedFixes(pass.Fset, copy, end), 78 }) 79 }) 80 return nil, nil 81 } 82 83 func suggestedFixes(fset *token.FileSet, rng *ast.RangeStmt, end token.Pos) []analysis.SuggestedFix { 84 var b bytes.Buffer 85 printer.Fprint(&b, fset, rng) 86 stmt := b.Bytes() 87 index := bytes.Index(stmt, []byte("\n")) 88 // If there is a new line character, then don't replace the body. 89 if index != -1 { 90 stmt = stmt[:index] 91 } 92 return []analysis.SuggestedFix{{ 93 Message: "Remove empty value", 94 TextEdits: []analysis.TextEdit{{ 95 Pos: rng.Pos(), 96 End: end, 97 NewText: stmt[:index], 98 }}, 99 }} 100 } 101 102 func newlineIndex(fset *token.FileSet, rng *ast.RangeStmt) token.Pos { 103 var b bytes.Buffer 104 printer.Fprint(&b, fset, rng) 105 contents := b.Bytes() 106 index := bytes.Index(contents, []byte("\n")) 107 if index == -1 { 108 return rng.End() 109 } 110 return rng.Pos() + token.Pos(index) 111 } 112 113 func isBlank(x ast.Expr) bool { 114 ident, ok := x.(*ast.Ident) 115 return ok && ident.Name == "_" 116 }