Repository: editorconfig-checker/editorconfig-checker (go)

Issue:
`insert_final_newline` should ensure a single newline character at the end of the file
Right now, editorconfig-checker does not complain about multiple newlines at EOF when the `insert_final_newline` option is set.

According to [this comment](https://github.com/editorconfig/editorconfig/issues/483#issuecomment-1451447540) on a feature request to support specifying the number of newlines that should be at EOF the option is intended to mean that a file should end in a singular newline, so editorconfig-checker should complain about multiple newlines at the end of a file.

Pull request description:
Check if multiple newlines are at EOF
With insert_final_newline enabled, only a single final newline should be at the end of file. Add a check to fail, if there are multiple newlines.

This new validation logic doesn't fail with wrong line endings anymore, there's already another validator for that.

Fixes #341

Patch:
diff --git a/pkg/validation/validators/validators.go b/pkg/validation/validators/validators.go
index 14a0739..8bd763e 100644
--- a/pkg/validation/validators/validators.go
+++ b/pkg/validation/validators/validators.go
@@ -93,22 +93,37 @@ func TrailingWhitespace(line string, trimTrailingWhitespace bool) error {
 
 // FinalNewline validates if a file has a final and correct newline
 func FinalNewline(fileContent string, insertFinalNewline string, endOfLine string) error {
-	if endOfLine != "" && insertFinalNewline == "true" {
-		expectedEolChar := utils.GetEolChar(endOfLine)
-		if !strings.HasSuffix(fileContent, expectedEolChar) || (expectedEolChar == "\n" && strings.HasSuffix(fileContent, "\r\n")) {
-			return errors.New("Wrong line endings or no final newline")
-		}
+	if insertFinalNewline == "" {
+		return nil
+	}
+
+	var expectedEolChars string
+
+	if endOfLine != "" {
+		expectedEolChars = utils.GetEolChar(endOfLine)
 	} else {
-		regexpPattern := "(\n|\r|\r\n)$"
-		hasFinalNewline, _ := regexp.MatchString(regexpPattern, fileContent)
+		expectedEolChars = `\r\n|\r|\n`
+	}
+
+	re := regexp.MustCompile("(?:" + expectedEolChars + ")((?:" + expectedEolChars + ")?)$")
+	matches := re.FindStringSubmatch(fileContent)
+
+	unexpectedCrLf := expectedEolChars == "\n" && strings.HasSuffix(fileContent, "\r\n")
 
-		if insertFinalNewline == "false" && hasFinalNewline {
+	if insertFinalNewline == "false" {
+		if matches != nil && !unexpectedCrLf {
 			return errors.New("No final newline expected")
 		}
+	}
 
-		if insertFinalNewline == "true" && !hasFinalNewline {
+	if insertFinalNewline == "true" {
+		if matches == nil || unexpectedCrLf {
 			return errors.New("Final newline expected")
 		}
+
+		if matches[1] != "" {
+			return errors.New("Multiple newlines at end of file")
+		}
 	}
 
 	return nil


Test patch:
diff --git a/pkg/validation/validators/validators_test.go b/pkg/validation/validators/validators_test.go
index feb7ca6..25c6ca6 100644
--- a/pkg/validation/validators/validators_test.go
+++ b/pkg/validation/validators/validators_test.go
@@ -19,33 +19,33 @@ func TestFinalNewline(t *testing.T) {
 		{"x\r", "true", "cr", nil},
 		{"x\r\n", "true", "crlf", nil},
 
-		{"x", "true", "lf", errors.New("Wrong line endings or no final newline")},
-		{"x", "true", "cr", errors.New("Wrong line endings or no final newline")},
-		{"x", "true", "crlf", errors.New("Wrong line endings or no final newline")},
+		{"x", "true", "lf", errors.New("Final newline expected")},
+		{"x", "true", "cr", errors.New("Final newline expected")},
+		{"x", "true", "crlf", errors.New("Final newline expected")},
 
-		{"x\n", "true", "cr", errors.New("Wrong line endings or no final newline")},
-		{"x\n", "true", "crlf", errors.New("Wrong line endings or no final newline")},
+		{"x\n", "true", "cr", errors.New("Final newline expected")},
+		{"x\n", "true", "crlf", errors.New("Final newline expected")},
 
-		{"x\r", "true", "lf", errors.New("Wrong line endings or no final newline")},
-		{"x\r", "true", "crlf", errors.New("Wrong line endings or no final newline")},
+		{"x\r", "true", "lf", errors.New("Final newline expected")},
+		{"x\r", "true", "crlf", errors.New("Final newline expected")},
 
-		{"x\r\n", "true", "lf", errors.New("Wrong line endings or no final newline")},
-		{"x\r\n", "true", "cr", errors.New("Wrong line endings or no final newline")},
+		{"x\r\n", "true", "lf", errors.New("Final newline expected")},
+		{"x\r\n", "true", "cr", errors.New("Final newline expected")},
 
 		// insert_final_newline false
 		{"x", "false", "lf", nil},
 		{"x\n", "false", "lf", errors.New("No final newline expected")},
-		{"x\r", "false", "lf", errors.New("No final newline expected")},
-		{"x\r\n", "false", "lf", errors.New("No final newline expected")},
+		{"x\r", "false", "lf", nil},
+		{"x\r\n", "false", "lf", nil},
 
 		{"x", "false", "cr", nil},
-		{"x\n", "false", "cr", errors.New("No final newline expected")},
+		{"x\n", "false", "cr", nil},
 		{"x\r", "false", "cr", errors.New("No final newline expected")},
-		{"x\r\n", "false", "cr", errors.New("No final newline expected")},
+		{"x\r\n", "false", "cr", nil},
 
 		{"x", "false", "crlf", nil},
-		{"x\n", "false", "crlf", errors.New("No final newline expected")},
-		{"x\r", "false", "crlf", errors.New("No final newline expected")},
+		{"x\n", "false", "crlf", nil},
+		{"x\r", "false", "crlf", nil},
 		{"x\r\n", "false", "crlf", errors.New("No final newline expected")},
 
 		// insert_final_newline not set
@@ -71,6 +71,14 @@ func TestFinalNewline(t *testing.T) {
 		{"x\r", "false", "", errors.New("No final newline expected")},
 		{"x\r\n", "true", "", nil},
 		{"x\r\n", "false", "", errors.New("No final newline expected")},
+
+		// multiple newlines
+		{"x\n\n", "true", "", errors.New("Multiple newlines at end of file")},
+		{"x\r\r", "true", "", errors.New("Multiple newlines at end of file")},
+		{"x\r\n\r\n", "true", "", errors.New("Multiple newlines at end of file")},
+		{"x\n\n", "true", "lf", errors.New("Multiple newlines at end of file")},
+		{"x\r\r", "true", "cr", errors.New("Multiple newlines at end of file")},
+		{"x\r\n\r\n", "true", "crlf", errors.New("Multiple newlines at end of file")},
 	}
 
 	for _, tt := range finalNewlineTests {
diff --git a/testfiles/spaces-with-tab.c b/testfiles/spaces-with-tab.c
index a015fa7..1ff9583 100644
--- a/testfiles/spaces-with-tab.c
+++ b/testfiles/spaces-with-tab.c
@@ -11,4 +11,3 @@ int main(int argc, char *argv[])
     }
     return 0;
 }
-