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

Issue:
Support "tabs for indentation, spaces for alignment" style
This is a continuation of discussion of the [issue in editorconfig-checker.javascript project](https://github.com/editorconfig-checker/editorconfig-checker.javascript/issues/68).

"Tabs for indentation, spaces for alignment" is a common coding style. It allows the code viewers to adjust any tab space they want without breaking alignments. Addition information is presented in these articles: https://dmitryfrank.com/articles/indent_with_tabs_align_with_spaces, https://vim.fandom.com/wiki/Indent_with_tabs,_align_with_spaces. This style is [called "Smart tabs"](https://www.jetbrains.com/help/phpstorm/settings-code-style-tabs-and-indents-common.html#f2a9d78e) in some IDEs (e. g. in JetBrains IDEs).

For now for projects using this style, `editorconfig-checker` shows the following error for each line with alignment:

```
136: Wrong indentation type(spaces instead of tabs)
```

I see several ways to deal with it it.

I think, the most natural way is automatically detect the alignment with spaces. If we are in a block, nested with N tabs, and then if we see spaces after N tabs, we should consider it an alignment with spaces.

Another approach would be to introduce additional command line key to enable this style or additional custom parameter to `.editorconfig` (if this is possible).

Pull request description:
feat(indentation): allow spaces after tab
TODO:
- [x] adjust tests to test correctly for both variants (with spaces allowed and without)
- [x] use options/params parameter for better maintainability instead of simply passing down more props


closes #66

Patch:
diff --git a/.ecrc b/.ecrc
new file mode 100644
index 0000000..6e1d57c
--- /dev/null
+++ b/.ecrc
@@ -0,0 +1,1 @@
+testfiles
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 41dfa1a..aefcd22 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,12 +3,14 @@
 ## Unreleased
 ### Added
 ### Changed
+* (allow spaces after tabs)[https://github.com/editorconfig-checker/editorconfig-checker/pull/67] with flag `spaces-after-tabs`
 ### Deprecated
 ### Removed
 ### Fixed
 ### Security
 ### Misc
-* updated editorconfig-core version to v2 which uses go modules now
+* Some code refactoring (together with (allow spaces after tabs)[https://github.com/editorconfig-checker/editorconfig-checker/pull/67])
+* (updated editorconfig-core version to v2 which uses go modules now)[https://github.com/editorconfig-checker/editorconfig-checker/pull/68]
 
 ## [1.2.1] - 2019-07-06
 ### Fixed
diff --git a/README.md b/README.md
index 35b2d7c..6081c45 100644
--- a/README.md
+++ b/README.md
@@ -66,6 +66,8 @@ USAGE:
   -i    ignore default excludes
   -ignore
         ignore default excludes
+  -spaces-after-tabs
+        allow spaces to be used as alignment after tabs
   -v    print debugging information
   -verbose
         print debugging information
diff --git a/cmd/editorconfig-checker/main.go b/cmd/editorconfig-checker/main.go
index eee1e09..4779e2e 100644
--- a/cmd/editorconfig-checker/main.go
+++ b/cmd/editorconfig-checker/main.go
@@ -2,23 +2,17 @@
 package main
 
 import (
-	"bufio"
 	"flag"
 	"fmt"
-	"io/ioutil"
 	"os"
-	"os/exec"
-	"path/filepath"
-	"regexp"
-	"strconv"
 	"strings"
 
-	"github.com/editorconfig/editorconfig-core-go/v2"
-
+	"github.com/editorconfig-checker/editorconfig-checker/pkg/error"
+	"github.com/editorconfig-checker/editorconfig-checker/pkg/files"
 	"github.com/editorconfig-checker/editorconfig-checker/pkg/logger"
 	"github.com/editorconfig-checker/editorconfig-checker/pkg/types"
 	"github.com/editorconfig-checker/editorconfig-checker/pkg/utils"
-	"github.com/editorconfig-checker/editorconfig-checker/pkg/validators"
+	"github.com/editorconfig-checker/editorconfig-checker/pkg/validation"
 )
 
 // version
@@ -48,6 +42,8 @@ func init() {
 	flag.BoolVar(&params.Verbose, "verbose", false, "print debugging information")
 	flag.BoolVar(&params.Verbose, "v", false, "print debugging information")
 
+	flag.BoolVar(&params.SpacesAfterTabs, "spaces-after-tabs", false, "allow spaces to be used as alignment after tabs")
+
 	// parse flags
 	flag.Parse()
 
@@ -64,8 +60,8 @@ func init() {
 		excludes = utils.DefaultExcludes
 	}
 
-	if utils.PathExists(".ecrc") == nil {
-		lines := readLineNumbersOfFile(".ecrc")
+	if files.PathExists(".ecrc") == nil {
+		lines := files.ReadLines(".ecrc")
 		if len(lines) > 0 {
 			if excludes != "" {
 				excludes = fmt.Sprintf("%s|%s", excludes, strings.Join(lines, "|"))
@@ -89,224 +85,6 @@ func init() {
 	params.RawFiles = rawFiles
 }
 
-// Returns wether the file is inside an unwanted folder
-func isExcluded(filePath string) bool {
-	if params.Excludes == "" {
-		return false
-	}
-
-	relativeFilePath, err := utils.GetRelativePath(filePath)
-	if err != nil {
-		panic(err)
-	}
-
-	result, err := regexp.MatchString(params.Excludes, relativeFilePath)
-	if err != nil {
-		panic(err)
-	}
-
-	return result
-}
-
-// Adds a file to a slice if it isn't already in there
-// and returns the new slice
-func addToFiles(files []string, filePath string, verbose bool) []string {
-	contentType, err := utils.GetContentType(filePath)
-
-	if err != nil {
-		logger.Error(fmt.Sprintf("Could not get the ContentType of file: %s", filePath))
-		logger.Error(err.Error())
-	}
-
-	if !isExcluded(filePath) && utils.IsAllowedContentType(contentType) {
-		if verbose {
-			logger.Output(fmt.Sprintf("Add %s to be checked", filePath))
-		}
-		return append(files, filePath)
-	}
-
-	if verbose {
-		logger.Output(fmt.Sprintf("Don't add %s to be checked", filePath))
-	}
-
-	return files
-}
-
-// Returns all files which should be checked
-func getFiles(verbose bool) []string {
-	var files []string
-
-	byteArray, err := exec.Command("git", "ls-tree", "-r", "--name-only", "HEAD").Output()
-	if err != nil {
-		// It is not a git repository.
-		cwd, err := os.Getwd()
-		if err != nil {
-			panic("Could not get the current working directly")
-		}
-
-		_ = filepath.Walk(cwd, func(path string, fi os.FileInfo, err error) error {
-			if fi.Mode().IsRegular() {
-				files = addToFiles(files, path, verbose)
-			}
-
-			return nil
-		})
-	}
-
-	filesSlice := strings.Split(string(byteArray[:]), "\n")
-
-	for _, filePath := range filesSlice {
-		if len(filePath) > 0 {
-			fi, err := os.Stat(filePath)
-
-			// The err would be a broken symlink for example,
-			// so we want to program to continue but the file should not be checked
-			if err == nil && fi.Mode().IsRegular() {
-				files = addToFiles(files, filePath, verbose)
-			}
-		}
-	}
-
-	return files
-}
-
-func readLineNumbersOfFile(filePath string) []string {
-	var lines []string
-	fileHandle, _ := os.Open(filePath)
-	defer fileHandle.Close()
-	fileScanner := bufio.NewScanner(fileHandle)
-
-	for fileScanner.Scan() {
-		lines = append(lines, fileScanner.Text())
-	}
-
-	return lines
-}
-
-// Validates a single file and returns the errors
-func validateFile(filePath string, verbose bool) []types.ValidationError {
-	var errors []types.ValidationError
-	lines := readLineNumbersOfFile(filePath)
-
-	// return if first line contains editorconfig-checker-disable-file
-	if len(lines) == 0 || strings.Contains(lines[0], "editorconfig-checker-disable-file") {
-		return errors
-	}
-
-	rawFileContent, err := ioutil.ReadFile(filePath)
-
-	if err != nil {
-		panic(err)
-	}
-
-	fileContent := string(rawFileContent)
-
-	editorconfig, err := editorconfig.GetDefinitionForFilename(filePath)
-	if err != nil {
-		panic(err)
-	}
-
-	if currentError := validators.FinalNewline(
-		fileContent,
-		editorconfig.Raw["insert_final_newline"],
-		editorconfig.Raw["end_of_line"]); currentError != nil {
-		if verbose {
-			logger.Output(fmt.Sprintf("Final newline error found in %s", filePath))
-		}
-		errors = append(errors, types.ValidationError{LineNumber: -1, Message: currentError})
-	}
-
-	if currentError := validators.LineEnding(
-		fileContent,
-		editorconfig.Raw["end_of_line"]); currentError != nil {
-		if verbose {
-			logger.Output(fmt.Sprintf("Line ending error found in %s", filePath))
-		}
-		errors = append(errors, types.ValidationError{LineNumber: -1, Message: currentError})
-	}
-
-	for lineNumber, line := range lines {
-		if strings.Contains(line, "editorconfig-checker-disable-line") {
-			continue
-		}
-
-		if currentError := validators.TrailingWhitespace(
-			line,
-			editorconfig.Raw["trim_trailing_whitespace"] == "true"); currentError != nil {
-			if verbose {
-				logger.Output(fmt.Sprintf("Trailing whitespace error found in %s on line %d", filePath, lineNumber))
-			}
-			errors = append(errors, types.ValidationError{LineNumber: lineNumber + 1, Message: currentError})
-		}
-
-		var indentSize int
-		indentSize, err = strconv.Atoi(editorconfig.Raw["indent_size"])
-
-		// Set indentSize to zero if there is no indentSize set
-		if err != nil {
-			indentSize = 0
-		}
-
-		if currentError := validators.Indentation(
-			line,
-			editorconfig.Raw["indent_style"],
-			indentSize); currentError != nil {
-			if verbose {
-				logger.Output(fmt.Sprintf("Indentation error found in %s on line %d", filePath, lineNumber))
-			}
-			errors = append(errors, types.ValidationError{LineNumber: lineNumber + 1, Message: currentError})
-		}
-	}
-
-	return errors
-}
-
-// Validates all files and returns an array of validation errors
-func processValidation(files []string, verbose bool) []types.ValidationErrors {
-	var validationErrors []types.ValidationErrors
-
-	for _, filePath := range files {
-		if verbose {
-			logger.Output(fmt.Sprintf("Validate %s", filePath))
-		}
-		validationErrors = append(validationErrors, types.ValidationErrors{FilePath: filePath, Errors: validateFile(filePath, verbose)})
-	}
-
-	return validationErrors
-}
-
-func getErrorCount(errors []types.ValidationErrors) int {
-	var errorCount = 0
-
-	for _, v := range errors {
-		errorCount += len(v.Errors)
-	}
-
-	return errorCount
-}
-
-func printErrors(errors []types.ValidationErrors) {
-	for _, fileErrors := range errors {
-		if len(fileErrors.Errors) > 0 {
-			relativeFilePath, err := utils.GetRelativePath(fileErrors.FilePath)
-
-			if err != nil {
-				logger.Error(err.Error())
-			}
-
-			logger.Print(fmt.Sprintf("%s:", relativeFilePath), logger.YELLOW, os.Stderr)
-			for _, singleError := range fileErrors.Errors {
-				if singleError.LineNumber != -1 {
-					logger.Error(fmt.Sprintf("\t%d: %s", singleError.LineNumber, singleError.Message))
-				} else {
-					logger.Error(fmt.Sprintf("\t%s", singleError.Message))
-				}
-
-			}
-		}
-	}
-}
-
 // Main function, dude
 func main() {
 	// Check for returnworthy params
@@ -325,26 +103,26 @@ func main() {
 	}
 
 	// contains all files which should be checked
-	files := getFiles(params.Verbose)
+	filePaths := files.GetFiles(params)
 
 	if params.DryRun {
-		for _, file := range files {
+		for _, file := range filePaths {
 			logger.Output(file)
 		}
 
 		os.Exit(0)
 	}
 
-	errors := processValidation(files, params.Verbose)
-	errorCount := getErrorCount(errors)
+	errors := validation.ProcessValidation(filePaths, params)
+	errorCount := error.GetErrorCount(errors)
 
 	if errorCount != 0 {
-		printErrors(errors)
+		error.PrintErrors(errors)
 		logger.Error(fmt.Sprintf("\n%d errors found", errorCount))
 	}
 
 	if params.Verbose {
-		logger.Output(fmt.Sprintf("%d files checked", len(files)))
+		logger.Output(fmt.Sprintf("%d files checked", len(filePaths)))
 	}
 
 	if errorCount != 0 {
diff --git a/pkg/error/error.go b/pkg/error/error.go
new file mode 100644
index 0000000..9312d10
--- /dev/null
+++ b/pkg/error/error.go
@@ -0,0 +1,42 @@
+package error
+
+import (
+	"fmt"
+	"os"
+
+	"github.com/editorconfig-checker/editorconfig-checker/pkg/files"
+	"github.com/editorconfig-checker/editorconfig-checker/pkg/logger"
+	"github.com/editorconfig-checker/editorconfig-checker/pkg/types"
+)
+
+func GetErrorCount(errors []types.ValidationErrors) int {
+	var errorCount = 0
+
+	for _, v := range errors {
+		errorCount += len(v.Errors)
+	}
+
+	return errorCount
+}
+
+func PrintErrors(errors []types.ValidationErrors) {
+	for _, fileErrors := range errors {
+		if len(fileErrors.Errors) > 0 {
+			relativeFilePath, err := files.GetRelativePath(fileErrors.FilePath)
+
+			if err != nil {
+				logger.Error(err.Error())
+			}
+
+			logger.Print(fmt.Sprintf("%s:", relativeFilePath), logger.YELLOW, os.Stderr)
+			for _, singleError := range fileErrors.Errors {
+				if singleError.LineNumber != -1 {
+					logger.Error(fmt.Sprintf("\t%d: %s", singleError.LineNumber, singleError.Message))
+				} else {
+					logger.Error(fmt.Sprintf("\t%s", singleError.Message))
+				}
+
+			}
+		}
+	}
+}
diff --git a/pkg/files/files.go b/pkg/files/files.go
new file mode 100644
index 0000000..270dcc0
--- /dev/null
+++ b/pkg/files/files.go
@@ -0,0 +1,168 @@
+package files
+
+import (
+	"bufio"
+	"fmt"
+	"net/http"
+	"os"
+	"os/exec"
+	"path/filepath"
+	"regexp"
+	"strings"
+
+	"github.com/editorconfig-checker/editorconfig-checker/pkg/logger"
+	"github.com/editorconfig-checker/editorconfig-checker/pkg/types"
+)
+
+// Returns wether the file is inside an unwanted folder
+func IsExcluded(filePath string, params types.Params) bool {
+	if params.Excludes == "" {
+		return false
+	}
+
+	relativeFilePath, err := GetRelativePath(filePath)
+	if err != nil {
+		panic(err)
+	}
+
+	result, err := regexp.MatchString(params.Excludes, relativeFilePath)
+	if err != nil {
+		panic(err)
+	}
+
+	return result
+}
+
+// Adds a file to a slice if it isn't already in there and meets the requirements
+// and returns the new slice
+func AddToFiles(filePaths []string, filePath string, params types.Params) []string {
+	contentType, err := GetContentType(filePath)
+
+	if err != nil {
+		logger.Error(fmt.Sprintf("Could not get the ContentType of file: %s", filePath))
+		logger.Error(err.Error())
+	}
+
+	if !IsExcluded(filePath, params) && IsAllowedContentType(contentType) {
+		if params.Verbose {
+			logger.Output(fmt.Sprintf("Add %s to be checked", filePath))
+		}
+		return append(filePaths, filePath)
+	}
+
+	if params.Verbose {
+		logger.Output(fmt.Sprintf("Don't add %s to be checked", filePath))
+	}
+
+	return filePaths
+}
+
+// Returns all files which should be checked
+func GetFiles(params types.Params) []string {
+	var filePaths []string
+
+	byteArray, err := exec.Command("git", "ls-tree", "-r", "--name-only", "HEAD").Output()
+	if err != nil {
+		// It is not a git repository.
+		cwd, err := os.Getwd()
+		if err != nil {
+			panic("Could not get the current working directly")
+		}
+
+		_ = filepath.Walk(cwd, func(path string, fi os.FileInfo, err error) error {
+			if fi.Mode().IsRegular() {
+				filePaths = AddToFiles(filePaths, path, params)
+			}
+
+			return nil
+		})
+	}
+
+	filesSlice := strings.Split(string(byteArray[:]), "\n")
+
+	for _, filePath := range filesSlice {
+		if len(filePath) > 0 {
+			fi, err := os.Stat(filePath)
+
+			// The err would be a broken symlink for example,
+			// so we want to program to continue but the file should not be checked
+			if err == nil && fi.Mode().IsRegular() {
+				filePaths = AddToFiles(filePaths, filePath, params)
+			}
+		}
+	}
+
+	return filePaths
+}
+
+// Returns the lines from a file as a slice
+func ReadLines(filePath string) []string {
+	var lines []string
+	fileHandle, _ := os.Open(filePath)
+	defer fileHandle.Close()
+	fileScanner := bufio.NewScanner(fileHandle)
+
+	for fileScanner.Scan() {
+		lines = append(lines, fileScanner.Text())
+	}
+
+	return lines
+}
+
+// GetContentType returns the content type of a file
+func GetContentType(path string) (string, error) {
+	fileStat, err := os.Stat(path)
+
+	if err != nil {
+		return "", err
+	}
+
+	if fileStat.Size() == 0 {
+		return "", nil
+	}
+
+	file, err := os.Open(path)
+	if err != nil {
+		return "", err
+	}
+	defer file.Close()
+
+	// Only the first 512 bytes are used to sniff the content type.
+	buffer := make([]byte, 512)
+	_, err = file.Read(buffer)
+	if err != nil {
+		return "", err
+	}
+
+	// Reset the read pointer if necessary.
+	_, err = file.Seek(0, 0)
+	if err != nil {
+		panic(fmt.Sprintf("ERROR: %s", err))
+	}
+
+	// Always returns a valid content-type and "application/octet-stream" if no others seemed to match.
+	return http.DetectContentType(buffer), nil
+}
+
+// PathExists checks wether a path of a file or directory exists or not
+func PathExists(filePath string) error {
+	absolutePath, _ := filepath.Abs(filePath)
+	_, err := os.Stat(absolutePath)
+
+	return err
+}
+
+// GetRelativePath returns the relative path of a file from the current working directory
+func GetRelativePath(filePath string) (string, error) {
+	cwd, err := os.Getwd()
+	if err != nil {
+		return "", fmt.Errorf("Could not get the current working directly")
+	}
+
+	relativePath := strings.Replace(filePath, cwd, ".", 1)
+	return relativePath, nil
+}
+
+func IsAllowedContentType(contentType string) bool {
+	return contentType == "application/octet-stream" || strings.Contains(contentType, "text/")
+}
diff --git a/pkg/types/types.go b/pkg/types/types.go
index b40afc8..e3f247f 100644
--- a/pkg/types/types.go
+++ b/pkg/types/types.go
@@ -3,14 +3,14 @@ package types
 
 // Params is a Struct which represents the cli-params
 type Params struct {
-	Version        bool
-	Help           bool
-	Verbose        bool
-	IgnoreDefaults bool
-	// directories and/or files which should be validated
-	RawFiles []string
-	Excludes string
-	DryRun   bool
+	Version         bool
+	Help            bool
+	Verbose         bool
+	IgnoreDefaults  bool
+	SpacesAfterTabs bool
+	DryRun          bool
+	Excludes        string
+	RawFiles        []string
 }
 
 // ValidationError represents one validation error
diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go
index 49b9322..3466bf4 100644
--- a/pkg/utils/utils.go
+++ b/pkg/utils/utils.go
@@ -2,10 +2,6 @@
 package utils
 
 import (
-	"fmt"
-	"net/http"
-	"os"
-	"path/filepath"
 	"strings"
 )
 
@@ -60,61 +56,3 @@ func GetEolChar(endOfLine string) string {
 
 	return "\n"
 }
-
-// PathExists checks wether a path of a file or directory exists or not
-func PathExists(filePath string) error {
-	absolutePath, _ := filepath.Abs(filePath)
-	_, err := os.Stat(absolutePath)
-
-	return err
-}
-
-// GetContentType returns the content type of a file
-func GetContentType(path string) (string, error) {
-	fileStat, err := os.Stat(path)
-
-	if err != nil {
-		return "", err
-	}
-
-	if fileStat.Size() == 0 {
-		return "", nil
-	}
-
-	file, err := os.Open(path)
-	if err != nil {
-		return "", err
-	}
-	defer file.Close()
-
-	// Only the first 512 bytes are used to sniff the content type.
-	buffer := make([]byte, 512)
-	_, err = file.Read(buffer)
-	if err != nil {
-		return "", err
-	}
-
-	// Reset the read pointer if necessary.
-	_, err = file.Seek(0, 0)
-	if err != nil {
-		panic(fmt.Sprintf("ERROR: %s", err))
-	}
-
-	// Always returns a valid content-type and "application/octet-stream" if no others seemed to match.
-	return http.DetectContentType(buffer), nil
-}
-
-// GetRelativePath returns the relative path of a file from the current working directory
-func GetRelativePath(filePath string) (string, error) {
-	cwd, err := os.Getwd()
-	if err != nil {
-		return "", fmt.Errorf("Could not get the current working directly")
-	}
-
-	relativePath := strings.Replace(filePath, cwd, ".", 1)
-	return relativePath, nil
-}
-
-func IsAllowedContentType(contentType string) bool {
-	return contentType == "application/octet-stream" || strings.Contains(contentType, "text/")
-}
diff --git a/pkg/validation/validation.go b/pkg/validation/validation.go
new file mode 100644
index 0000000..ea16afd
--- /dev/null
+++ b/pkg/validation/validation.go
@@ -0,0 +1,105 @@
+package validation
+
+import (
+	"fmt"
+	"github.com/editorconfig/editorconfig-core-go/v2"
+	"io/ioutil"
+	"strconv"
+	"strings"
+
+	"github.com/editorconfig-checker/editorconfig-checker/pkg/files"
+	"github.com/editorconfig-checker/editorconfig-checker/pkg/logger"
+	"github.com/editorconfig-checker/editorconfig-checker/pkg/types"
+	"github.com/editorconfig-checker/editorconfig-checker/pkg/validation/validators"
+)
+
+// Validates a single file and returns the errors
+func ValidateFile(filePath string, params types.Params) []types.ValidationError {
+	var errors []types.ValidationError
+	lines := files.ReadLines(filePath)
+
+	// return if first line contains editorconfig-checker-disable-file
+	if len(lines) == 0 || strings.Contains(lines[0], "editorconfig-checker-disable-file") {
+		return errors
+	}
+
+	rawFileContent, err := ioutil.ReadFile(filePath)
+	if err != nil {
+		panic(err)
+	}
+
+	fileContent := string(rawFileContent)
+
+	editorconfig, err := editorconfig.GetDefinitionForFilename(filePath)
+	if err != nil {
+		panic(err)
+	}
+
+	if currentError := validators.FinalNewline(
+		fileContent,
+		editorconfig.Raw["insert_final_newline"],
+		editorconfig.Raw["end_of_line"]); currentError != nil {
+		if params.Verbose {
+			logger.Output(fmt.Sprintf("Final newline error found in %s", filePath))
+		}
+		errors = append(errors, types.ValidationError{LineNumber: -1, Message: currentError})
+	}
+
+	if currentError := validators.LineEnding(
+		fileContent,
+		editorconfig.Raw["end_of_line"]); currentError != nil {
+		if params.Verbose {
+			logger.Output(fmt.Sprintf("Line ending error found in %s", filePath))
+		}
+		errors = append(errors, types.ValidationError{LineNumber: -1, Message: currentError})
+	}
+
+	for lineNumber, line := range lines {
+		if strings.Contains(line, "editorconfig-checker-disable-line") {
+			continue
+		}
+
+		if currentError := validators.TrailingWhitespace(
+			line,
+			editorconfig.Raw["trim_trailing_whitespace"] == "true"); currentError != nil {
+			if params.Verbose {
+				logger.Output(fmt.Sprintf("Trailing whitespace error found in %s on line %d", filePath, lineNumber))
+			}
+			errors = append(errors, types.ValidationError{LineNumber: lineNumber + 1, Message: currentError})
+		}
+
+		var indentSize int
+		indentSize, err = strconv.Atoi(editorconfig.Raw["indent_size"])
+
+		// Set indentSize to zero if there is no indentSize set
+		if err != nil {
+			indentSize = 0
+		}
+
+		if currentError := validators.Indentation(
+			line,
+			editorconfig.Raw["indent_style"],
+			indentSize, params); currentError != nil {
+			if params.Verbose {
+				logger.Output(fmt.Sprintf("Indentation error found in %s on line %d", filePath, lineNumber))
+			}
+			errors = append(errors, types.ValidationError{LineNumber: lineNumber + 1, Message: currentError})
+		}
+	}
+
+	return errors
+}
+
+// Validates all files and returns an array of validation errors
+func ProcessValidation(files []string, params types.Params) []types.ValidationErrors {
+	var validationErrors []types.ValidationErrors
+
+	for _, filePath := range files {
+		if params.Verbose {
+			logger.Output(fmt.Sprintf("Validate %s", filePath))
+		}
+		validationErrors = append(validationErrors, types.ValidationErrors{FilePath: filePath, Errors: ValidateFile(filePath, params)})
+	}
+
+	return validationErrors
+}
diff --git a/pkg/validators/validators.go b/pkg/validation/validators/validators.go
similarity index 85%
rename from pkg/validators/validators.go
rename to pkg/validation/validators/validators.go
index e08336d..6974ccb 100644
--- a/pkg/validators/validators.go
+++ b/pkg/validation/validators/validators.go
@@ -7,15 +7,16 @@ import (
 	"regexp"
 	"strings"
 
+	"github.com/editorconfig-checker/editorconfig-checker/pkg/types"
 	"github.com/editorconfig-checker/editorconfig-checker/pkg/utils"
 )
 
 // Indentation validates a files indentation
-func Indentation(line string, indentStyle string, indentSize int) error {
+func Indentation(line string, indentStyle string, indentSize int, params types.Params) error {
 	if indentStyle == "space" {
 		return Space(line, indentSize)
 	} else if indentStyle == "tab" {
-		return Tab(line)
+		return Tab(line, params)
 	}
 
 	// if no indentStyle is given it should be valid
@@ -42,12 +43,20 @@ func Space(line string, indentSize int) error {
 }
 
 // Tab validates if a line is indented with only tabs
-func Tab(line string) error {
+func Tab(line string, params types.Params) error {
 	if len(line) > 0 {
-		// match recurring tabs - this can be recurring or never
-		// match either a space followed by a * and maybe a space (block-comments)
-		// or match everything despite a space or tab-character
+		// match starting with one or more tabs followed by a non-whitespace char
+		// OR
+		// match starting with one or more tabs, followed by one space and followed by at least one non-whitespace character
+		// OR
+		// match starting with a space followed by at least one non-whitespace character
+
 		regexpPattern := "^(\t)*( \\* ?|[^ \t])"
+
+		if params.SpacesAfterTabs {
+			regexpPattern = "(^(\t)*\\S)|(^(\t)+( )*\\S)|(^ \\S)"
+		}
+
 		matched, _ := regexp.MatchString(regexpPattern, line)
 
 		if !matched {


Test patch:
diff --git a/cmd/editorconfig-checker/main_test.go b/cmd/editorconfig-checker/main_test.go
index 96c8de9..97adee8 100644
--- a/cmd/editorconfig-checker/main_test.go
+++ b/cmd/editorconfig-checker/main_test.go
@@ -6,30 +6,6 @@ import (
 	"testing"
 )
 
-func TestProcessValidation(t *testing.T) {
-	// Should not have errors when validating main.go
-	processValidationResult := processValidation([]string{"./main.go"}, false)
-	if len(processValidationResult) > 1 && len(processValidationResult[0].Errors) != 0 {
-		t.Error("Expected something, got", processValidationResult)
-	}
-
-	processValidationResult = processValidation([]string{"./main.go"}, true)
-	if len(processValidationResult) > 1 && len(processValidationResult[0].Errors) != 0 {
-		t.Error("Expected something, got", processValidationResult)
-	}
-
-	processValidationResult = processValidation([]string{"./../../testfiles/disabled-file.ext"}, true)
-	if len(processValidationResult) > 1 && len(processValidationResult[0].Errors) != 0 {
-		t.Error("Expected something, got", processValidationResult)
-	}
-
-	// empty file should have no errors
-	processValidationResult = processValidation([]string{"./../../testfiles/empty-file.txt"}, true)
-	if len(processValidationResult) > 1 && len(processValidationResult[0].Errors) != 0 {
-		t.Error("Expected something, got", processValidationResult)
-	}
-}
-
 func BenchmarkMain(b *testing.B) {
 	// run the binary b.N times
 	for n := 0; n < b.N; n++ {
diff --git a/pkg/error/error_test.go b/pkg/error/error_test.go
new file mode 100644
index 0000000..e7be6d0
--- /dev/null
+++ b/pkg/error/error_test.go
@@ -0,0 +1,147 @@
+package error
+
+import (
+	"errors"
+	"testing"
+
+	"github.com/editorconfig-checker/editorconfig-checker/pkg/types"
+)
+
+func TestGetErrorCount(t *testing.T) {
+	count := GetErrorCount([]types.ValidationErrors{})
+	if count != 0 {
+		t.Error("Expected empty slice to have no errors, got", count)
+	}
+
+	input := []types.ValidationErrors{
+		{
+			FilePath: "some/path",
+			Errors: []types.ValidationError{
+				{
+					LineNumber: 1,
+					Message:    errors.New("WRONG"),
+				},
+			},
+		},
+	}
+
+	count = GetErrorCount(input)
+	if count != 1 {
+		t.Error("Expected one error slice to have exactly one erorr errors, got", count)
+	}
+
+	input = []types.ValidationErrors{
+		{
+			FilePath: "some/path",
+			Errors: []types.ValidationError{
+				{
+					LineNumber: 1,
+					Message:    errors.New("WRONG"),
+				},
+			},
+		},
+		{
+			FilePath: "some/other/path",
+			Errors: []types.ValidationError{
+				{
+					LineNumber: 1,
+					Message:    errors.New("WRONG"),
+				},
+			},
+		},
+	}
+
+	count = GetErrorCount(input)
+	if count != 2 {
+		t.Error("Expected two error slice to have exactly one erorr errors, got", count)
+	}
+
+	input = []types.ValidationErrors{
+		{
+			FilePath: "some/path",
+			Errors: []types.ValidationError{
+				{
+					LineNumber: 1,
+					Message:    errors.New("WRONG"),
+				},
+			},
+		},
+		{
+			FilePath: "some/other/path",
+			Errors: []types.ValidationError{
+				{
+					LineNumber: 1,
+					Message:    errors.New("WRONG"),
+				},
+				{
+					LineNumber: 2,
+					Message:    errors.New("WRONG"),
+				},
+			},
+		},
+	}
+
+	count = GetErrorCount(input)
+	if count != 3 {
+		t.Error("Expected three error slice to have exactly one erorr errors, got", count)
+	}
+
+	input = []types.ValidationErrors{
+		{
+			FilePath: "some/path",
+			Errors:   []types.ValidationError{},
+		},
+		{
+			FilePath: "some/other/path",
+			Errors: []types.ValidationError{
+				{
+					LineNumber: 1,
+					Message:    errors.New("WRONG"),
+				},
+				{
+					LineNumber: 2,
+					Message:    errors.New("WRONG"),
+				},
+			},
+		},
+	}
+
+	count = GetErrorCount(input)
+	if count != 2 {
+		t.Error("Expected three error slice to have exactly one erorr errors, got", count)
+	}
+}
+
+func TestPrintErrors(t *testing.T) {
+	input := []types.ValidationErrors{
+		{
+			FilePath: "some/path",
+			Errors:   []types.ValidationError{},
+		},
+		{
+			FilePath: "/proc/cpuinfo",
+			Errors: []types.ValidationError{
+				{
+					LineNumber: 1,
+					Message:    errors.New("WRONG"),
+				},
+			},
+		},
+		{
+			FilePath: "some/other/path",
+			Errors: []types.ValidationError{
+				{
+					LineNumber: 1,
+					Message:    errors.New("WRONG"),
+				},
+				{
+					LineNumber: -1,
+					Message:    errors.New("WRONG"),
+				},
+			},
+		},
+	}
+
+	// wannabe test
+	PrintErrors(input)
+}
diff --git a/pkg/files/files_test.go b/pkg/files/files_test.go
new file mode 100644
index 0000000..48ad904
--- /dev/null
+++ b/pkg/files/files_test.go
@@ -0,0 +1,106 @@
+package files
+
+import (
+	"fmt"
+	"os"
+	"strings"
+	"testing"
+
+	"github.com/editorconfig-checker/editorconfig-checker/pkg/types"
+)
+
+func TestGetContentType(t *testing.T) {
+	contentType, _ := GetContentType("./files.go")
+	if !strings.Contains(contentType, "text/plain") {
+		t.Error("Expected getContentType.go to be of type text/plain")
+	}
+
+	contentType, _ = GetContentType("./../../docs/logo.png")
+	if !strings.Contains(contentType, "image/png") {
+		t.Error("Expected getContentType_test.go to be of type application/octet-stream")
+	}
+
+	_, err := GetContentType(".")
+	if err == nil {
+		t.Error("Expected to return an error for a directory")
+	}
+
+	_, err = GetContentType("/abc/!@#")
+	if err == nil {
+		t.Error("Expected to return an error for a not existing file")
+	}
+
+	emptyFile, _ := os.Create("empty.txt")
+	defer emptyFile.Close()
+	defer os.Remove("empty.txt")
+
+	contentType, err = GetContentType("empty.txt")
+	if contentType != "" || err != nil {
+		t.Error("Expected to return an empty string for an empty file and no error")
+	}
+}
+
+func TestIsAllowedContentType(t *testing.T) {
+	if IsAllowedContentType("bla") {
+		t.Error("Bla shouldn't be an allowed contentType")
+	}
+
+	if !IsAllowedContentType("text/") {
+		t.Error("text/ shouldn't be an allowed contentType")
+	}
+
+	if !IsAllowedContentType("text/xml abc") {
+		t.Error("text/xml shouldn't be an allowed contentType")
+	}
+}
+
+func TestPathExists(t *testing.T) {
+	if PathExists(".") != nil {
+		t.Error("Expected . to be an existing path")
+	}
+
+	if PathExists("notexisting") == nil {
+		t.Error("Expected \"notexisting\" to not exist")
+	}
+}
+
+func TestGetRelativePath(t *testing.T) {
+	cwd, _ := os.Getwd()
+	filePath := "/bin/ec"
+	relativeFilePath, _ := GetRelativePath(cwd + filePath)
+
+	if relativeFilePath != "."+filePath {
+		t.Error("Expected the relative filePath to match")
+	}
+
+	DIR := "/tmp/stuff"
+	os.Remove(DIR)
+	err := os.Mkdir(DIR, 0755)
+	if err != nil {
+		panic(fmt.Sprintf("ERROR: %s", err))
+	}
+
+	err = os.Chdir(DIR)
+	if err != nil {
+		panic(fmt.Sprintf("ERROR: %s", err))
+	}
+
+	err = os.Remove(DIR)
+	if err != nil {
+		panic(fmt.Sprintf("ERROR: %s", err))
+	}
+
+	_, err = GetRelativePath(cwd + filePath)
+
+	if err == nil {
+		t.Error("Expected an error for a not existing directory")
+	}
+}
+
+func TestIsExcluded(t *testing.T) {
+	result := IsExcluded("./cmd/editorconfig-checker/main.go", types.Params{})
+
+	if result {
+		t.Error("Should return true if no excludes are given, got", result)
+	}
+}
diff --git a/pkg/logger/logger_test.go b/pkg/logger/logger_test.go
new file mode 100644
index 0000000..f678dcd
--- /dev/null
+++ b/pkg/logger/logger_test.go
@@ -0,0 +1,26 @@
+package logger
+
+import (
+	"os"
+	"testing"
+)
+
+// Wannbe tests
+
+func TestWarning(t *testing.T) {
+	Warning("Hello")
+}
+
+func TestError(t *testing.T) {
+	Error("Hello")
+}
+
+func TestOutput(t *testing.T) {
+	Output("Hello")
+}
+
+func TestPrint(t *testing.T) {
+	Print("Hello", RED, os.Stdout)
+	Print("Hello", GREEN, os.Stdout)
+	Print("Hello", YELLOW, os.Stdout)
+}
diff --git a/pkg/utils/utils_test.go b/pkg/utils/utils_test.go
index 81cd685..1c60947 100644
--- a/pkg/utils/utils_test.go
+++ b/pkg/utils/utils_test.go
@@ -1,19 +1,14 @@
 package utils
 
 import (
-	"fmt"
-	"os"
-	"strings"
 	"testing"
 )
 
-func TestPathExists(t *testing.T) {
-	if PathExists(".") != nil {
-		t.Error("Expected . to be an existing path")
-	}
+func TestDefaultExcludes(t *testing.T) {
+	result := "yarn\\.lock$|package-lock\\.json|composer\\.lock$|\\.snap$|\\.otf$|\\.woff$|\\.woff2$|\\.eot$|\\.ttf$|\\.gif$|\\.png$|\\.jpg$|\\.jpeg$|\\.mp4$|\\.wmv$|\\.svg$|\\.ico$|\\.bak$|\\.bin$|\\.pdf$|\\.zip$|\\.gz$|\\.tar$|\\.7z$|\\.bz2$|\\.log$|\\.css\\.map$|\\.js\\.map$|min\\.css$|min\\.js$"
 
-	if PathExists("notexisting") == nil {
-		t.Error("Expected \"notexisting\" to not exist")
+	if DefaultExcludes != result {
+		t.Error("Expected default excludes to match", result)
 	}
 }
 
@@ -34,89 +29,3 @@ func TestGetEolChar(t *testing.T) {
 		t.Error("Expected end of line character to be \\n as a fallback")
 	}
 }
-
-func TestGetContentType(t *testing.T) {
-	contentType, _ := GetContentType("./utils.go")
-	if !strings.Contains(contentType, "text/plain") {
-		t.Error("Expected getContentType.go to be of type text/plain")
-	}
-
-	contentType, _ = GetContentType("./../../docs/logo.png")
-	if !strings.Contains(contentType, "image/png") {
-		t.Error("Expected getContentType_test.go to be of type application/octet-stream")
-	}
-
-	_, err := GetContentType(".")
-	if err == nil {
-		t.Error("Expected to return an error for a directory")
-	}
-
-	_, err = GetContentType("/abc/!@#")
-	if err == nil {
-		t.Error("Expected to return an error for a not existing file")
-	}
-
-	emptyFile, _ := os.Create("empty.txt")
-	defer emptyFile.Close()
-	defer os.Remove("empty.txt")
-
-	contentType, err = GetContentType("empty.txt")
-	if contentType != "" || err != nil {
-		t.Error("Expected to return an empty string for an empty file and no error")
-	}
-}
-
-func TestGetRelativePath(t *testing.T) {
-	cwd, _ := os.Getwd()
-	filePath := "/bin/ec"
-	relativeFilePath, _ := GetRelativePath(cwd + filePath)
-
-	if relativeFilePath != "."+filePath {
-		t.Error("Expected the relative filePath to match")
-	}
-
-	DIR := "/tmp/stuff"
-	os.Remove(DIR)
-	err := os.Mkdir(DIR, 0755)
-	if err != nil {
-		panic(fmt.Sprintf("ERROR: %s", err))
-	}
-
-	err = os.Chdir(DIR)
-	if err != nil {
-		panic(fmt.Sprintf("ERROR: %s", err))
-	}
-
-	err = os.Remove(DIR)
-	if err != nil {
-		panic(fmt.Sprintf("ERROR: %s", err))
-	}
-
-	_, err = GetRelativePath(cwd + filePath)
-
-	if err == nil {
-		t.Error("Expected an error for a not existing directory")
-	}
-}
-
-func TestDefaultExcludes(t *testing.T) {
-	result := "yarn\\.lock$|package-lock\\.json|composer\\.lock$|\\.snap$|\\.otf$|\\.woff$|\\.woff2$|\\.eot$|\\.ttf$|\\.gif$|\\.png$|\\.jpg$|\\.jpeg$|\\.mp4$|\\.wmv$|\\.svg$|\\.ico$|\\.bak$|\\.bin$|\\.pdf$|\\.zip$|\\.gz$|\\.tar$|\\.7z$|\\.bz2$|\\.log$|\\.css\\.map$|\\.js\\.map$|min\\.css$|min\\.js$"
-
-	if DefaultExcludes != result {
-		t.Error("Expected default excludes to match", result)
-	}
-}
-
-func TestIsAllowedContentType(t *testing.T) {
-	if IsAllowedContentType("bla") {
-		t.Error("Bla shouldn't be an allowed contentType")
-	}
-
-	if !IsAllowedContentType("text/") {
-		t.Error("text/ shouldn't be an allowed contentType")
-	}
-
-	if !IsAllowedContentType("text/xml abc") {
-		t.Error("text/xml shouldn't be an allowed contentType")
-	}
-}
diff --git a/pkg/validation/validation_test.go b/pkg/validation/validation_test.go
new file mode 100644
index 0000000..f9d30e3
--- /dev/null
+++ b/pkg/validation/validation_test.go
@@ -0,0 +1,86 @@
+package validation
+
+import (
+	"testing"
+
+	"github.com/editorconfig-checker/editorconfig-checker/pkg/types"
+)
+
+func TestProcessValidation(t *testing.T) {
+	params := types.Params{Verbose: true}
+
+	processValidationResult := ProcessValidation([]string{"./main.go"}, params)
+	if len(processValidationResult) > 1 || len(processValidationResult[0].Errors) != 0 {
+		t.Error("Should not have errors when validating main.go, got", processValidationResult)
+	}
+
+	processValidationResult = ProcessValidation([]string{"./../../testfiles/disabled-file.ext"}, params)
+	if len(processValidationResult) > 1 || len(processValidationResult[0].Errors) != 0 {
+		t.Error("Disabled file should have no errors, got", processValidationResult)
+	}
+
+	processValidationResult = ProcessValidation([]string{"./../../testfiles/empty-file.txt"}, params)
+	if len(processValidationResult) > 1 || len(processValidationResult[0].Errors) != 0 {
+		t.Error("Empty file should have no errors, got", processValidationResult)
+	}
+
+	processValidationResult = ProcessValidation([]string{"./../../testfiles/wrong-file.txt"}, params)
+	if (len(processValidationResult) > 1) || (len(processValidationResult[0].Errors) != 1) {
+		t.Error("Wrong file should have errors, got", processValidationResult)
+	}
+}
+
+func TestValidateFile(t *testing.T) {
+	params := types.Params{Verbose: true}
+
+	result := ValidateFile("./main.go", params)
+	if len(result) != 0 {
+		t.Error("Should not have errors when validating main.go, got", result)
+	}
+
+	result = ValidateFile("./../../testfiles/wrong-file.txt", params)
+	if len(result) != 1 {
+		t.Error("Should have errors when validating file with one error, got", result)
+	}
+
+	params = types.Params{SpacesAfterTabs: true}
+	result = ValidateFile("./../../testfiles/spaces-after-tabs.txt", params)
+	if len(result) != 0 {
+		t.Error("Should have no errors when validating valid file, got", result)
+	}
+
+	params = types.Params{SpacesAfterTabs: false}
+	result = ValidateFile("./../../testfiles/zero-indent.txt", params)
+	if len(result) != 0 {
+		t.Error("Should have no errors when validating valid file, got", result)
+	}
+
+	result = ValidateFile("./../../testfiles/disabled-line.txt", params)
+	if len(result) != 0 {
+		t.Error("Should have no errors when validating valid file, got", result)
+	}
+
+	params = types.Params{SpacesAfterTabs: false}
+	result = ValidateFile("./../../testfiles/spaces-after-tabs.txt", params)
+	if len(result) != 1 {
+		t.Error("Should have one error, got", result)
+	}
+
+	params = types.Params{Verbose: true}
+	result = ValidateFile("./../../testfiles/trailing-whitespace.txt", params)
+	if len(result) != 1 {
+		t.Error("Should have one error, got", result)
+	}
+
+	params = types.Params{Verbose: true}
+	result = ValidateFile("./../../testfiles/final-newline-missing.txt", params)
+	if len(result) != 1 {
+		t.Error("Should have no error, got", result)
+	}
+
+	params = types.Params{Verbose: true}
+	result = ValidateFile("./../../testfiles/wrong-line-ending.txt", params)
+	if len(result) == 0 {
+		t.Error("Should have no error, got", result)
+	}
+}
diff --git a/pkg/validators/validators_test.go b/pkg/validation/validators/validators_test.go
similarity index 86%
rename from pkg/validators/validators_test.go
rename to pkg/validation/validators/validators_test.go
index 59ca271..49dc90f 100644
--- a/pkg/validators/validators_test.go
+++ b/pkg/validation/validators/validators_test.go
@@ -2,6 +2,8 @@ package validators
 
 import (
 	"testing"
+
+	"github.com/editorconfig-checker/editorconfig-checker/pkg/types"
 )
 
 func TestFinalNewlineTrue(t *testing.T) {
@@ -222,27 +224,28 @@ func TestLineEndingCrlf(t *testing.T) {
 }
 
 func TestIndentation(t *testing.T) {
-	if (Indentation("    x", "space", 4)) != nil {
+	params := types.Params{SpacesAfterTabs: false}
+	if (Indentation("    x", "space", 4, params)) != nil {
 		t.Error("Expected correctly indented line to return an nil")
 	}
 
-	if (Indentation("   x", "space", 4)) == nil {
+	if (Indentation("   x", "space", 4, params)) == nil {
 		t.Error("Expected wrong indented line to return an error")
 	}
 
-	if (Indentation("	x", "tab", 0)) != nil {
+	if (Indentation("	x", "tab", 0, params)) != nil {
 		t.Error("Expected correctly indented line to return an nil")
 	}
 
-	if (Indentation("   x", "tab", 0)) == nil {
+	if (Indentation("   x", "tab", 0, params)) == nil {
 		t.Error("Expected wrong indented line to return an error")
 	}
 
-	if (Indentation("	x", "x", 0)) != nil {
+	if (Indentation("	x", "x", 0, params)) != nil {
 		t.Error("Expected unknown indentation to return nil")
 	}
 
-	if (Indentation("   x", "x", 0)) != nil {
+	if (Indentation("   x", "x", 0, params)) != nil {
 		t.Error("Expected unknown indentation to return nil")
 	}
 }
@@ -282,45 +285,72 @@ func TestSpace(t *testing.T) {
 }
 
 func TestTab(t *testing.T) {
-	if Tab("") != nil {
+	spacesAllowed := types.Params{SpacesAfterTabs: true}
+	spacesForbidden := types.Params{SpacesAfterTabs: false}
+
+	if Tab("", spacesForbidden) != nil {
 		t.Error("Expected empty line to return true regardless of parameter")
 	}
 
-	if Tab("x") != nil {
+	if Tab("x", spacesForbidden) != nil {
 		t.Error("Expected line which starts at the beginning to return true")
 	}
 
-	if Tab("	x") != nil {
+	if Tab("	x", spacesForbidden) != nil {
 		t.Error("Expected correctly indented line to return true")
 	}
 
-	if Tab(" x") == nil {
-		t.Error("Expected falsy indented line to return false")
+	if Tab(" x", spacesAllowed) != nil {
+		t.Error("Expected char after space to return nil")
 	}
 
-	if Tab(" *") != nil {
+	if Tab(" *", spacesForbidden) != nil {
 		t.Error("Expected tab indented block comment without indentation to return nil")
 	}
 
-	if Tab(" * some comment") != nil {
+	if Tab(" * some comment", spacesForbidden) != nil {
 		t.Error("Expected tab indented block comment without indentation and with comment to return nil")
 	}
 
-	if Tab("	 *") != nil {
+	if Tab("	 *", spacesForbidden) != nil {
 		t.Error("Expected tab indented block comment with indentation and without comment to return nil")
 	}
 
-	if Tab("	 * some comment") != nil {
+	if Tab("	 * some comment", spacesForbidden) != nil {
 		t.Error("Expected tab indented block comment with indentation and with comment to return nil")
 	}
 
-	if Tab(" */") != nil {
+	if Tab(" */", spacesForbidden) != nil {
 		t.Error("Expected tab indented block comment without indentation in the last line to return nil")
 	}
 
-	if Tab("	 */") != nil {
+	if Tab("	 */", spacesForbidden) != nil {
 		t.Error("Expected tab indented block comment with indentation in the last line to return nil")
 	}
+
+	if Tab("	   bla", spacesAllowed) != nil {
+		t.Error("Expected tab indented and with spaces aligned line to return nil")
+	}
+
+	if Tab("	 bla", spacesAllowed) != nil {
+		t.Error("Expected tab indented and with spaces aligned line to return nil")
+	}
+
+	if (Tab("		  xx", spacesAllowed)) != nil {
+		t.Error("starting with tabs and trailing spaces with at least one character after the spaces")
+	}
+
+	if (Tab("		x", spacesForbidden)) != nil {
+		t.Error("starting with tabs and characters after that")
+	}
+
+	if (Tab("  	a", spacesForbidden)) == nil {
+		t.Error("Starting with two spaces, followed by a tab and followed by a non whitespace char should not return nil")
+	}
+
+	if (Tab(" *", spacesForbidden)) != nil {
+		t.Error("starting with a space and at leat one character after the space")
+	}
 }
 
 func TestTrailingWhitespace(t *testing.T) {
diff --git a/testfiles/.editorconfig b/testfiles/.editorconfig
index 0b225af..85aefe5 100644
--- a/testfiles/.editorconfig
+++ b/testfiles/.editorconfig
@@ -1,2 +1,16 @@
 [*]
 indent_style = tab
+
+[trailing-whitespace.txt]
+trim_trailing_whitespace = true
+
+[zero-indent.txt]
+indent_style = space
+indent_size =
+
+[final-newline-missing.txt]
+end_of_line = lf
+insert_final_newline = false
+
+[wrong-line-ending.txt]
+end_of_line = lf
diff --git a/testfiles/disabled-line.txt b/testfiles/disabled-line.txt
new file mode 100644
index 0000000..826a7b2
--- /dev/null
+++ b/testfiles/disabled-line.txt
@@ -0,0 +1,3 @@
+hello
+	world // editorconfig-checker-disable-line
+what
diff --git a/testfiles/final-newline-missing.txt b/testfiles/final-newline-missing.txt
new file mode 100644
index 0000000..5e19b10
--- /dev/null
+++ b/testfiles/final-newline-missing.txt
@@ -0,0 +1,3 @@
+helllo
+nice
+world
diff --git a/testfiles/spaces-after-tabs.txt b/testfiles/spaces-after-tabs.txt
new file mode 100644
index 0000000..4609ff5
--- /dev/null
+++ b/testfiles/spaces-after-tabs.txt
@@ -0,0 +1,2 @@
+hello
+	  world
diff --git a/testfiles/trailing-whitespace.txt b/testfiles/trailing-whitespace.txt
new file mode 100644
index 0000000..98b6af6
--- /dev/null
+++ b/testfiles/trailing-whitespace.txt
@@ -0,0 +1,1 @@
+ahhaaa    
diff --git a/testfiles/wrong-file.txt b/testfiles/wrong-file.txt
new file mode 100644
index 0000000..cdbf1d6
--- /dev/null
+++ b/testfiles/wrong-file.txt
@@ -0,0 +1,2 @@
+hello
+  world
diff --git a/testfiles/wrong-line-ending.txt b/testfiles/wrong-line-ending.txt
new file mode 100644
index 0000000..9ffbba1
--- /dev/null
+++ b/testfiles/wrong-line-ending.txt
@@ -0,0 +1,4 @@
+hello
+world
+whats
+up
diff --git a/testfiles/zero-indent.txt b/testfiles/zero-indent.txt
new file mode 100644
index 0000000..ba418b1
--- /dev/null
+++ b/testfiles/zero-indent.txt
@@ -0,0 +1,2 @@
+hello
+    world