// 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 == "_" }