diff --git a/doc.go b/doc.go new file mode 100644 index 0000000..62f4cb2 --- /dev/null +++ b/doc.go @@ -0,0 +1,2 @@ +// Package eclint is a set of linters to for the EditorConfig rules +package eclint diff --git a/files.go b/files.go index 7d31bb4..98bc7c8 100644 --- a/files.go +++ b/files.go @@ -19,7 +19,7 @@ import ( // When args are given, it recursively walks into them. func ListFiles(log logr.Logger, args ...string) ([]string, error) { if len(args) == 0 { - fs, err := gitLsFiles(log, ".") + fs, err := GitLsFiles(log, ".") if err == nil { return fs, nil } @@ -28,11 +28,11 @@ func ListFiles(log logr.Logger, args ...string) ([]string, error) { args = append(args, ".") } - return walk(log, args...) + return Walk(log, args...) } -// walk iterates on each path item recursively. -func walk(log logr.Logger, paths ...string) ([]string, error) { +// Walk iterates on each path item recursively. +func Walk(log logr.Logger, paths ...string) ([]string, error) { files := make([]string, 0) for _, path := range paths { err := filepath.Walk(path, func(p string, i os.FileInfo, e error) error { @@ -53,11 +53,11 @@ func walk(log logr.Logger, paths ...string) ([]string, error) { return files, nil } -// gitLsFiles returns the list of file based on what is in the git index. +// GitLsFiles returns the list of file based on what is in the git index. // // -z is mandatory as some repositories non-ASCII file names which creates // quoted and escaped file names. -func gitLsFiles(log logr.Logger, path string) ([]string, error) { +func GitLsFiles(log logr.Logger, path string) ([]string, error) { output, err := exec.Command("git", "ls-files", "-z", path).Output() if err != nil { return nil, err diff --git a/files_test.go b/files_test.go index 1581dbd..8fdf614 100644 --- a/files_test.go +++ b/files_test.go @@ -1,4 +1,4 @@ -package eclint +package eclint_test import ( "fmt" @@ -6,6 +6,7 @@ import ( "testing" tlogr "github.com/go-logr/logr/testing" + "gitlab.com/greut/eclint" ) const ( @@ -17,7 +18,7 @@ const ( func TestListFiles(t *testing.T) { l := tlogr.TestLogger{} d := testdataSimple - fs, err := ListFiles(l, d) + fs, err := eclint.ListFiles(l, d) if err != nil { t.Fatal(err) } @@ -45,7 +46,7 @@ func TestListFilesNoArgs(t *testing.T) { t.Fatal(err) } - fs, err := ListFiles(l) + fs, err := eclint.ListFiles(l) if err != nil { t.Fatal(err) } @@ -78,7 +79,7 @@ func TestListFilesNoGit(t *testing.T) { t.Fatal(err) } - fs, err := ListFiles(l) + fs, err := eclint.ListFiles(l) if err != nil { t.Fatal(err) } @@ -90,7 +91,7 @@ func TestListFilesNoGit(t *testing.T) { func TestWalk(t *testing.T) { l := tlogr.TestLogger{} d := testdataSimple - fs, err := walk(l, d) + fs, err := eclint.Walk(l, d) if err != nil { t.Fatal(err) } @@ -103,7 +104,7 @@ func TestGitLsFiles(t *testing.T) { l := tlogr.TestLogger{} d := testdataSimple - fs, err := gitLsFiles(l, d) + fs, err := eclint.GitLsFiles(l, d) if err != nil { t.Fatal(err) } @@ -121,7 +122,7 @@ func TestGitLsFilesFailure(t *testing.T) { t.Fatal(err) } - _, err = gitLsFiles(l, d) + _, err = eclint.GitLsFiles(l, d) if err == nil { t.Error("an error was expected") } diff --git a/lint.go b/lint.go index 0a570f8..295e538 100644 --- a/lint.go +++ b/lint.go @@ -64,18 +64,18 @@ func validate(r io.Reader, log logr.Logger, def *editorconfig.Definition) []erro } } - errs := readLines(r, func(index int, data []byte) error { + errs := ReadLines(r, func(index int, data []byte) error { var err error // The first line may contain the BOM for detecting some encodings - if index == 1 { + if index == 0 { if def.Charset != "utf-8" && def.Charset != "latin1" { charset = detectCharsetUsingBOM(data) if def.Charset != "" && charset != def.Charset { return validationError{ error: fmt.Sprintf("no %s prefix were found (got %q)", def.Charset, charset), - position: 1, + position: 0, index: index, line: data, } @@ -136,7 +136,7 @@ func validate(r io.Reader, log logr.Logger, def *editorconfig.Definition) []erro if err == nil && maxLength > 0 && tabWidth > 0 { // Remove any BOM from the first line. d := data - if index == 1 && charset != "" { + if index == 0 && charset != "" { for _, bom := range [][]byte{utf8Bom} { if bytes.HasPrefix(data, bom) { d = data[len(utf8Bom):] diff --git a/print.go b/print.go index 3079a1f..20d702b 100644 --- a/print.go +++ b/print.go @@ -26,10 +26,10 @@ func PrintErrors(opt Option, filename string, errors []error) error { if ve, ok := err.(validationError); ok { log.V(4).Info("lint error", "error", ve) if !opt.Summary { - vi := au.Green(strconv.Itoa(ve.index)) - vp := au.Green(strconv.Itoa(ve.position)) + vi := au.Green(strconv.Itoa(ve.index + 1)) + vp := au.Green(strconv.Itoa(ve.position + 1)) fmt.Fprintf(stdout, "%s:%s: %s\n", vi, vp, ve.error) - l, err := errorAt(au, ve.line, ve.position-1) + l, err := errorAt(au, ve.line, ve.position) if err != nil { log.Error(err, "line formating failure", "error", ve) return err diff --git a/scanner.go b/scanner.go index 2e44e35..9803e08 100644 --- a/scanner.go +++ b/scanner.go @@ -5,13 +5,13 @@ import ( "io" ) -// lineFunc is the callback for a line. +// LineFunc is the callback for a line. // // It returns the line number starting from zero. -type lineFunc func(int, []byte) error +type LineFunc func(int, []byte) error -// splitLines works like bufio.ScanLines while keeping the line endings. -func splitLines(data []byte, atEOF bool) (int, []byte, error) { +// SplitLines works like bufio.ScanLines while keeping the line endings. +func SplitLines(data []byte, atEOF bool) (int, []byte, error) { i := 0 for i < len(data) { if data[i] == cr { @@ -38,17 +38,17 @@ func splitLines(data []byte, atEOF bool) (int, []byte, error) { return 0, nil, io.EOF } -// readLines consumes the reader and emit each line via the lineFunc +// ReadLines consumes the reader and emit each line via the LineFunc // -// Line numbering starts at 1. Scanner is pretty smart an will reuse +// Line numbering starts at 0. Scanner is pretty smart an will reuse // its memory structure. This is somehing we explicitly avoid by copying // the content to a new slice. -func readLines(r io.Reader, fn lineFunc) []error { +func ReadLines(r io.Reader, fn LineFunc) []error { errs := make([]error, 0) sc := bufio.NewScanner(r) - sc.Split(splitLines) + sc.Split(SplitLines) - i := 1 + i := 0 for sc.Scan() { l := sc.Bytes() line := make([]byte, len(l)) diff --git a/scanner_test.go b/scanner_test.go new file mode 100644 index 0000000..7e1dd75 --- /dev/null +++ b/scanner_test.go @@ -0,0 +1,70 @@ +package eclint_test + +import ( + "bytes" + "fmt" + "testing" + + "gitlab.com/greut/eclint" +) + +func TestReadLines(t *testing.T) { + tests := []struct { + Name string + File []byte + LineFunc eclint.LineFunc + }{ + { + Name: "Empty file", + File: []byte(""), + LineFunc: func(i int, line []byte) error { + if i != 0 || len(line) > 0 { + return fmt.Errorf("more than one line found (%d), or non epmty line %q", i, line) + } + return nil + }, + }, { + Name: "crlf", + File: []byte("\r\n\r\n"), + LineFunc: func(i int, line []byte) error { + if i > 1 || len(line) > 2 { + return fmt.Errorf("more than two lines found (%d), or non empty line %q", i, line) + } + return nil + }, + }, { + Name: "cr", + File: []byte("\r\r"), + LineFunc: func(i int, line []byte) error { + if i > 1 || len(line) > 2 { + return fmt.Errorf("more than two lines found (%d), or non empty line %q", i, line) + } + return nil + }, + }, { + Name: "lf", + File: []byte("\n\n"), + LineFunc: func(i int, line []byte) error { + if i > 1 || len(line) > 2 { + return fmt.Errorf("more than two lines found (%d), or non empty line %q", i, line) + } + return nil + }, + }, + } + + for _, tc := range tests { + tc := tc + + t.Run(tc.Name, func(t *testing.T) { + t.Parallel() + + r := bytes.NewReader(tc.File) + errs := eclint.ReadLines(r, tc.LineFunc) + if len(errs) > 0 { + t.Errorf("no errors were expected, got some. %s", errs[0]) + return + } + }) + } +} diff --git a/validators.go b/validators.go index 69c414b..5a500de 100644 --- a/validators.go +++ b/validators.go @@ -113,8 +113,7 @@ func detectCharset(charset string, data []byte) error { if len(results) > 0 { return validationError{ - error: fmt.Sprintf("detected charset %q does not match expected %q", results[0].Charset, charset), - position: 1, + error: fmt.Sprintf("detected charset %q does not match expected %q", results[0].Charset, charset), } } @@ -146,7 +145,7 @@ func indentStyle(style string, size int, data []byte) error { if data[i] == x { return validationError{ error: fmt.Sprintf("indentation style mismatch expected %q (%s) got %q", c, style, x), - position: i + 1, + position: i, } } if data[i] == cr || data[i] == lf || (size > 0 && i%size == 0) { @@ -154,7 +153,7 @@ func indentStyle(style string, size int, data []byte) error { } return validationError{ error: fmt.Sprintf("indentation size doesn't match expected %d, got %d", size, i), - position: i + 1, + position: i, } } @@ -170,7 +169,7 @@ func trimTrailingWhitespace(data []byte) error { if data[i] == space || data[i] == tab { return validationError{ error: "line has some trailing whitespaces", - position: i + 1, + position: i, } } break @@ -198,7 +197,7 @@ func checkBlockComment(i int, prefix []byte, data []byte) error { if !bytes.HasPrefix(data[i:], prefix) { return validationError{ error: fmt.Sprintf("block_comment prefix %q was expected inside a block comment", string(prefix)), - position: i + 1, + position: i, } } break @@ -245,7 +244,7 @@ func MaxLineLength(maxLength int, tabWidth int, data []byte) error { if length > maxLength { return validationError{ error: fmt.Sprintf("line is too long (%d > %d)", length, maxLength), - position: breakingPosition + 1, + position: breakingPosition, } }