1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
|
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.FuncLit); ok && v.settings.MultiFunc {
checkMultiLine(v, stmt.Body, stmt.Type)
}
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
}
|