-
Notifications
You must be signed in to change notification settings - Fork 7
/
Copy pathlint.go
202 lines (171 loc) · 4.36 KB
/
lint.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
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
package cmd
import (
"fmt"
"io"
"os"
"path/filepath"
"strings"
"unicode"
cli "github.com/urfave/cli/v2"
"github.com/conventionalcommit/commitlint/config"
"github.com/conventionalcommit/commitlint/lint"
)
const (
// errExitCode represent error exit code
errExitCode = 1
)
// lintMsg is the callback function for lint command
func lintMsg(confPath, msgPath string) error {
// NOTE: lint should return with exit code for error case
resStr, hasError, err := runLint(confPath, msgPath)
if err != nil {
return cli.Exit(err, errExitCode)
}
if hasError {
return cli.Exit(resStr, errExitCode)
}
// print success message
fmt.Println(resStr)
return nil
}
func runLint(confFilePath, fileInput string) (lintResult string, hasError bool, err error) {
linter, format, err := getLinter(confFilePath)
if err != nil {
return "", false, err
}
commitMsg, err := getCommitMsg(fileInput)
if err != nil {
return "", false, err
}
cleanMsg, err := cleanupMsg(commitMsg)
if err != nil {
return "", false, err
}
result, err := linter.ParseAndLint(cleanMsg)
if err != nil {
return "", false, err
}
output, err := format.Format(result)
if err != nil {
return "", false, err
}
return output, hasErrorSeverity(result), nil
}
func getLinter(confParam string) (*lint.Linter, lint.Formatter, error) {
conf, err := getConfig(confParam)
if err != nil {
return nil, nil, err
}
format, err := config.GetFormatter(conf)
if err != nil {
return nil, nil, err
}
linter, err := config.NewLinter(conf)
if err != nil {
return nil, nil, err
}
return linter, format, nil
}
func getConfig(confParam string) (*lint.Config, error) {
if confParam != "" {
confParam = filepath.Clean(confParam)
return config.Parse(confParam)
}
// If config param is empty, lookup for defaults
conf, err := config.LookupAndParse()
if err != nil {
return nil, err
}
return conf, nil
}
func getCommitMsg(fileInput string) (string, error) {
commitMsg, err := readStdInPipe()
if err != nil {
return "", err
}
if commitMsg != "" {
return commitMsg, nil
}
// TODO: check if currentDir is inside git repo?
if fileInput == "" {
fileInput = "./.git/COMMIT_EDITMSG"
}
fileInput = filepath.Clean(fileInput)
inBytes, err := os.ReadFile(fileInput)
if err != nil {
return "", err
}
return string(inBytes), nil
}
func trimRightSpace(s string) string {
return strings.TrimRightFunc(s, unicode.IsSpace)
}
func cleanupMsg(dirtyMsg string) (string, error) {
// commit msg cleanup in git is configurable: https://git-scm.com/docs/git-commit#Documentation/git-commit.txt---cleanupltmodegt
// For now we do a combination of the "scissors" behavior and the "strip" behavior
// * remove the scissors line and everything below
// * strip leading and trailing empty lines
// * strip commentary (lines stating with commentChar '#')
// * strip trailing whitespace
// * collapse consecutive empty lines
// TODO: check via "git config --get" if any of those two hardcoded constants was reconfigured
// TODO: find out if commit messages on windows actually
gitCommentChar := "#"
scissors := gitCommentChar + " ------------------------ >8 ------------------------"
cleanMsg := ""
lastLine := ""
for _, line := range strings.Split(dirtyMsg, "\n") {
if line == scissors {
// remove everything below scissors (including the scissors line)
break
}
if strings.HasPrefix(line, gitCommentChar) {
// strip commentary
continue
}
line = trimRightSpace(line)
// strip trailing whitespace
if lastLine == "" && line == "" {
// strip leading empty lines
// collapse consecutive empty lines
continue
}
if cleanMsg == "" {
cleanMsg = line
} else {
cleanMsg += "\n" + line
}
lastLine = line
}
if lastLine == "" {
//strip trailing empty line
cleanMsg = strings.TrimSuffix(cleanMsg, "\n")
}
return cleanMsg, nil
}
func readStdInPipe() (string, error) {
stat, err := os.Stdin.Stat()
if err != nil {
return "", err
}
// user input from terminal
if (stat.Mode() & os.ModeCharDevice) != 0 {
// not handling this case
return "", nil
}
// user input from stdin pipe
readBytes, err := io.ReadAll(os.Stdin)
if err != nil {
return "", err
}
s := string(readBytes)
return strings.TrimSpace(s), nil
}
func hasErrorSeverity(result *lint.Result) bool {
for _, i := range result.Issues() {
if i.Severity() == lint.SeverityError {
return true
}
}
return false
}