Skip to content

Commit

Permalink
Add lines and columns to output (#22)
Browse files Browse the repository at this point in the history
  • Loading branch information
fmenezes authored Jul 28, 2020
1 parent b1e54ae commit 7bf637d
Show file tree
Hide file tree
Showing 6 changed files with 151 additions and 23 deletions.
17 changes: 17 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Launch",
"type": "go",
"request": "launch",
"mode": "auto",
"program": "${fileDirname}",
"env": {},
"args": []
}
]
}
38 changes: 32 additions & 6 deletions checker.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package codeowners

import (
"bufio"
"fmt"
"os"
"path/filepath"
Expand Down Expand Up @@ -36,7 +37,7 @@ func RegisterChecker(name string, checker Checker) error {

// Checker provides tools for validating CODEOWNER file contents
type Checker interface {
CheckLine(lineNo int, filePattern string, owners ...string) []CheckResult
CheckLine(lineNo int, line string) []CheckResult
}

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

// Position provides structured way to evaluate where a given validation result is located in the CODEOWNERs file
type Position struct {
StartLine int
StartColumn int
EndLine int
EndColumn int
}

// String formats the position data
func (p Position) String() string {
output := fmt.Sprintf("%d", p.StartLine)
if p.StartColumn >= 1 {
output = fmt.Sprintf("%s:%d", output, p.StartColumn)
}
if p.EndLine > p.StartLine {
output = fmt.Sprintf("%s-%d:%d", output, p.EndLine, p.EndColumn)
} else if p.StartColumn >= 1 && p.EndColumn > p.StartColumn {
output = fmt.Sprintf("%s-%d", output, p.EndColumn)
}

return output
}

// CheckResult provides structured way to evaluate results of a CODEOWNERS validation check
type CheckResult struct {
LineNo int
Position Position
Message string
Severity SeverityLevel
CheckName string
Expand All @@ -76,15 +100,17 @@ func Check(directory string, checkers ...string) ([]CheckResult, error) {
defer file.Close()

results := []CheckResult{}
decoder := NewDecoder(file)
for decoder.More() {
token, lineNo := decoder.Token()
scanner := bufio.NewScanner(file)
lineNo := 0
for scanner.Scan() {
line := scanner.Text()
lineNo++
for _, checker := range checkers {
c, ok := availableCheckers[checker]
if !ok {
return nil, fmt.Errorf("'%s' not found", checker)
}
lineResults := c.CheckLine(lineNo, token.Path(), token.Owners()...)
lineResults := c.CheckLine(lineNo, line)
if lineResults != nil {
results = append(results, lineResults...)
}
Expand Down
87 changes: 81 additions & 6 deletions checker_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,13 @@ const dummyCheckerName string = "dummy"
type dummyChecker struct {
}

func (c dummyChecker) CheckLine(lineNo int, filePath string, owners ...string) []codeowners.CheckResult {
func (c dummyChecker) CheckLine(lineNo int, line string) []codeowners.CheckResult {
return []codeowners.CheckResult{
{
LineNo: 1,
Position: codeowners.Position{
StartLine: 1,
EndLine: 1,
},
Message: "Dummy Error",
Severity: codeowners.Error,
CheckName: dummyCheckerName,
Expand Down Expand Up @@ -58,11 +61,85 @@ func TestSeverityLevelLabels(t *testing.T) {
}
}

func TestPositionString(t *testing.T) {
testCases := []struct {
input codeowners.Position
want string
}{
{
input: codeowners.Position{
StartLine: 1,
StartColumn: 1,
EndLine: 2,
EndColumn: 2,
},
want: "1:1-2:2",
},
{
input: codeowners.Position{
StartLine: 1,
StartColumn: 1,
EndLine: 1,
EndColumn: 2,
},
want: "1:1-2",
},
{
input: codeowners.Position{
StartLine: 1,
StartColumn: 1,
EndLine: 1,
EndColumn: 1,
},
want: "1:1",
},
{
input: codeowners.Position{
StartLine: 1,
StartColumn: 0,
EndLine: 1,
EndColumn: 0,
},
want: "1",
},
{
input: codeowners.Position{
StartLine: 1,
StartColumn: 0,
EndLine: 0,
EndColumn: 0,
},
want: "1",
},
{
input: codeowners.Position{
StartLine: 0,
StartColumn: 0,
EndLine: 0,
EndColumn: 0,
},
want: "0",
},
}

for _, testCase := range testCases {
got := testCase.input.String()
if got != testCase.want {
t.Errorf("Input: %v, Want: %v, Got: %v", testCase.input, testCase.want, got)
}
}
}

func TestSimpleCheck(t *testing.T) {
input := "./test/data/pass"
want := []codeowners.CheckResult{
{
LineNo: 1,
Position: codeowners.Position{
StartLine: 1,
StartColumn: 0,
EndLine: 1,
EndColumn: 0,
},
Message: "Dummy Error",
Severity: codeowners.Error,
CheckName: dummyCheckerName,
Expand Down Expand Up @@ -102,7 +179,6 @@ func TestNoCodeownersCheck(t *testing.T) {
input := "./test/data"
want := []codeowners.CheckResult{
{
LineNo: 0,
Message: "No CODEOWNERS file found",
Severity: codeowners.Error,
CheckName: "NoCodeowners",
Expand All @@ -122,7 +198,6 @@ func TestMultipleCodeownersCheck(t *testing.T) {
input := "./test/data/multiple_codeowners"
want := []codeowners.CheckResult{
{
LineNo: 0,
Message: "Multiple CODEOWNERS files found (CODEOWNERS, docs/CODEOWNERS)",
Severity: codeowners.Warning,
CheckName: "MultipleCodeowners",
Expand All @@ -144,7 +219,7 @@ func ExampleCheck() {
panic(err)
}
for _, check := range checks {
fmt.Printf("%d ::%s:: %s [%s]\n", check.LineNo, check.Severity, check.Message, check.CheckName)
fmt.Printf("%s ::%s:: %s [%s]\n", check.Position, check.Severity, check.Message, check.CheckName)
}
//Output:
//0 ::Error:: No CODEOWNERS file found [NoCodeowners]
Expand Down
11 changes: 9 additions & 2 deletions checkers/noowner.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,20 @@ func init() {
type NoOwner struct{}

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

_, owners := codeowners.ParseLine(line)

if len(owners) == 0 {
results = []codeowners.CheckResult{
{
LineNo: lineNo,
Position: codeowners.Position{
StartLine: lineNo,
EndLine: lineNo,
StartColumn: 0,
EndColumn: 0,
},
Message: "No owners specified",
Severity: codeowners.Error,
CheckName: noOwnerCheckerName,
Expand Down
19 changes: 11 additions & 8 deletions checkers/noowner_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,25 +10,28 @@ import (

func TestNoOwnerCheck(t *testing.T) {
input := struct {
lineNo int
pattern string
owners []string
lineNo int
line string
}{
lineNo: 1,
pattern: "filepattern",
owners: []string{},
lineNo: 1,
line: "filepattern",
}
want := []codeowners.CheckResult{
{
LineNo: 1,
Position: codeowners.Position{
StartLine: 1,
StartColumn: 0,
EndLine: 1,
EndColumn: 0,
},
Message: "No owners specified",
Severity: codeowners.Error,
CheckName: "NoOwner",
},
}

checker := checkers.NoOwner{}
got := checker.CheckLine(input.lineNo, input.pattern, input.owners...)
got := checker.CheckLine(input.lineNo, input.line)
if !reflect.DeepEqual(got, want) {
t.Errorf("Input: %v, Want: %v, Got: %v", input, want, got)
}
Expand Down
2 changes: 1 addition & 1 deletion cmd/codeowners/linter.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ func run(wr io.Writer, opt options) error {
return err
}

format := "{{range .}}{{ .LineNo }} ::{{ .Severity }}:: {{ .Message }} [{{ .CheckName }}]\n{{end}}"
format := "{{range .}}{{ .Position }} ::{{ .Severity }}:: {{ .Message }} [{{ .CheckName }}]\n{{end}}"
if len(opt.format) > 0 {
format = opt.format
}
Expand Down

0 comments on commit 7bf637d

Please sign in to comment.