blob: c9cb38798636face590e05a2f78fd38b27adbfce [file] [log] [blame]
// 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 == "_"
}