golang.org/x/tools/gopls@v0.15.3/internal/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 6 7 import ( 8 "bytes" 9 _ "embed" 10 "go/ast" 11 "go/printer" 12 "go/token" 13 14 "golang.org/x/tools/go/analysis" 15 "golang.org/x/tools/go/analysis/passes/inspect" 16 "golang.org/x/tools/go/ast/inspector" 17 "golang.org/x/tools/internal/analysisinternal" 18 ) 19 20 //go:embed doc.go 21 var doc string 22 23 var Analyzer = &analysis.Analyzer{ 24 Name: "simplifyrange", 25 Doc: analysisinternal.MustExtractDoc(doc, "simplifyrange"), 26 Requires: []*analysis.Analyzer{inspect.Analyzer}, 27 Run: run, 28 URL: "https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/simplifyrange", 29 } 30 31 func run(pass *analysis.Pass) (interface{}, error) { 32 inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) 33 nodeFilter := []ast.Node{ 34 (*ast.RangeStmt)(nil), 35 } 36 inspect.Preorder(nodeFilter, func(n ast.Node) { 37 var copy *ast.RangeStmt 38 if stmt, ok := n.(*ast.RangeStmt); ok { 39 x := *stmt 40 copy = &x 41 } 42 if copy == nil { 43 return 44 } 45 end := newlineIndex(pass.Fset, copy) 46 47 // Range statements of the form: for i, _ := range x {} 48 var old ast.Expr 49 if isBlank(copy.Value) { 50 old = copy.Value 51 copy.Value = nil 52 } 53 // Range statements of the form: for _ := range x {} 54 if isBlank(copy.Key) && copy.Value == nil { 55 old = copy.Key 56 copy.Key = nil 57 } 58 // Return early if neither if condition is met. 59 if old == nil { 60 return 61 } 62 pass.Report(analysis.Diagnostic{ 63 Pos: old.Pos(), 64 End: old.End(), 65 Message: "simplify range expression", 66 SuggestedFixes: suggestedFixes(pass.Fset, copy, end), 67 }) 68 }) 69 return nil, nil 70 } 71 72 func suggestedFixes(fset *token.FileSet, rng *ast.RangeStmt, end token.Pos) []analysis.SuggestedFix { 73 var b bytes.Buffer 74 printer.Fprint(&b, fset, rng) 75 stmt := b.Bytes() 76 index := bytes.Index(stmt, []byte("\n")) 77 // If there is a new line character, then don't replace the body. 78 if index != -1 { 79 stmt = stmt[:index] 80 } 81 return []analysis.SuggestedFix{{ 82 Message: "Remove empty value", 83 TextEdits: []analysis.TextEdit{{ 84 Pos: rng.Pos(), 85 End: end, 86 NewText: stmt[:index], 87 }}, 88 }} 89 } 90 91 func newlineIndex(fset *token.FileSet, rng *ast.RangeStmt) token.Pos { 92 var b bytes.Buffer 93 printer.Fprint(&b, fset, rng) 94 contents := b.Bytes() 95 index := bytes.Index(contents, []byte("\n")) 96 if index == -1 { 97 return rng.End() 98 } 99 return rng.Pos() + token.Pos(index) 100 } 101 102 func isBlank(x ast.Expr) bool { 103 ident, ok := x.(*ast.Ident) 104 return ok && ident.Name == "_" 105 }