-
-
Notifications
You must be signed in to change notification settings - Fork 1.4k
/
Copy pathlll.go
163 lines (133 loc) · 3.94 KB
/
lll.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
package golinters
import (
"bufio"
"fmt"
"go/token"
"os"
"regexp"
"strings"
"sync"
"unicode/utf8"
"golang.org/x/tools/go/analysis"
"github.com/golangci/golangci-lint/pkg/config"
"github.com/golangci/golangci-lint/pkg/golinters/goanalysis"
"github.com/golangci/golangci-lint/pkg/lint/linter"
"github.com/golangci/golangci-lint/pkg/result"
)
const lllName = "lll"
const goCommentDirectivePrefix = "//go:"
//nolint:dupl
func NewLLL(settings *config.LllSettings) *goanalysis.Linter {
var mu sync.Mutex
var resIssues []goanalysis.Issue
analyzer := &analysis.Analyzer{
Name: lllName,
Doc: goanalysis.TheOnlyanalyzerDoc,
Run: func(pass *analysis.Pass) (any, error) {
issues, err := runLll(pass, settings)
if err != nil {
return nil, err
}
if len(issues) == 0 {
return nil, nil
}
mu.Lock()
resIssues = append(resIssues, issues...)
mu.Unlock()
return nil, nil
},
}
return goanalysis.NewLinter(
lllName,
"Reports long lines",
[]*analysis.Analyzer{analyzer},
nil,
).WithIssuesReporter(func(*linter.Context) []goanalysis.Issue {
return resIssues
}).WithLoadMode(goanalysis.LoadModeSyntax)
}
func runLll(pass *analysis.Pass, settings *config.LllSettings) ([]goanalysis.Issue, error) {
fileNames := getFileNames(pass)
spaces := strings.Repeat(" ", settings.TabWidth)
var issues []goanalysis.Issue
for _, f := range fileNames {
lintIssues, err := getLLLIssuesForFile(f, settings.LineLength, spaces)
if err != nil {
return nil, err
}
for i := range lintIssues {
issues = append(issues, goanalysis.NewIssue(&lintIssues[i], pass))
}
}
return issues, nil
}
func getLLLIssuesForFile(filename string, maxLineLen int, tabSpaces string) ([]result.Issue, error) {
var res []result.Issue
f, err := os.Open(filename)
if err != nil {
return nil, fmt.Errorf("can't open file %s: %s", filename, err)
}
defer f.Close()
lineNumber := 0
multiImportEnabled := false
urlComment := regexp.MustCompile(`\s*//\s*http(s)?://[^ ]+$`)
scanner := bufio.NewScanner(f)
for scanner.Scan() {
lineNumber++
line := scanner.Text()
line = strings.ReplaceAll(line, "\t", tabSpaces)
if strings.HasPrefix(line, goCommentDirectivePrefix) {
continue
}
if urlComment.MatchString(line) {
continue
}
if strings.HasPrefix(line, "import") {
multiImportEnabled = strings.HasSuffix(line, "(")
continue
}
if multiImportEnabled {
if line == ")" {
multiImportEnabled = false
}
continue
}
lineLen := utf8.RuneCountInString(line)
if lineLen > maxLineLen {
res = append(res, result.Issue{
Pos: token.Position{
Filename: filename,
Line: lineNumber,
},
Text: fmt.Sprintf("line is %d characters", lineLen),
FromLinter: lllName,
})
}
}
if err := scanner.Err(); err != nil {
if err == bufio.ErrTooLong && maxLineLen < bufio.MaxScanTokenSize {
// scanner.Scan() might fail if the line is longer than bufio.MaxScanTokenSize
// In the case where the specified maxLineLen is smaller than bufio.MaxScanTokenSize
// we can return this line as a long line instead of returning an error.
// The reason for this change is that this case might happen with autogenerated files
// The go-bindata tool for instance might generate a file with a very long line.
// In this case, as it's an auto generated file, the warning returned by lll will
// be ignored.
// But if we return a linter error here, and this error happens for an autogenerated
// file the error will be discarded (fine), but all the subsequent errors for lll will
// be discarded for other files, and we'll miss legit error.
res = append(res, result.Issue{
Pos: token.Position{
Filename: filename,
Line: lineNumber,
Column: 1,
},
Text: fmt.Sprintf("line is more than %d characters", bufio.MaxScanTokenSize),
FromLinter: lllName,
})
} else {
return nil, fmt.Errorf("can't scan file %s: %s", filename, err)
}
}
return res, nil
}