This repository has been archived by the owner on Mar 15, 2024. It is now read-only.
/
multilinefunctions.go
144 lines (134 loc) · 4.17 KB
/
multilinefunctions.go
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
package multilinefunctions
import (
"fmt"
"go/ast"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
)
const Doc = `review formatting of multi-line argument lists in function declarations`
func Analyzer() *analysis.Analyzer {
return &analysis.Analyzer{
Name: "multilinefunction",
Doc: Doc,
Requires: []*analysis.Analyzer{inspect.Analyzer},
Run: run,
}
}
func run(pass *analysis.Pass) (interface{}, error) {
inspectResult, ok := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
if !ok {
return nil, fmt.Errorf("multilinefunctions inspector type cast failed")
}
nodeFilter := []ast.Node{
(*ast.FuncType)(nil),
(*ast.CallExpr)(nil),
}
inspectResult.Preorder(nodeFilter, func(n ast.Node) {
switch node := n.(type) {
case *ast.FuncType:
analyzeFunctionDeclaration(pass, node.Params)
analyzeFunctionDeclaration(pass, node.Results)
case *ast.CallExpr:
analyseFunctionCall(pass, node)
}
})
return nil, nil
}
// maxArgsToAnalyze is the maximum number of arguments to apply this rule to.
// Calls with more args are considered special cases where special formatting is allowed.
const maxCallArgsToAnalyze = 5
func analyseFunctionCall(pass *analysis.Pass, call *ast.CallExpr) {
lastLine := pass.Fset.Position(call.Rparen).Line
firstLine := pass.Fset.Position(call.Lparen).Line
if firstLine == lastLine {
return
}
if len(call.Args) == 0 {
return
}
if len(call.Args) > maxCallArgsToAnalyze {
return // too many fields
}
if isSingleLineCall(pass, call) {
return
}
if pass.Fset.Position(call.Args[0].Pos()).Line == firstLine {
// only ok format is if each argument starts on the line when the previous ended, like so:
// `a(A{
// ` a: nil,
// `}, A{})
prevEnd := 0
for _, a := range call.Args {
if prevEnd != 0 && pass.Fset.Position(a.Pos()).Line != prevEnd {
pass.Reportf(a.Pos(), "must either have all arguments on individual lines "+
"or no linebreaks before or after arguments")
return
}
prevEnd = pass.Fset.Position(a.End()).Line
}
if prevEnd != lastLine {
pass.Reportf(call.Rparen, "closing paren should be on the same line as last argument")
}
} else {
prevEnd := 0
for _, a := range call.Args {
if pass.Fset.Position(a.Pos()).Line == prevEnd {
pass.Reportf(a.Pos(), "each argument should start on a new line")
}
prevEnd = pass.Fset.Position(a.End()).Line
}
if prevEnd == lastLine {
pass.Reportf(call.Rparen, "closing paren should be on a new line")
}
}
}
func analyzeFunctionDeclaration(pass *analysis.Pass, fields *ast.FieldList) {
if fields == nil {
return // no fields
}
openingLine := pass.Fset.Position(fields.Opening).Line
closingLine := pass.Fset.Position(fields.Closing).Line
if openingLine == closingLine {
return // Not multiline
}
firstParamPos := fields.List[0].Type.Pos()
firstParamLine := pass.Fset.Position(firstParamPos).Line
if openingLine == firstParamLine {
pass.Reportf(fields.Opening, "first field should not be on the same line as opening paren")
}
numParams := len(fields.List)
lastParamPos := fields.List[numParams-1].Type.Pos()
lastParamLine := pass.Fset.Position(lastParamPos).Line
if closingLine == lastParamLine {
pass.Reportf(fields.Closing, "last field should not be on the same line as closing paren")
}
prevParamLine := 0
for _, result := range fields.List {
if len(result.Names) > 1 {
pass.Reportf(result.Names[0].NamePos, "multiline fields should declare the type for each name")
}
pos := result.Type.Pos()
line := pass.Fset.Position(pos).Line
if prevParamLine > 0 {
if line == prevParamLine {
pass.Reportf(pos, "multiline fields should declare one name per line")
}
}
prevParamLine = line
}
}
func isSingleLineCall(pass *analysis.Pass, call *ast.CallExpr) bool {
if len(call.Args) == 0 {
return false
}
firstLine := pass.Fset.Position(call.Lparen).Line
lastLine := pass.Fset.Position(call.Rparen).Line
firstArgLine := pass.Fset.Position(call.Args[0].Pos()).Line
for _, arg := range call.Args {
if pass.Fset.Position(arg.Pos()).Line != firstArgLine {
return false
}
}
return firstArgLine == firstLine+1 && lastLine == firstArgLine+1
}