1
0
mirror of https://github.com/golang/go synced 2024-09-30 20:28:32 -06:00
go/internal/lsp/analysis/simplifyrange/simplifyrange.go
Rebecca Stambler 1fd976651f internal/lsp: make sure that gofmt -s analyses don't modify AST
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>
2020-04-06 21:01:14 +00:00

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