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

Issue:
Support for .ecrc.json
Please fall back to `.ecrc.json` configuration file if `.ecrc` does not exist. It's much easier to validate JSON against the `.json` extension and not add many extra one-off files to a project. Many other tools started to support such options to ease config file validation.

Pull request description:
feat: support `.editorconfig-checker.json` config
Fixes #306

Patch:
diff --git a/README.md b/README.md
index 67db25d..5fd3da0 100644
--- a/README.md
+++ b/README.md
@@ -169,9 +169,12 @@ The following output formats are supported:
 
 ## Configuration
 
-The configuration is done via arguments or an `.ecrc` file.
+The configuration is done via arguments or it will take the first config file found with the following file names:
 
-A sample `.ecrc` file can look like this and will be used from your current working directory if not specified via the `--config` argument:
+- `.editorconfig-checker.json`
+- `.ecrc` (deprecated filename, soon unsupported)
+
+A sample configuration file can look like this and will be used from your current working directory if not specified via the `--config` argument:
 
 ```json
 {
@@ -252,7 +255,7 @@ const myTemplateString = `
 
 You can exclude paths from being checked in several ways:
 - ignoring a file by documenting it inside the to-be-excluded file
-- adding a regex matching the path to the `.ecrc`
+- adding a regex matching the path to the [configuration file](#configuration)
 - passing a regex matching the path as argument to `--exclude`
 
 All these excludes are used in addition to the [default excludes](#default-excludes), unless you [opt out of them](#ignoring-default-excludes).
@@ -330,10 +333,10 @@ If you either set `IgnoreDefaults` to `true` or pass the `-ignore-defaults` comm
 
 ##### via configuration
 
-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](#ignoring-default-excludes)). You should remember to escape your regular expressions correctly. ;)
+In your [configuration file](#configuration) 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](#ignoring-default-excludes)). 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:
+A [configuration file](#configuration) which would ignore all test files and all Markdown files can look like this:
 
 ```json
 {
@@ -354,7 +357,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. The argument given will be added to the excludes as defined by your `.ecrc` (respecting both its [`Exclude`](#via-configuration) and [`IgnoreDefaults`](#ignoring-default-excludes) settings).
+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. The argument given will be added to the excludes as defined by your [configuration file](#configuration) (respecting both its [`Exclude`](#via-configuration) and [`IgnoreDefaults`](#ignoring-default-excludes) settings).
 
 For example: `ec --exclude node_modules`
 
diff --git a/cmd/editorconfig-checker/main.go b/cmd/editorconfig-checker/main.go
index 8970f16..3bfd058 100644
--- a/cmd/editorconfig-checker/main.go
+++ b/cmd/editorconfig-checker/main.go
@@ -18,8 +18,8 @@ import (
 // version is dynamically set at compiletime
 var version string
 
-// defaultConfigFilePath determines where the config is located
-const defaultConfigFilePath = ".ecrc"
+// defaultConfigFileNames determines the file names where the config is located
+var defaultConfigFileNames = []string{".editorconfig-checker.json", ".ecrc"}
 
 // currentConfig is the config used in this run
 var currentConfig *config.Config
@@ -54,11 +54,14 @@ func init() {
 
 	flag.Parse()
 
+	var configPaths = []string{}
 	if configFilePath == "" {
-		configFilePath = defaultConfigFilePath
+		configPaths = append(configPaths, defaultConfigFileNames[:]...)
+	} else {
+		configPaths = append(configPaths, configFilePath)
 	}
 
-	currentConfig, _ = config.NewConfig(configFilePath)
+	currentConfig, _ = config.NewConfig(configPaths)
 
 	if init {
 		err := currentConfig.Save(version)
diff --git a/pkg/config/config.go b/pkg/config/config.go
index 8378f08..17b524d 100644
--- a/pkg/config/config.go
+++ b/pkg/config/config.go
@@ -112,10 +112,13 @@ type DisabledChecks struct {
 }
 
 // NewConfig initializes a new config
-func NewConfig(configPath string) (*Config, error) {
+func NewConfig(configPaths []string) (*Config, error) {
+	if len(configPaths) == 0 {
+		return nil, fmt.Errorf("No config paths provided")
+	}
+
 	var config Config
 
-	config.Path = configPath
 	config.AllowedContentTypes = defaultAllowedContentTypes
 	config.Exclude = []string{}
 	config.PassedFiles = []string{}
@@ -124,9 +127,19 @@ func NewConfig(configPath string) (*Config, error) {
 		Parser: editorconfig.NewCachedParser(),
 	}
 
-	if !utils.IsRegularFile(configPath) {
+	var configPath string = ""
+	for _, path := range configPaths {
+		if utils.IsRegularFile(path) {
+			configPath = path
+			break
+		}
+	}
+	if configPath == "" {
+		var configPath string = configPaths[0]
+		config.Path = configPath
 		return &config, fmt.Errorf("No file found at %s", configPath)
 	}
+	config.Path = configPath
 
 	return &config, nil
 }


Test patch:
diff --git a/pkg/config/config_test.go b/pkg/config/config_test.go
index bcc8c96..04a3fed 100644
--- a/pkg/config/config_test.go
+++ b/pkg/config/config_test.go
@@ -8,13 +8,11 @@ import (
 	"testing"
 )
 
-const (
-	rootConfigFilePath        = "../../.ecrc"
-	configWithIgnoredDefaults = "../../testfiles/.ecrc"
-)
+var rootConfigFilePath = []string{"../../.ecrc"}
+var configWithIgnoredDefaults = []string{"../../testfiles/.ecrc"}
 
 func TestNewConfig(t *testing.T) {
-	actual, _ := NewConfig("abc")
+	actual, _ := NewConfig([]string{"abc"})
 	var expected Config
 
 	if actual == &expected {
@@ -22,6 +20,37 @@ func TestNewConfig(t *testing.T) {
 	}
 }
 
+func TestNoConfigFileFound(t *testing.T) {
+	_, err := NewConfig([]string{"abc"})
+	var expected = "No file found at abc"
+	if err == nil || err.Error() != expected {
+		t.Errorf("expected an error (%s), got %v", expected, err)
+	}
+}
+
+func TestNoConfigs(t *testing.T) {
+	_, err := NewConfig([]string{})
+	var expected = "No config paths provided"
+	if err == nil || err.Error() != expected {
+		t.Errorf("expected an error (%s), got %v", expected, err)
+	}
+}
+
+func TestNoConfigFileFoundInMultiplePaths(t *testing.T) {
+	_, err := NewConfig([]string{"abc", "def"})
+	var expected = "No file found at abc"
+	if err == nil || err.Error() != expected {
+		t.Errorf("expected an error (%s), got %v", expected, err)
+	}
+}
+
+func TestConfigFileFirstFoundInMultiplePaths(t *testing.T) {
+	c, _ := NewConfig([]string{"abc", rootConfigFilePath[0], configWithIgnoredDefaults[0]})
+	if c.Path != rootConfigFilePath[0] {
+		t.Errorf("expected %s, got %s", rootConfigFilePath[0], c.Path)
+	}
+}
+
 func TestGetExcludesAsRegularExpression(t *testing.T) {
 	c, _ := NewConfig(configWithIgnoredDefaults)
 	err := c.Parse()
@@ -60,7 +89,7 @@ func TestGetExcludesAsRegularExpression(t *testing.T) {
 }
 
 func TestMerge(t *testing.T) {
-	c1, err := NewConfig("../../.ecrc")
+	c1, err := NewConfig([]string{"../../.ecrc"})
 	if err != nil {
 		t.Errorf("Expected to create a config without errors, got %v", err)
 	}
@@ -73,7 +102,7 @@ func TestMerge(t *testing.T) {
 	mergeConfig := Config{}
 	c1.Merge(mergeConfig)
 
-	c2, _ := NewConfig("../../.ecrc")
+	c2, _ := NewConfig([]string{"../../.ecrc"})
 	_ = c2.Parse()
 
 	if !reflect.DeepEqual(c1, c2) {
@@ -138,7 +167,7 @@ func TestMerge(t *testing.T) {
 }
 
 func TestParse(t *testing.T) {
-	c, _ := NewConfig("../../testfiles/.ecrc")
+	c, _ := NewConfig([]string{"../../testfiles/.ecrc"})
 	_ = c.Parse()
 
 	if c.Verbose != true ||
@@ -160,7 +189,7 @@ func TestSave(t *testing.T) {
 	dir, _ := os.MkdirTemp("", "example")
 	defer os.RemoveAll(dir)
 	configFile := filepath.Join(dir, "config")
-	c, _ := NewConfig(configFile)
+	c, _ := NewConfig([]string{configFile})
 	if c.Save("VERSION") != nil {
 		t.Error("Should create the config")
 	}
@@ -171,7 +200,7 @@ func TestSave(t *testing.T) {
 }
 
 func TestGetAsString(t *testing.T) {
-	c, _ := NewConfig("../../.ecrc")
+	c, _ := NewConfig([]string{"../../.ecrc"})
 	_ = c.Parse()
 
 	actual := c.GetAsString()