golang.org/x/tools/gopls@v0.15.3/internal/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
     6  
     7  import (
     8  	"bytes"
     9  	_ "embed"
    10  	"go/ast"
    11  	"go/printer"
    12  	"go/token"
    13  
    14  	"golang.org/x/tools/go/analysis"
    15  	"golang.org/x/tools/go/analysis/passes/inspect"
    16  	"golang.org/x/tools/go/ast/inspector"
    17  	"golang.org/x/tools/internal/analysisinternal"
    18  )
    19  
    20  //go:embed doc.go
    21  var doc string
    22  
    23  var Analyzer = &analysis.Analyzer{
    24  	Name:     "simplifyrange",
    25  	Doc:      analysisinternal.MustExtractDoc(doc, "simplifyrange"),
    26  	Requires: []*analysis.Analyzer{inspect.Analyzer},
    27  	Run:      run,
    28  	URL:      "https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/simplifyrange",
    29  }
    30  
    31  func run(pass *analysis.Pass) (interface{}, error) {
    32  	inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
    33  	nodeFilter := []ast.Node{
    34  		(*ast.RangeStmt)(nil),
    35  	}
    36  	inspect.Preorder(nodeFilter, func(n ast.Node) {
    37  		var copy *ast.RangeStmt
    38  		if stmt, ok := n.(*ast.RangeStmt); ok {
    39  			x := *stmt
    40  			copy = &x
    41  		}
    42  		if copy == nil {
    43  			return
    44  		}
    45  		end := newlineIndex(pass.Fset, copy)
    46  
    47  		// Range statements of the form: for i, _ := range x {}
    48  		var old ast.Expr
    49  		if isBlank(copy.Value) {
    50  			old = copy.Value
    51  			copy.Value = nil
    52  		}
    53  		// Range statements of the form: for _ := range x {}
    54  		if isBlank(copy.Key) && copy.Value == nil {
    55  			old = copy.Key
    56  			copy.Key = nil
    57  		}
    58  		// Return early if neither if condition is met.
    59  		if old == nil {
    60  			return
    61  		}
    62  		pass.Report(analysis.Diagnostic{
    63  			Pos:            old.Pos(),
    64  			End:            old.End(),
    65  			Message:        "simplify range expression",
    66  			SuggestedFixes: suggestedFixes(pass.Fset, copy, end),
    67  		})
    68  	})
    69  	return nil, nil
    70  }
    71  
    72  func suggestedFixes(fset *token.FileSet, rng *ast.RangeStmt, end token.Pos) []analysis.SuggestedFix {
    73  	var b bytes.Buffer
    74  	printer.Fprint(&b, fset, rng)
    75  	stmt := b.Bytes()
    76  	index := bytes.Index(stmt, []byte("\n"))
    77  	// If there is a new line character, then don't replace the body.
    78  	if index != -1 {
    79  		stmt = stmt[:index]
    80  	}
    81  	return []analysis.SuggestedFix{{
    82  		Message: "Remove empty value",
    83  		TextEdits: []analysis.TextEdit{{
    84  			Pos:     rng.Pos(),
    85  			End:     end,
    86  			NewText: stmt[:index],
    87  		}},
    88  	}}
    89  }
    90  
    91  func newlineIndex(fset *token.FileSet, rng *ast.RangeStmt) token.Pos {
    92  	var b bytes.Buffer
    93  	printer.Fprint(&b, fset, rng)
    94  	contents := b.Bytes()
    95  	index := bytes.Index(contents, []byte("\n"))
    96  	if index == -1 {
    97  		return rng.End()
    98  	}
    99  	return rng.Pos() + token.Pos(index)
   100  }
   101  
   102  func isBlank(x ast.Expr) bool {
   103  	ident, ok := x.(*ast.Ident)
   104  	return ok && ident.Name == "_"
   105  }