github.com/v2fly/tools@v0.100.0/internal/lsp/analysis/simplifyrange/simplifyrange.go (about)

     1  // Copyright 2020 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // Package simplifyrange defines an Analyzer that simplifies range statements.
     6  // https://golang.org/cmd/gofmt/#hdr-The_simplify_command
     7  // https://github.com/golang/go/blob/master/src/cmd/gofmt/simplify.go
     8  package simplifyrange
     9  
    10  import (
    11  	"bytes"
    12  	"go/ast"
    13  	"go/printer"
    14  	"go/token"
    15  
    16  	"github.com/v2fly/tools/go/analysis"
    17  	"github.com/v2fly/tools/go/analysis/passes/inspect"
    18  	"github.com/v2fly/tools/go/ast/inspector"
    19  )
    20  
    21  const Doc = `check for range statement simplifications
    22  
    23  A range of the form:
    24  	for x, _ = range v {...}
    25  will be simplified to:
    26  	for x = range v {...}
    27  
    28  A range of the form:
    29  	for _ = range v {...}
    30  will be simplified to:
    31  	for range v {...}
    32  
    33  This is one of the simplifications that "gofmt -s" applies.`
    34  
    35  var Analyzer = &analysis.Analyzer{
    36  	Name:     "simplifyrange",
    37  	Doc:      Doc,
    38  	Requires: []*analysis.Analyzer{inspect.Analyzer},
    39  	Run:      run,
    40  }
    41  
    42  func run(pass *analysis.Pass) (interface{}, error) {
    43  	inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
    44  	nodeFilter := []ast.Node{
    45  		(*ast.RangeStmt)(nil),
    46  	}
    47  	inspect.Preorder(nodeFilter, func(n ast.Node) {
    48  		var copy *ast.RangeStmt
    49  		if stmt, ok := n.(*ast.RangeStmt); ok {
    50  			x := *stmt
    51  			copy = &x
    52  		}
    53  		if copy == nil {
    54  			return
    55  		}
    56  		end := newlineIndex(pass.Fset, copy)
    57  
    58  		// Range statements of the form: for i, _ := range x {}
    59  		var old ast.Expr
    60  		if isBlank(copy.Value) {
    61  			old = copy.Value
    62  			copy.Value = nil
    63  		}
    64  		// Range statements of the form: for _ := range x {}
    65  		if isBlank(copy.Key) && copy.Value == nil {
    66  			old = copy.Key
    67  			copy.Key = nil
    68  		}
    69  		// Return early if neither if condition is met.
    70  		if old == nil {
    71  			return
    72  		}
    73  		pass.Report(analysis.Diagnostic{
    74  			Pos:            old.Pos(),
    75  			End:            old.End(),
    76  			Message:        "simplify range expression",
    77  			SuggestedFixes: suggestedFixes(pass.Fset, copy, end),
    78  		})
    79  	})
    80  	return nil, nil
    81  }
    82  
    83  func suggestedFixes(fset *token.FileSet, rng *ast.RangeStmt, end token.Pos) []analysis.SuggestedFix {
    84  	var b bytes.Buffer
    85  	printer.Fprint(&b, fset, rng)
    86  	stmt := b.Bytes()
    87  	index := bytes.Index(stmt, []byte("\n"))
    88  	// If there is a new line character, then don't replace the body.
    89  	if index != -1 {
    90  		stmt = stmt[:index]
    91  	}
    92  	return []analysis.SuggestedFix{{
    93  		Message: "Remove empty value",
    94  		TextEdits: []analysis.TextEdit{{
    95  			Pos:     rng.Pos(),
    96  			End:     end,
    97  			NewText: stmt[:index],
    98  		}},
    99  	}}
   100  }
   101  
   102  func newlineIndex(fset *token.FileSet, rng *ast.RangeStmt) token.Pos {
   103  	var b bytes.Buffer
   104  	printer.Fprint(&b, fset, rng)
   105  	contents := b.Bytes()
   106  	index := bytes.Index(contents, []byte("\n"))
   107  	if index == -1 {
   108  		return rng.End()
   109  	}
   110  	return rng.Pos() + token.Pos(index)
   111  }
   112  
   113  func isBlank(x ast.Expr) bool {
   114  	ident, ok := x.(*ast.Ident)
   115  	return ok && ident.Name == "_"
   116  }