Skip to content

Commit 7bf637d

Browse files
authored
Add lines and columns to output (#22)
1 parent b1e54ae commit 7bf637d

6 files changed

Lines changed: 151 additions & 23 deletions

File tree

.vscode/launch.json

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
// Use IntelliSense to learn about possible attributes.
3+
// Hover to view descriptions of existing attributes.
4+
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5+
"version": "0.2.0",
6+
"configurations": [
7+
{
8+
"name": "Launch",
9+
"type": "go",
10+
"request": "launch",
11+
"mode": "auto",
12+
"program": "${fileDirname}",
13+
"env": {},
14+
"args": []
15+
}
16+
]
17+
}

checker.go

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package codeowners
22

33
import (
4+
"bufio"
45
"fmt"
56
"os"
67
"path/filepath"
@@ -36,7 +37,7 @@ func RegisterChecker(name string, checker Checker) error {
3637

3738
// Checker provides tools for validating CODEOWNER file contents
3839
type Checker interface {
39-
CheckLine(lineNo int, filePattern string, owners ...string) []CheckResult
40+
CheckLine(lineNo int, line string) []CheckResult
4041
}
4142

4243
// SeverityLevel exposes all possible levels of severity check results
@@ -53,9 +54,32 @@ func (l SeverityLevel) String() string {
5354
return [...]string{"Error", "Warning"}[l]
5455
}
5556

57+
// Position provides structured way to evaluate where a given validation result is located in the CODEOWNERs file
58+
type Position struct {
59+
StartLine int
60+
StartColumn int
61+
EndLine int
62+
EndColumn int
63+
}
64+
65+
// String formats the position data
66+
func (p Position) String() string {
67+
output := fmt.Sprintf("%d", p.StartLine)
68+
if p.StartColumn >= 1 {
69+
output = fmt.Sprintf("%s:%d", output, p.StartColumn)
70+
}
71+
if p.EndLine > p.StartLine {
72+
output = fmt.Sprintf("%s-%d:%d", output, p.EndLine, p.EndColumn)
73+
} else if p.StartColumn >= 1 && p.EndColumn > p.StartColumn {
74+
output = fmt.Sprintf("%s-%d", output, p.EndColumn)
75+
}
76+
77+
return output
78+
}
79+
5680
// CheckResult provides structured way to evaluate results of a CODEOWNERS validation check
5781
type CheckResult struct {
58-
LineNo int
82+
Position Position
5983
Message string
6084
Severity SeverityLevel
6185
CheckName string
@@ -76,15 +100,17 @@ func Check(directory string, checkers ...string) ([]CheckResult, error) {
76100
defer file.Close()
77101

78102
results := []CheckResult{}
79-
decoder := NewDecoder(file)
80-
for decoder.More() {
81-
token, lineNo := decoder.Token()
103+
scanner := bufio.NewScanner(file)
104+
lineNo := 0
105+
for scanner.Scan() {
106+
line := scanner.Text()
107+
lineNo++
82108
for _, checker := range checkers {
83109
c, ok := availableCheckers[checker]
84110
if !ok {
85111
return nil, fmt.Errorf("'%s' not found", checker)
86112
}
87-
lineResults := c.CheckLine(lineNo, token.Path(), token.Owners()...)
113+
lineResults := c.CheckLine(lineNo, line)
88114
if lineResults != nil {
89115
results = append(results, lineResults...)
90116
}

checker_test.go

Lines changed: 81 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,13 @@ const dummyCheckerName string = "dummy"
1414
type dummyChecker struct {
1515
}
1616

17-
func (c dummyChecker) CheckLine(lineNo int, filePath string, owners ...string) []codeowners.CheckResult {
17+
func (c dummyChecker) CheckLine(lineNo int, line string) []codeowners.CheckResult {
1818
return []codeowners.CheckResult{
1919
{
20-
LineNo: 1,
20+
Position: codeowners.Position{
21+
StartLine: 1,
22+
EndLine: 1,
23+
},
2124
Message: "Dummy Error",
2225
Severity: codeowners.Error,
2326
CheckName: dummyCheckerName,
@@ -58,11 +61,85 @@ func TestSeverityLevelLabels(t *testing.T) {
5861
}
5962
}
6063

64+
func TestPositionString(t *testing.T) {
65+
testCases := []struct {
66+
input codeowners.Position
67+
want string
68+
}{
69+
{
70+
input: codeowners.Position{
71+
StartLine: 1,
72+
StartColumn: 1,
73+
EndLine: 2,
74+
EndColumn: 2,
75+
},
76+
want: "1:1-2:2",
77+
},
78+
{
79+
input: codeowners.Position{
80+
StartLine: 1,
81+
StartColumn: 1,
82+
EndLine: 1,
83+
EndColumn: 2,
84+
},
85+
want: "1:1-2",
86+
},
87+
{
88+
input: codeowners.Position{
89+
StartLine: 1,
90+
StartColumn: 1,
91+
EndLine: 1,
92+
EndColumn: 1,
93+
},
94+
want: "1:1",
95+
},
96+
{
97+
input: codeowners.Position{
98+
StartLine: 1,
99+
StartColumn: 0,
100+
EndLine: 1,
101+
EndColumn: 0,
102+
},
103+
want: "1",
104+
},
105+
{
106+
input: codeowners.Position{
107+
StartLine: 1,
108+
StartColumn: 0,
109+
EndLine: 0,
110+
EndColumn: 0,
111+
},
112+
want: "1",
113+
},
114+
{
115+
input: codeowners.Position{
116+
StartLine: 0,
117+
StartColumn: 0,
118+
EndLine: 0,
119+
EndColumn: 0,
120+
},
121+
want: "0",
122+
},
123+
}
124+
125+
for _, testCase := range testCases {
126+
got := testCase.input.String()
127+
if got != testCase.want {
128+
t.Errorf("Input: %v, Want: %v, Got: %v", testCase.input, testCase.want, got)
129+
}
130+
}
131+
}
132+
61133
func TestSimpleCheck(t *testing.T) {
62134
input := "./test/data/pass"
63135
want := []codeowners.CheckResult{
64136
{
65-
LineNo: 1,
137+
Position: codeowners.Position{
138+
StartLine: 1,
139+
StartColumn: 0,
140+
EndLine: 1,
141+
EndColumn: 0,
142+
},
66143
Message: "Dummy Error",
67144
Severity: codeowners.Error,
68145
CheckName: dummyCheckerName,
@@ -102,7 +179,6 @@ func TestNoCodeownersCheck(t *testing.T) {
102179
input := "./test/data"
103180
want := []codeowners.CheckResult{
104181
{
105-
LineNo: 0,
106182
Message: "No CODEOWNERS file found",
107183
Severity: codeowners.Error,
108184
CheckName: "NoCodeowners",
@@ -122,7 +198,6 @@ func TestMultipleCodeownersCheck(t *testing.T) {
122198
input := "./test/data/multiple_codeowners"
123199
want := []codeowners.CheckResult{
124200
{
125-
LineNo: 0,
126201
Message: "Multiple CODEOWNERS files found (CODEOWNERS, docs/CODEOWNERS)",
127202
Severity: codeowners.Warning,
128203
CheckName: "MultipleCodeowners",
@@ -144,7 +219,7 @@ func ExampleCheck() {
144219
panic(err)
145220
}
146221
for _, check := range checks {
147-
fmt.Printf("%d ::%s:: %s [%s]\n", check.LineNo, check.Severity, check.Message, check.CheckName)
222+
fmt.Printf("%s ::%s:: %s [%s]\n", check.Position, check.Severity, check.Message, check.CheckName)
148223
}
149224
//Output:
150225
//0 ::Error:: No CODEOWNERS file found [NoCodeowners]

checkers/noowner.go

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,20 @@ func init() {
1212
type NoOwner struct{}
1313

1414
// CheckLine runs this NoOwner's check against each line
15-
func (c NoOwner) CheckLine(lineNo int, pattern string, owners ...string) []codeowners.CheckResult {
15+
func (c NoOwner) CheckLine(lineNo int, line string) []codeowners.CheckResult {
1616
var results []codeowners.CheckResult
1717

18+
_, owners := codeowners.ParseLine(line)
19+
1820
if len(owners) == 0 {
1921
results = []codeowners.CheckResult{
2022
{
21-
LineNo: lineNo,
23+
Position: codeowners.Position{
24+
StartLine: lineNo,
25+
EndLine: lineNo,
26+
StartColumn: 0,
27+
EndColumn: 0,
28+
},
2229
Message: "No owners specified",
2330
Severity: codeowners.Error,
2431
CheckName: noOwnerCheckerName,

checkers/noowner_test.go

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,25 +10,28 @@ import (
1010

1111
func TestNoOwnerCheck(t *testing.T) {
1212
input := struct {
13-
lineNo int
14-
pattern string
15-
owners []string
13+
lineNo int
14+
line string
1615
}{
17-
lineNo: 1,
18-
pattern: "filepattern",
19-
owners: []string{},
16+
lineNo: 1,
17+
line: "filepattern",
2018
}
2119
want := []codeowners.CheckResult{
2220
{
23-
LineNo: 1,
21+
Position: codeowners.Position{
22+
StartLine: 1,
23+
StartColumn: 0,
24+
EndLine: 1,
25+
EndColumn: 0,
26+
},
2427
Message: "No owners specified",
2528
Severity: codeowners.Error,
2629
CheckName: "NoOwner",
2730
},
2831
}
2932

3033
checker := checkers.NoOwner{}
31-
got := checker.CheckLine(input.lineNo, input.pattern, input.owners...)
34+
got := checker.CheckLine(input.lineNo, input.line)
3235
if !reflect.DeepEqual(got, want) {
3336
t.Errorf("Input: %v, Want: %v, Got: %v", input, want, got)
3437
}

cmd/codeowners/linter.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ func run(wr io.Writer, opt options) error {
2020
return err
2121
}
2222

23-
format := "{{range .}}{{ .LineNo }} ::{{ .Severity }}:: {{ .Message }} [{{ .CheckName }}]\n{{end}}"
23+
format := "{{range .}}{{ .Position }} ::{{ .Severity }}:: {{ .Message }} [{{ .CheckName }}]\n{{end}}"
2424
if len(opt.format) > 0 {
2525
format = opt.format
2626
}

0 commit comments

Comments
 (0)