github.com/songshiyun/revive@v1.1.5-0.20220323112655-f8433a19b3c5/rule/empty-lines.go (about)

     1  package rule
     2  
     3  import (
     4  	"go/ast"
     5  	"go/token"
     6  
     7  	"github.com/songshiyun/revive/lint"
     8  )
     9  
    10  // EmptyLinesRule lints empty lines in blocks.
    11  type EmptyLinesRule struct{}
    12  
    13  // Apply applies the rule to given file.
    14  func (r *EmptyLinesRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure {
    15  	var failures []lint.Failure
    16  
    17  	onFailure := func(failure lint.Failure) {
    18  		failures = append(failures, failure)
    19  	}
    20  
    21  	w := lintEmptyLines{file, file.CommentMap(), onFailure}
    22  	ast.Walk(w, file.AST)
    23  	return failures
    24  }
    25  
    26  // Name returns the rule name.
    27  func (r *EmptyLinesRule) Name() string {
    28  	return "empty-lines"
    29  }
    30  
    31  type lintEmptyLines struct {
    32  	file      *lint.File
    33  	cmap      ast.CommentMap
    34  	onFailure func(lint.Failure)
    35  }
    36  
    37  func (w lintEmptyLines) Visit(node ast.Node) ast.Visitor {
    38  	block, ok := node.(*ast.BlockStmt)
    39  	if !ok {
    40  		return w
    41  	}
    42  
    43  	w.checkStart(block)
    44  	w.checkEnd(block)
    45  
    46  	return w
    47  }
    48  
    49  func (w lintEmptyLines) checkStart(block *ast.BlockStmt) {
    50  	if len(block.List) == 0 {
    51  		return
    52  	}
    53  
    54  	start := w.position(block.Lbrace)
    55  	firstNode := block.List[0]
    56  
    57  	if w.commentBetween(start, firstNode) {
    58  		return
    59  	}
    60  
    61  	first := w.position(firstNode.Pos())
    62  	if first.Line-start.Line > 1 {
    63  		w.onFailure(lint.Failure{
    64  			Confidence: 1,
    65  			Node:       block,
    66  			Category:   "style",
    67  			Failure:    "extra empty line at the start of a block",
    68  		})
    69  	}
    70  }
    71  
    72  func (w lintEmptyLines) checkEnd(block *ast.BlockStmt) {
    73  	if len(block.List) < 1 {
    74  		return
    75  	}
    76  
    77  	end := w.position(block.Rbrace)
    78  	lastNode := block.List[len(block.List)-1]
    79  
    80  	if w.commentBetween(end, lastNode) {
    81  		return
    82  	}
    83  
    84  	last := w.position(lastNode.End())
    85  	if end.Line-last.Line > 1 {
    86  		w.onFailure(lint.Failure{
    87  			Confidence: 1,
    88  			Node:       lastNode,
    89  			Category:   "style",
    90  			Failure:    "extra empty line at the end of a block",
    91  		})
    92  	}
    93  }
    94  
    95  func (w lintEmptyLines) commentBetween(position token.Position, node ast.Node) bool {
    96  	comments := w.cmap.Filter(node).Comments()
    97  	if len(comments) == 0 {
    98  		return false
    99  	}
   100  
   101  	for _, comment := range comments {
   102  		start, end := w.position(comment.Pos()), w.position(comment.End())
   103  		if start.Line-position.Line == 1 || position.Line-end.Line == 1 {
   104  			return true
   105  		}
   106  	}
   107  
   108  	return false
   109  }
   110  
   111  func (w lintEmptyLines) position(pos token.Pos) token.Position {
   112  	return w.file.ToPosition(pos)
   113  }