|  | // Copyright 2020 The Go Authors. All rights reserved. | 
|  | // Use of this source code is governed by a BSD-style | 
|  | // license that can be found in the LICENSE file. | 
|  |  | 
|  | // Package simplifyrange defines an Analyzer that simplifies range statements. | 
|  | // https://golang.org/cmd/gofmt/#hdr-The_simplify_command | 
|  | // https://github.com/golang/go/blob/master/src/cmd/gofmt/simplify.go | 
|  | package simplifyrange | 
|  |  | 
|  | import ( | 
|  | "bytes" | 
|  | "go/ast" | 
|  | "go/printer" | 
|  | "go/token" | 
|  |  | 
|  | "golang.org/x/tools/go/analysis" | 
|  | "golang.org/x/tools/go/analysis/passes/inspect" | 
|  | "golang.org/x/tools/go/ast/inspector" | 
|  | ) | 
|  |  | 
|  | const Doc = `check for range statement simplifications | 
|  |  | 
|  | A range of the form: | 
|  | for x, _ = range v {...} | 
|  | will be simplified to: | 
|  | for x = range v {...} | 
|  |  | 
|  | A range of the form: | 
|  | for _ = range v {...} | 
|  | will be simplified to: | 
|  | for range v {...} | 
|  |  | 
|  | This is one of the simplifications that "gofmt -s" applies.` | 
|  |  | 
|  | var Analyzer = &analysis.Analyzer{ | 
|  | Name:     "simplifyrange", | 
|  | Doc:      Doc, | 
|  | Requires: []*analysis.Analyzer{inspect.Analyzer}, | 
|  | Run:      run, | 
|  | } | 
|  |  | 
|  | func run(pass *analysis.Pass) (interface{}, error) { | 
|  | inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) | 
|  | nodeFilter := []ast.Node{ | 
|  | (*ast.RangeStmt)(nil), | 
|  | } | 
|  | inspect.Preorder(nodeFilter, func(n ast.Node) { | 
|  | var copy *ast.RangeStmt | 
|  | if stmt, ok := n.(*ast.RangeStmt); ok { | 
|  | x := *stmt | 
|  | copy = &x | 
|  | } | 
|  | if copy == nil { | 
|  | return | 
|  | } | 
|  | end := newlineIndex(pass.Fset, copy) | 
|  |  | 
|  | // Range statements of the form: for i, _ := range x {} | 
|  | var old ast.Expr | 
|  | if isBlank(copy.Value) { | 
|  | old = copy.Value | 
|  | copy.Value = nil | 
|  | } | 
|  | // Range statements of the form: for _ := range x {} | 
|  | if isBlank(copy.Key) && copy.Value == nil { | 
|  | old = copy.Key | 
|  | copy.Key = nil | 
|  | } | 
|  | // Return early if neither if condition is met. | 
|  | if old == nil { | 
|  | return | 
|  | } | 
|  | pass.Report(analysis.Diagnostic{ | 
|  | Pos:            old.Pos(), | 
|  | End:            old.End(), | 
|  | Message:        "simplify range expression", | 
|  | SuggestedFixes: suggestedFixes(pass.Fset, copy, end), | 
|  | }) | 
|  | }) | 
|  | return nil, nil | 
|  | } | 
|  |  | 
|  | func suggestedFixes(fset *token.FileSet, rng *ast.RangeStmt, end token.Pos) []analysis.SuggestedFix { | 
|  | var b bytes.Buffer | 
|  | printer.Fprint(&b, fset, rng) | 
|  | stmt := b.Bytes() | 
|  | index := bytes.Index(stmt, []byte("\n")) | 
|  | // If there is a new line character, then don't replace the body. | 
|  | if index != -1 { | 
|  | stmt = stmt[:index] | 
|  | } | 
|  | return []analysis.SuggestedFix{{ | 
|  | Message: "Remove empty value", | 
|  | TextEdits: []analysis.TextEdit{{ | 
|  | Pos:     rng.Pos(), | 
|  | End:     end, | 
|  | NewText: stmt[:index], | 
|  | }}, | 
|  | }} | 
|  | } | 
|  |  | 
|  | func newlineIndex(fset *token.FileSet, rng *ast.RangeStmt) token.Pos { | 
|  | var b bytes.Buffer | 
|  | printer.Fprint(&b, fset, rng) | 
|  | contents := b.Bytes() | 
|  | index := bytes.Index(contents, []byte("\n")) | 
|  | if index == -1 { | 
|  | return rng.End() | 
|  | } | 
|  | return rng.Pos() + token.Pos(index) | 
|  | } | 
|  |  | 
|  | func isBlank(x ast.Expr) bool { | 
|  | ident, ok := x.(*ast.Ident) | 
|  | return ok && ident.Name == "_" | 
|  | } |