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

Issue:
[Request] Support max_line_length

Pull request description:
feat: `max_line_length`
closes #111 

Patch:
diff --git a/.ecrc b/.ecrc
index 0492ace..2bb627e 100644
--- a/.ecrc
+++ b/.ecrc
@@ -12,6 +12,7 @@
     "Indentation": false,
     "InsertFinalNewline": false,
     "TrimTrailingWhitespace": false,
-    "IndentSize": false
+    "IndentSize": false,
+    "MaxLineLength": false
   }
 }
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c6b033f..3a7e304 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,6 +3,7 @@
 ## Unreleased
 ### BREAKING
 ### Added
+* `max_line_length` support [#1l2](https://github.com/editorconfig-checker/editorconfig-checker/pull/112) ([@mstruebing](https://github.com/mstruebing))
 ### Deprecated
 ### Removed
 ### Fixed
diff --git a/README.md b/README.md
index 88d2525..4383a50 100644
--- a/README.md
+++ b/README.md
@@ -40,6 +40,7 @@ Currently implemented editorconfig features are:
 * `trim_trailing_whitespace`
 * `indent_style`
 * `indent_size`
+* `max_line_length`
 
 Unsupported features are:
 * `charset`
@@ -135,7 +136,8 @@ A sample `.ecrc` file can look like this and will be used from your current work
     "Indentation": false,
     "InsertFinalNewline": false,
     "TrimTrailingWhitespace": false,
-    "IndentSize": false
+    "IndentSize": false,
+    "MaxLineLength": false
   }
 }
 ```
diff --git a/cmd/editorconfig-checker/main.go b/cmd/editorconfig-checker/main.go
index b210d3b..f2837ca 100644
--- a/cmd/editorconfig-checker/main.go
+++ b/cmd/editorconfig-checker/main.go
@@ -47,6 +47,7 @@ func init() {
 	flag.BoolVar(&c.Disable.InsertFinalNewline, "disable-insert-final-newline", false, "disables the final newline check")
 	flag.BoolVar(&c.Disable.Indentation, "disable-indentation", false, "disables the indentation check")
 	flag.BoolVar(&c.Disable.IndentSize, "disable-indent-size", false, "disables only the indent-size check")
+	flag.BoolVar(&c.Disable.MaxLineLength, "disable-max-line-length", false, "disables only the max-line-length check")
 
 	flag.Parse()
 
diff --git a/go.mod b/go.mod
index 02e1fd2..c2a6ed1 100644
--- a/go.mod
+++ b/go.mod
@@ -4,7 +4,7 @@ go 1.12
 
 require (
 	github.com/davecgh/go-spew v1.1.1 // indirect
-	github.com/editorconfig/editorconfig-core-go/v2 v2.3.3
+	github.com/editorconfig/editorconfig-core-go/v2 v2.3.2
 	github.com/gopherjs/gopherjs v0.0.0-20190812055157-5d271430af9f // indirect
 	github.com/smartystreets/assertions v1.0.1 // indirect
 	github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337 // indirect
diff --git a/go.sum b/go.sum
index 9dfcacf..74c5b37 100644
--- a/go.sum
+++ b/go.sum
@@ -16,10 +16,7 @@ github.com/editorconfig/editorconfig-core-go/v2 v2.3.1 h1:8+L7G4cCtuYprGaNawfTBq
 github.com/editorconfig/editorconfig-core-go/v2 v2.3.1/go.mod h1:mJYZ8yC2PWr+pabYXwHMfcEe45fh2w2sxk8cudJdLPM=
 github.com/editorconfig/editorconfig-core-go/v2 v2.3.2 h1:j9GLz0kWF9+1T3IX0MOhhvzLtqhFOvIKLhZFxtY95Qc=
 github.com/editorconfig/editorconfig-core-go/v2 v2.3.2/go.mod h1:+u4rFiKVvlbukHyJM76GYXqQcnHScxvQCuKpMLRtJVw=
-github.com/editorconfig/editorconfig-core-go/v2 v2.3.3 h1:+KQjnwIe18EP4OjhkdEnSAoTCd4r1SRGNIHsZMcy0Gc=
-github.com/editorconfig/editorconfig-core-go/v2 v2.3.3/go.mod h1:18LwmSQp3KWHV5W8THrnvpjRTTQlUIIS3N9oNmzjO3A=
 github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
 github.com/gopherjs/gopherjs v0.0.0-20190812055157-5d271430af9f/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
 github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
@@ -35,8 +32,6 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/mod v0.2.0 h1:KU7oHjnv3XNWfa5COkzUifxZmxp1TyI7ImMXqFxLwvQ=
 golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
-golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4=
-golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@@ -59,7 +54,5 @@ gopkg.in/ini.v1 v1.54.0 h1:oM5ElzbIi7gwLnNbPX2M25ED1vSAK3B6dex50eS/6Fs=
 gopkg.in/ini.v1 v1.54.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
 gopkg.in/ini.v1 v1.55.0 h1:E8yzL5unfpW3M6fz/eB7Cb5MQAYSZ7GKo4Qth+N2sgQ=
 gopkg.in/ini.v1 v1.55.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
-gopkg.in/ini.v1 v1.56.0 h1:DPMeDvGTM54DXbPkVIZsp19fp/I2K7zwA/itHYHKo8Y=
-gopkg.in/ini.v1 v1.56.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
diff --git a/pkg/config/config.go b/pkg/config/config.go
index bfd43c5..a8067f3 100644
--- a/pkg/config/config.go
+++ b/pkg/config/config.go
@@ -83,6 +83,7 @@ type DisabledChecks struct {
 	InsertFinalNewline     bool
 	TrimTrailingWhitespace bool
 	IndentSize             bool
+	MaxLineLength          bool
 }
 
 // NewConfig initializes a new config
@@ -200,6 +201,10 @@ func (c *Config) mergeDisabled(disabled DisabledChecks) {
 	if disabled.IndentSize {
 		c.Disable.IndentSize = disabled.IndentSize
 	}
+
+	if disabled.MaxLineLength {
+		c.Disable.MaxLineLength = disabled.MaxLineLength
+	}
 }
 
 // GetExcludesAsRegularExpression returns the excludes as a combined regular expression
diff --git a/pkg/validation/validation.go b/pkg/validation/validation.go
index 0144597..5846317 100644
--- a/pkg/validation/validation.go
+++ b/pkg/validation/validation.go
@@ -65,6 +65,10 @@ func ValidateFile(filePath string, config config.Config) []error.ValidationError
 			validationErrors = append(validationErrors, validationError)
 		}
 
+		validationError = ValidateMaxLineLength(fileInformation, config)
+		if validationError.Message != nil {
+			validationErrors = append(validationErrors, validationError)
+		}
 	}
 
 	return validationErrors
@@ -128,6 +132,21 @@ func ValidateTrailingWhitespace(fileInformation files.FileInformation, config co
 	return error.ValidationError{}
 }
 
+// ValidateMaxLineLength runs the max line length validator and processes the error into the proper type
+func ValidateMaxLineLength(fileInformation files.FileInformation, config config.Config) error.ValidationError {
+	maxLineLength, err := strconv.Atoi(fileInformation.Editorconfig.Raw["max_line_length"])
+	if err != nil {
+		return error.ValidationError{}
+	}
+
+	if currentError := validators.MaxLineLength(fileInformation.Line, maxLineLength); !config.Disable.MaxLineLength && currentError != nil {
+		config.Logger.Verbose(fmt.Sprintf("Max line length error found in %s on %d", fileInformation.FilePath, fileInformation.LineNumber))
+		return error.ValidationError{LineNumber: -1, Message: currentError}
+	}
+
+	return error.ValidationError{}
+}
+
 // ProcessValidation Validates all files and returns an array of validation errors
 func ProcessValidation(files []string, config config.Config) []error.ValidationErrors {
 	var validationErrors []error.ValidationErrors
diff --git a/pkg/validation/validators/validators.go b/pkg/validation/validators/validators.go
index aa9ff45..3236aec 100644
--- a/pkg/validation/validators/validators.go
+++ b/pkg/validation/validators/validators.go
@@ -140,3 +140,12 @@ func LineEnding(fileContent string, endOfLine string) error {
 
 	return nil
 }
+
+func MaxLineLength(line string, maxLineLength int) error {
+	length := len(line)
+	if length > maxLineLength {
+		return fmt.Errorf("Line to long (%d instead of %d)", length, maxLineLength)
+	}
+
+	return nil
+}


Test patch:
diff --git a/pkg/config/config_test.go b/pkg/config/config_test.go
index 899bf60..410d587 100644
--- a/pkg/config/config_test.go
+++ b/pkg/config/config_test.go
@@ -97,6 +97,7 @@ func TestMerge(t *testing.T) {
 			InsertFinalNewline:     true,
 			Indentation:            true,
 			IndentSize:             true,
+			MaxLineLength:          true,
 		},
 	}
 
@@ -169,7 +170,7 @@ func TestGetAsString(t *testing.T) {
 	_ = c.Parse()
 
 	actual := c.GetAsString()
-	expected := "Config: {Version:false Help:false DryRun:false Path:../../.ecrc Verbose:false Debug:false IgnoreDefaults:false SpacesAftertabs:false NoColor:false Exclude:[testfiles] AllowedContentTypes:[text/ application/octet-stream] PassedFiles:[] Disable:{EndOfLine:false Indentation:false InsertFinalNewline:false TrimTrailingWhitespace:false IndentSize:false} Logger:{Verbosee:false Debugg:false NoColor:false}}"
+	expected := "Config: {Version:false Help:false DryRun:false Path:../../.ecrc Verbose:false Debug:false IgnoreDefaults:false SpacesAftertabs:false NoColor:false Exclude:[testfiles] AllowedContentTypes:[text/ application/octet-stream] PassedFiles:[] Disable:{EndOfLine:false Indentation:false InsertFinalNewline:false TrimTrailingWhitespace:false IndentSize:false MaxLineLength:false} Logger:{Verbosee:false Debugg:false NoColor:false}}"
 
 	if actual != expected {
 		t.Errorf("Expected: %v, got: %v ", expected, actual)
diff --git a/pkg/validation/validation_test.go b/pkg/validation/validation_test.go
index 045411d..0f6c84b 100644
--- a/pkg/validation/validation_test.go
+++ b/pkg/validation/validation_test.go
@@ -110,4 +110,17 @@ func TestValidateFile(t *testing.T) {
 	if len(result) != 0 {
 		t.Error("Should have no error, got", result)
 	}
+
+	configuration = config.Config{Verbose: true}
+	result = ValidateFile("./../../testfiles/line-to-long.txt", configuration)
+	if len(result) != 1 {
+		t.Error("Should have no error, got", result)
+	}
+
+	configuration = config.Config{Verbose: true}
+	configuration.Disable.MaxLineLength = true
+	result = ValidateFile("./../../testfiles/line-to-long.txt", configuration)
+	if len(result) != 0 {
+		t.Error("Should have no error, got", result)
+	}
 }
diff --git a/pkg/validation/validators/validators_test.go b/pkg/validation/validators/validators_test.go
index d02d052..6f12cf1 100644
--- a/pkg/validation/validators/validators_test.go
+++ b/pkg/validation/validators/validators_test.go
@@ -239,3 +239,23 @@ func TestTrailingWhitespace(t *testing.T) {
 		}
 	}
 }
+
+func TestMaxLineLength(t *testing.T) {
+	maxLineLengthTest := []struct {
+		line          string
+		maxLineLength int
+		expected      error
+	}{
+		{"", 80, nil},
+		{"abc", 2, errors.New("Line to long (3 instead of 2)")},
+		{"   ", 2, errors.New("Line to long (3 instead of 2)")},
+		{"xx", 2, nil},
+	}
+
+	for _, tt := range maxLineLengthTest {
+		actual := MaxLineLength(tt.line, tt.maxLineLength)
+		if !reflect.DeepEqual(actual, tt.expected) {
+			t.Errorf("Max(%s, %v): expected: %v, got: %v", tt.line, tt.maxLineLength, tt.expected, actual)
+		}
+	}
+}
diff --git a/testfiles/.editorconfig b/testfiles/.editorconfig
index 85aefe5..7e7c604 100644
--- a/testfiles/.editorconfig
+++ b/testfiles/.editorconfig
@@ -14,3 +14,6 @@ insert_final_newline = false
 
 [wrong-line-ending.txt]
 end_of_line = lf
+
+[line-to-long.txt]
+max_line_length = 2
diff --git a/testfiles/generated-config.json b/testfiles/generated-config.json
index 3849367..1eba2a7 100644
--- a/testfiles/generated-config.json
+++ b/testfiles/generated-config.json
@@ -12,6 +12,7 @@
     "Indentation": false,
     "InsertFinalNewline": false,
     "TrimTrailingWhitespace": false,
-    "IndentSize": false
+    "IndentSize": false,
+    "MaxLineLength": false
   }
 }
\ No newline at end of file
diff --git a/testfiles/line-to-long.txt b/testfiles/line-to-long.txt
new file mode 100644
index 0000000..3b18e51
--- /dev/null
+++ b/testfiles/line-to-long.txt
@@ -0,0 +1,1 @@
+hello world