113 lines
2.3 KiB
Go
113 lines
2.3 KiB
Go
package whitespace
|
|
|
|
import (
|
|
"go/ast"
|
|
"go/token"
|
|
)
|
|
|
|
// Message contains a message
|
|
type Message struct {
|
|
Pos token.Position
|
|
Message string
|
|
}
|
|
|
|
// Run runs this linter on the provided code
|
|
func Run(file *ast.File, fset *token.FileSet) []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}
|
|
ast.Walk(&vis, decl)
|
|
|
|
messages = append(messages, vis.messages...)
|
|
}
|
|
|
|
return messages
|
|
}
|
|
|
|
type visitor struct {
|
|
comments []*ast.CommentGroup
|
|
fset *token.FileSet
|
|
messages []Message
|
|
}
|
|
|
|
func (v *visitor) Visit(node ast.Node) ast.Visitor {
|
|
if node == nil {
|
|
return v
|
|
}
|
|
|
|
if stmt, ok := node.(*ast.BlockStmt); ok {
|
|
first, last := firstAndLast(v.comments, v.fset, stmt.Pos(), stmt.End(), stmt.List)
|
|
|
|
if msg := checkStart(v.fset, stmt.Lbrace, first); msg != nil {
|
|
v.messages = append(v.messages, *msg)
|
|
}
|
|
if msg := checkEnd(v.fset, stmt.Rbrace, last); msg != nil {
|
|
v.messages = append(v.messages, *msg)
|
|
}
|
|
}
|
|
|
|
return v
|
|
}
|
|
|
|
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, `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, `unnecessary trailing newline`}
|
|
}
|
|
|
|
return nil
|
|
}
|