159 lines
3.6 KiB
Go
159 lines
3.6 KiB
Go
package whitespace
|
|
|
|
import (
|
|
"go/ast"
|
|
"go/token"
|
|
)
|
|
|
|
// Message contains a message
|
|
type Message struct {
|
|
Pos token.Position
|
|
Type MessageType
|
|
Message string
|
|
}
|
|
|
|
// MessageType describes what should happen to fix the warning
|
|
type MessageType uint8
|
|
|
|
// List of MessageTypes
|
|
const (
|
|
MessageTypeLeading MessageType = iota + 1
|
|
MessageTypeTrailing
|
|
MessageTypeAddAfter
|
|
)
|
|
|
|
// Settings contains settings for edge-cases
|
|
type Settings struct {
|
|
MultiIf bool
|
|
MultiFunc bool
|
|
}
|
|
|
|
// Run runs this linter on the provided code
|
|
func Run(file *ast.File, fset *token.FileSet, settings Settings) []Message {
|
|
var messages []Message
|
|
|
|
for _, f := range file.Decls {
|
|
decl, ok := f.(*ast.FuncDecl)
|
|
if !ok || decl.Body == nil { // decl.Body can be nil for e.g. cgo
|
|
continue
|
|
}
|
|
|
|
vis := visitor{file.Comments, fset, nil, make(map[*ast.BlockStmt]bool), settings}
|
|
ast.Walk(&vis, decl)
|
|
|
|
messages = append(messages, vis.messages...)
|
|
}
|
|
|
|
return messages
|
|
}
|
|
|
|
type visitor struct {
|
|
comments []*ast.CommentGroup
|
|
fset *token.FileSet
|
|
messages []Message
|
|
wantNewline map[*ast.BlockStmt]bool
|
|
settings Settings
|
|
}
|
|
|
|
func (v *visitor) Visit(node ast.Node) ast.Visitor {
|
|
if node == nil {
|
|
return v
|
|
}
|
|
|
|
if stmt, ok := node.(*ast.IfStmt); ok && v.settings.MultiIf {
|
|
checkMultiLine(v, stmt.Body, stmt.Cond)
|
|
}
|
|
|
|
if stmt, ok := node.(*ast.FuncDecl); ok && v.settings.MultiFunc {
|
|
checkMultiLine(v, stmt.Body, stmt.Type)
|
|
}
|
|
|
|
if stmt, ok := node.(*ast.BlockStmt); ok {
|
|
wantNewline := v.wantNewline[stmt]
|
|
|
|
comments := v.comments
|
|
if wantNewline {
|
|
comments = nil // Comments also count as a newline if we want a newline
|
|
}
|
|
first, last := firstAndLast(comments, v.fset, stmt.Pos(), stmt.End(), stmt.List)
|
|
|
|
startMsg := checkStart(v.fset, stmt.Lbrace, first)
|
|
|
|
if wantNewline && startMsg == nil {
|
|
v.messages = append(v.messages, Message{v.fset.Position(stmt.Pos()), MessageTypeAddAfter, `multi-line statement should be followed by a newline`})
|
|
} else if !wantNewline && startMsg != nil {
|
|
v.messages = append(v.messages, *startMsg)
|
|
}
|
|
|
|
if msg := checkEnd(v.fset, stmt.Rbrace, last); msg != nil {
|
|
v.messages = append(v.messages, *msg)
|
|
}
|
|
}
|
|
|
|
return v
|
|
}
|
|
|
|
func checkMultiLine(v *visitor, body *ast.BlockStmt, stmtStart ast.Node) {
|
|
start, end := posLine(v.fset, stmtStart.Pos()), posLine(v.fset, stmtStart.End())
|
|
|
|
if end > start { // Check only multi line conditions
|
|
v.wantNewline[body] = true
|
|
}
|
|
}
|
|
|
|
func posLine(fset *token.FileSet, pos token.Pos) int {
|
|
return fset.Position(pos).Line
|
|
}
|
|
|
|
func firstAndLast(comments []*ast.CommentGroup, fset *token.FileSet, start, end token.Pos, stmts []ast.Stmt) (ast.Node, ast.Node) {
|
|
if len(stmts) == 0 {
|
|
return nil, nil
|
|
}
|
|
|
|
first, last := ast.Node(stmts[0]), ast.Node(stmts[len(stmts)-1])
|
|
|
|
for _, c := range comments {
|
|
if posLine(fset, c.Pos()) == posLine(fset, start) || posLine(fset, c.End()) == posLine(fset, end) {
|
|
continue
|
|
}
|
|
|
|
if c.Pos() < start || c.End() > end {
|
|
continue
|
|
}
|
|
if c.Pos() < first.Pos() {
|
|
first = c
|
|
}
|
|
if c.End() > last.End() {
|
|
last = c
|
|
}
|
|
}
|
|
|
|
return first, last
|
|
}
|
|
|
|
func checkStart(fset *token.FileSet, start token.Pos, first ast.Node) *Message {
|
|
if first == nil {
|
|
return nil
|
|
}
|
|
|
|
if posLine(fset, start)+1 < posLine(fset, first.Pos()) {
|
|
pos := fset.Position(start)
|
|
return &Message{pos, MessageTypeLeading, `unnecessary leading newline`}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func checkEnd(fset *token.FileSet, end token.Pos, last ast.Node) *Message {
|
|
if last == nil {
|
|
return nil
|
|
}
|
|
|
|
if posLine(fset, end)-1 > posLine(fset, last.End()) {
|
|
pos := fset.Position(end)
|
|
return &Message{pos, MessageTypeTrailing, `unnecessary trailing newline`}
|
|
}
|
|
|
|
return nil
|
|
}
|