Skip to content

Commit

Permalink
feat: add fix support for missing final newlines
Browse files Browse the repository at this point in the history
  • Loading branch information
nicmr authored and greut committed Oct 5, 2023
1 parent ed016aa commit deefac3
Show file tree
Hide file tree
Showing 4 changed files with 136 additions and 8 deletions.
6 changes: 3 additions & 3 deletions definition.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,11 +90,11 @@ func newDefinition(d *editorconfig.Definition) (*definition, error) { //nolint:c
// EOL returns the byte value of the given definition.
func (def *definition) EOL() ([]byte, error) {
switch def.EndOfLine {
case "cr":
case editorconfig.EndOfLineCr:
return []byte{cr}, nil
case "crlf":
case editorconfig.EndOfLineCrLf:
return []byte{cr, lf}, nil
case "lf":
case editorconfig.EndOfLineLf:
return []byte{lf}, nil
default:
return nil, fmt.Errorf("%w: unsupported EndOfLine value %s", ErrConfiguration, def.EndOfLine)
Expand Down
25 changes: 24 additions & 1 deletion fix.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ func fixWithFilename(ctx context.Context, def *definition, filename string, file
return fix(ctx, r, fileSize, charset, def)
}

func fix( //nolint:funlen
func fix( //nolint:funlen,cyclop
_ context.Context,
r io.Reader,
fileSize int64,
Expand Down Expand Up @@ -177,6 +177,10 @@ func fix( //nolint:funlen
return nil, errs[0]
}

if def.InsertFinalNewline != nil {
fixInsertFinalNewline(buf, *def.InsertFinalNewline, eol)
}

return buf, nil
}

Expand Down Expand Up @@ -238,3 +242,22 @@ outer:

return data
}

// fixInsertFinalNewline modifies buf to fix the existence of a final newline.
// Line endings are assumed to already be consistent within the buffer.
// A nil buffer or an empty buffer is returned as is.
func fixInsertFinalNewline(buf *bytes.Buffer, insertFinalNewline bool, endOfLine []byte) {
if buf == nil || buf.Len() == 0 {
return
}

if insertFinalNewline {
if !bytes.HasSuffix(buf.Bytes(), endOfLine) {
buf.Write(endOfLine)
}
} else {
for bytes.HasSuffix(buf.Bytes(), endOfLine) {
buf.Truncate(buf.Len() - len(endOfLine))
}
}
}
105 changes: 104 additions & 1 deletion fix_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ func TestFixEndOfLine(t *testing.T) {
t.Parallel()

def, err := newDefinition(&editorconfig.Definition{
EndOfLine: "crlf",
EndOfLine: editorconfig.EndOfLineCrLf,
})
if err != nil {
t.Fatal(err)
Expand Down Expand Up @@ -214,3 +214,106 @@ func TestFixTrimTrailingWhitespace(t *testing.T) {
})
}
}

func TestFixInsertFinalNewline(t *testing.T) {
eolVariants := [][]byte{
{cr},
{lf},
{cr, lf},
}

insertFinalNewlineVariants := []bool{true, false}
newlinesAtEOLVariants := []int{0, 1, 3}

type Test struct {
InsertFinalNewline bool
File []byte
EolVariant []byte
NewlinesAtEOL int
}

tests := make([]Test, 0, 54)

// single line tests
singleLineFile := []byte(`A single line file.`)

for _, eolVariant := range eolVariants {
for _, insertFinalNewlineVariant := range insertFinalNewlineVariants {
for newlinesAtEOL := range newlinesAtEOLVariants {
file := singleLineFile
for i := 0; i < newlinesAtEOL; i++ {
file = append(file, eolVariant...)
}

tests = append(tests,
Test{
InsertFinalNewline: insertFinalNewlineVariant,
File: file,
EolVariant: eolVariant,
NewlinesAtEOL: newlinesAtEOL,
},
)
}
}
}

// multiline tests
multilineComponents := [][]byte{[]byte(`A`), []byte(`multiline`), []byte(`file.`)}

for _, eolVariant := range eolVariants {
multilineFile := bytes.Join(multilineComponents, eolVariant)

for _, insertFinalNewlineVariant := range insertFinalNewlineVariants {
for newlinesAtEOL := range newlinesAtEOLVariants {
file := multilineFile
for i := 0; i < newlinesAtEOL; i++ {
file = append(file, eolVariant...)
}

tests = append(tests,
Test{
InsertFinalNewline: insertFinalNewlineVariant,
File: file,
EolVariant: eolVariant,
NewlinesAtEOL: newlinesAtEOL,
},
)
}
}
}

// empty file tests
emptyFile := []byte("")

for _, eolVariant := range eolVariants {
for _, insertFinalNewlineVariant := range insertFinalNewlineVariants {
tests = append(tests,
Test{
InsertFinalNewline: insertFinalNewlineVariant,
File: emptyFile,
EolVariant: eolVariant,
},
)
}
}

for _, tc := range tests {
tc := tc

t.Run("TestFixInsertFinalNewline", func(t *testing.T) {
t.Parallel()

buf := bytes.Buffer{}
buf.Write(tc.File)
before := buf.Bytes()
fixInsertFinalNewline(&buf, tc.InsertFinalNewline, tc.EolVariant)
after := buf.Bytes()
err := checkInsertFinalNewline(buf.Bytes(), tc.InsertFinalNewline)
if err != nil {
t.Logf("before: %q", string(before))
t.Logf("after: %q", string(after))
t.Errorf("encountered error %s with test configuration %+v", err, tc)
}
})
}
}
8 changes: 5 additions & 3 deletions validators.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import (
"bytes"
"errors"
"fmt"

"github.com/editorconfig/editorconfig-core-go/v2"
)

const (
Expand Down Expand Up @@ -45,21 +47,21 @@ func (e ValidationError) Error() string {
// endOfLines checks the line ending.
func endOfLine(eol string, data []byte) error {
switch eol {
case "lf":
case editorconfig.EndOfLineLf:
if !bytes.HasSuffix(data, []byte{lf}) || bytes.HasSuffix(data, []byte{cr, lf}) {
return ValidationError{
Message: "line does not end with lf (`\\n`)",
Position: len(data),
}
}
case "crlf":
case editorconfig.EndOfLineCrLf:
if !bytes.HasSuffix(data, []byte{cr, lf}) && !bytes.HasSuffix(data, []byte{0x00, cr, 0x00, lf}) {
return ValidationError{
Message: "line does not end with crlf (`\\r\\n`)",
Position: len(data),
}
}
case "cr":
case editorconfig.EndOfLineCr:
if !bytes.HasSuffix(data, []byte{cr}) {
return ValidationError{
Message: "line does not end with cr (`\\r`)",
Expand Down

0 comments on commit deefac3

Please sign in to comment.