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

Issue:
feat: editorconfig-checker-disable-next-line
It may seen redundant to introduce such a feature at first, but here's [GNU Make](https://www.gnu.org/software/make/manual/html_node/Makefile-Contents.html)

> You cannot use comments within variable references or function calls: any instance of # will be treated literally (rather than as the start of a comment) inside a variable reference or function call.

I bet there are other instances out on the wild, that cannot do with a inline comment to disable editorconfig-checker for that specific line.

Pull request:
feat: editorconfig-checker-disable-next-line
- **fix: broken test**
- **chore: fix typos and style in documentation**
- **feat: add editorconfig-checker-next-line**

Fixes #180 

Patch:
diff --git a/README.md b/README.md
index 4bde08c..10115c2 100644
--- a/README.md
+++ b/README.md
@@ -38,7 +38,7 @@ This tool only needs your `.editorconfig` to check all files.
 
 If you don't know about editorconfig already you can read about it here: [editorconfig.org](https://editorconfig.org/).
 
-Currently implemented editorconfig features are:
+Currently, implemented editorconfig features are:
 
 - `end_of_line`
 - `insert_final_newline`
@@ -84,7 +84,7 @@ Also, development (VCS) package is available in the [AUR](https://aur.archlinux.
 paru -S editorconfig-checker-git
 ```
 
-If go 1.16 or greater is installed, you can also install it globally via `go install`:
+If Go 1.16 or greater is installed, you can also install it globally via `go install`:
 
 ```shell
 go install github.com/editorconfig-checker/editorconfig-checker/v3/cmd/editorconfig-checker@latest
@@ -171,11 +171,11 @@ A sample `.ecrc` file can look like this and will be used from your current work
 
 You can set any of the options under the `"Disable"` section to `true` to disable those particular checks.
 
-You could also specify command line arguments and they will get merged with the configuration file, the command line arguments have a higher precedence than the configuration.
+You could also specify command line arguments, and they will get merged with the configuration file. The command line arguments have a higher precedence than the configuration.
 
-You can create a configuration with the `init`-flag. If you specify an `config`-path it will be created there.
+You can create a configuration with the `init`-flag. If you specify a `config`-path it will be created there.
 
-By default the allowed_content_types are:
+By default, the allowed_content_types are:
 
 1. `text/` (matches `text/plain`, `text/html`, etc.)
 1. `application/ecmascript`
@@ -197,9 +197,19 @@ You can exclude single lines inline. To do that you need a comment on that line
 ```javascript
 const myTemplateString = `
   first line
-     wrongly indended line because it needs to be` // editorconfig-checker-disable-line
+     wrongly indented line because it needs to be` // editorconfig-checker-disable-line
 ```
 
+Alternatively, you can use `editorconfig-checker-disable-next-line` to skip the line that comes after this comment.
+This modifier is present to improve readability, or because your sometimes have no other choice because of your own/language constraints.
+
+```javascript
+// editorconfig-checker-disable-next-line used because blah blah blah what ever the reason blah
+const myTemplateString = `a line that is (...) longer (...) than ... usual` // or with a very long inline comment
+```
+
+Please note that using `editorconfig-checker-disable-next-line` has only an effect on the next line, so it will report if the line where you added the modifier doesn't comply.
+
 ### Excluding Blocks
 
 To temporarily disable all checks, add a comment containing `editorconfig-checker-disable`. Re-enable with a comment containing `editorconfig-checker-enable`
@@ -208,7 +218,7 @@ To temporarily disable all checks, add a comment containing `editorconfig-checke
 // editorconfig-checker-disable
 const myTemplateString = `
   first line
-     wrongly indended line because it needs to be
+     wrongly indented line because it needs to be
 `
 // editorconfig-checker-enable
 ```
@@ -283,7 +293,7 @@ If you don't pass the `ignore-defaults` flag to the binary these files are exclu
 In your `.ecrc` file you can exclude files with the `"exclude"` key which takes an array of regular expressions.
 This will get merged with the default excludes (if not ignored). You should remember to escape your regular expressions correctly. ;)
 
-An `.ecrc` which would ignore all test files and all markdown files can look like this:
+An `.ecrc` which would ignore all test files and all Markdown files can look like this:
 
 ```json
 {
@@ -304,7 +314,7 @@ An `.ecrc` which would ignore all test files and all markdown files can look lik
 
 ##### via arguments
 
-If you want to play around how the tool would behave you can also pass the `--exclude` argument to the binary. This will accept a regular expression as well. If you use this argument the default excludes as well as the excludes from the `.ecrc` file will merged together.
+If you want to play around how the tool would behave you can also pass the `--exclude` argument to the binary. This will accept a regular expression as well. If you use this argument the default excludes as well as the excludes from the `.ecrc` file will be merged together.
 
 For example: `ec --exclude node_modules`
 
@@ -322,7 +332,7 @@ You are able to run this tool inside a Docker container.
 To do this you need to have Docker installed and run this command in your repository root which you want to check:
 `docker run --rm --volume=$PWD:/check mstruebing/editorconfig-checker`
 
-Dockerhub: [mstruebing/editorconfig-checker](https://hub.docker.com/r/mstruebing/editorconfig-checker)
+Docker Hub: [mstruebing/editorconfig-checker](https://hub.docker.com/r/mstruebing/editorconfig-checker)
 
 ## Continuous Integration
 
diff --git a/pkg/validation/validation.go b/pkg/validation/validation.go
index eb5a3ca..a4d2651 100644
--- a/pkg/validation/validation.go
+++ b/pkg/validation/validation.go
@@ -33,6 +33,7 @@ func ValidateFile(filePath string, config config.Config) []error.ValidationError
 	const directiveDisable = directivePrefix + "disable"
 	const directiveDisableFile = directivePrefix + "disable-file"
 	const directiveDisableLine = directivePrefix + "disable-line"
+	const directiveDisableNextLine = directivePrefix + "disable-next-line"
 	const directiveEnable = directivePrefix + "enable"
 
 	var validationErrors []error.ValidationError
@@ -95,22 +96,60 @@ func ValidateFile(filePath string, config config.Config) []error.ValidationError
 		validationErrors = append(validationErrors, validationError)
 	}
 
+	var disableNextLineFound bool // used to ignore the line when editorconfig-checker-disable-next-line was found on previous line
 	for lineNumber, line := range lines {
-		if strings.Contains(line, directiveEnable) {
+		// search for editorconfig-checker-enable
+		// but only if not disabled for performance reasons
+		if isDisabled && strings.Contains(line, directiveEnable) {
 			isDisabled = false
-		} else if directiveIndex := strings.Index(line, directiveDisable); directiveIndex != -1 {
-			if strings.HasPrefix(line[directiveIndex:], directiveDisableLine) {
-				// found editorconfig-checker-disable-line, so just skip current line
-				continue
-			} else {
-				isDisabled = true
-			}
+		}
+
+		// check for the status of the previous line (it was the next line on previous loop iteration)
+		if disableNextLineFound {
+			// editorconfig-checker-disable-next-line was found on previous line
+
+			// check for successive editorconfig-checker-disable-next-line
+			disableNextLineFound = strings.Contains(line, directiveDisableNextLine)
+
+			// there is no need to check for editorconfig-checker-disable-line here, since line will be skipped
+
+			// skip current line
+			continue
 		}
 
 		if isDisabled {
+			// no need to check further if disabled, for performance reasons
 			continue
 		}
 
+		if directiveIndex := strings.Index(line, directiveDisable); directiveIndex != -1 {
+			// a directive STARTING with editorconfig-checker-disable was found
+			// let's check the possible modifiers
+
+			directiveText := line[directiveIndex:] // shorten the text for performance reasons
+
+			// this variable is here for reability, code could have been simplified, but it would have been harder to read
+			activateDisable := true
+
+			// check for editorconfig-checker-disable-next-line, and set status for next line
+			if strings.Contains(directiveText, directiveDisableNextLine) {
+				disableNextLineFound = true
+				// it's not a editorconfig-checker-disable, there is no reason to disable all the following lines
+				activateDisable = false
+			}
+
+			if strings.Contains(directiveText, directiveDisableLine) {
+				// found editorconfig-checker-disable-line, skip current line
+				continue
+			}
+
+			if activateDisable {
+				// found editorconfig-checker-disable, skip current line and all following
+				isDisabled = true
+				continue
+			}
+		}
+
 		fileInformation = files.FileInformation{Line: line, FilePath: filePath, LineNumber: lineNumber, Editorconfig: definition}
 		validationError = ValidateTrailingWhitespace(fileInformation, config)
 		if validationError.Message != nil {


Test patch:
diff --git a/pkg/validation/validation_test.go b/pkg/validation/validation_test.go
index fa19a13..9f89ff0 100644
--- a/pkg/validation/validation_test.go
+++ b/pkg/validation/validation_test.go
@@ -68,6 +68,11 @@ func TestValidateFile(t *testing.T) {
 		t.Error("Should have no errors when validating valid file, got", result)
 	}
 
+	result = ValidateFile("./../../testfiles/disabled-next-line.txt", configuration)
+	if len(result) != 0 {
+		t.Error("Should have no errors when validating valid file, got", result)
+	}
+
 	result = ValidateFile("./../../testfiles/disabled-block.txt", configuration)
 	if len(result) != 0 {
 		t.Error("Should have no errors when validating valid file, got", result)
@@ -116,6 +121,13 @@ func TestValidateFile(t *testing.T) {
 		t.Error("Should have one error, got", result)
 	}
 
+	configuration = config.Config{Verbose: true}
+	result = ValidateFile("./../../testfiles/wrong-next-line.txt", configuration)
+	nbExpectedError := 2
+	if len(result) != nbExpectedError {
+		t.Errorf("Should have %d error, got %v", nbExpectedError, result)
+	}
+
 	configuration = config.Config{Verbose: true}
 	configuration.Disable.EndOfLine = true
 	configuration.Disable.InsertFinalNewline = true
diff --git a/testfiles/disabled-line.txt b/testfiles/disabled-line.txt
index 826a7b2..ef531a8 100644
--- a/testfiles/disabled-line.txt
+++ b/testfiles/disabled-line.txt
@@ -1,3 +1,3 @@
 hello
-	world // editorconfig-checker-disable-line
+  world // editorconfig-checker-disable-line
 what
diff --git a/testfiles/disabled-next-line.txt b/testfiles/disabled-next-line.txt
new file mode 100644
index 0000000..0c57be1
--- /dev/null
+++ b/testfiles/disabled-next-line.txt
@@ -0,0 +1,13 @@
+hello
+whatever // editorconfig-checker-disable-next-line
+  world
+what
+
+# crazy use cases, but people are crazy out there
+hello      // editorconfig-checker-disable-next-line
+  whatever // editorconfig-checker-disable-next-line
+  world
+what
+
+hello
+// editorconfig-checker-disable-next-line // using it on last line should not lead to problem
diff --git a/testfiles/wrong-next-line.txt b/testfiles/wrong-next-line.txt
new file mode 100644
index 0000000..24f0f68
--- /dev/null
+++ b/testfiles/wrong-next-line.txt
@@ -0,0 +1,4 @@
+hello
+  world // editorconfig-checker-disable-next-line has no effect on current line
+
+  world // editorconfig-checker-disable-next-line has no effect on current line, even it's the last one