mirror of
https://github.com/golang/go
synced 2024-11-18 15:34:53 -07:00
1fd976651f
The code for `gofmt -s` directly modifies the AST, since the ASTs are not long-lived. Some of this code made it into our analysis implementations, causing very strange bugs to manifest. Added a regression test for this specific case. Fixes golang/go#38267 Change-Id: I235620adcbf2bbc7027c6d83ff2c7fe74729062e Reviewed-on: https://go-review.googlesource.com/c/tools/+/227299 Run-TryBot: Rebecca Stambler <rstambler@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Heschi Kreinick <heschi@google.com>
117 lines
2.8 KiB
Go
117 lines
2.8 KiB
Go
// 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 == "_"
|
|
}
|