Repository: rrrene/credo
Pull request #501 (2018-01-30 20:16:58)

Command-line switch to load a custom configuration file
This functionality would be useful for a [web service](https://github.com/revelrylabs/lintron) I use that lints GitHub PRs. It [uses credo](https://github.com/revelrylabs/lintron/blob/master/app/models/linters/credo.rb) with the `--read-from-stdin` switch, which works great. Since credo is invoked in the context of the web service, though, rather than the project whose PR is being linted, it can't use that project's credo config without a path override like this.

I think this option is definitely going to be helpful to anybody using stdin or passing a [list of files](https://github.com/rrrene/credo/pull/334#issuecomment-288332243).

Please let me know if a feature like this would be compatible with credo's goals and if so, what else I can do to help push this work forward.


Patch:
diff --git a/lib/credo/cli/options.ex b/lib/credo/cli/options.ex
index ff6a1c3a..2d404fd3 100644
--- a/lib/credo/cli/options.ex
+++ b/lib/credo/cli/options.ex
@@ -11,6 +11,7 @@ defmodule Credo.CLI.Options do
     all: :boolean,
     checks: :string,
     config_name: :string,
+    config_file: :string,
     color: :boolean,
     crash_on_error: :boolean,
     debug: :boolean,
diff --git a/lib/credo/config_builder.ex b/lib/credo/config_builder.ex
index 70d81fed..2b46464a 100644
--- a/lib/credo/config_builder.ex
+++ b/lib/credo/config_builder.ex
@@ -1,17 +1,13 @@
 defmodule Credo.ConfigBuilder do
   alias Credo.CLI.Filename
+  alias Credo.CLI.Options
   alias Credo.CLI.Output.UI
   alias Credo.ConfigFile
   alias Credo.Execution
 
   def parse(exec) do
     options = exec.cli_options
-    config_name = options.switches[:config_name]
-
-    config_file =
-      options.path
-      |> Filename.remove_line_no_and_column()
-      |> ConfigFile.read_or_default(config_name)
+    config_file = get_config_file(options)
 
     exec
     |> add_config_file_to_exec(config_file)
@@ -19,6 +15,18 @@ defmodule Credo.ConfigBuilder do
     |> add_switches_to_exec(options.switches)
   end
 
+  defp get_config_file(%Options{} = options) do
+    config_name = options.switches[:config_name]
+    config_file = options.switches[:config_file]
+    dir = Filename.remove_line_no_and_column(options.path)
+
+    if is_binary(config_file) do
+      ConfigFile.read_from_file_path(dir, config_file, config_name)
+    else
+      ConfigFile.read_or_default(dir, config_name)
+    end
+  end
+
   defp add_config_file_to_exec(exec, %ConfigFile{} = config_file) do
     %Execution{
       exec
diff --git a/lib/credo/config_file.ex b/lib/credo/config_file.ex
index 8c5b7c89..72b70b3e 100644
--- a/lib/credo/config_file.ex
+++ b/lib/credo/config_file.ex
@@ -30,6 +30,24 @@ defmodule Credo.ConfigFile do
   def read_or_default(dir, config_name \\ nil, safe \\ false) do
     dir
     |> relevant_config_files
+    |> combine_configs(dir, config_name, safe)
+  end
+
+  @doc """
+  Returns Execution struct representing a consolidated Execution for
+  the provided config_file merged into the default configuration.
+
+  - `config_file`: full path to the custom configuration file
+  - `config_name`: name of the configuration to load
+  - `safe`: if +true+, the config files are loaded using static analysis rather
+            than `Code.eval_string/1`
+  """
+  def read_from_file_path(dir, config_file, config_name \\ nil, safe \\ false) do
+    combine_configs([config_file], dir, config_name, safe)
+  end
+
+  defp combine_configs(files, dir, config_name, safe) do
+    files
     |> Enum.filter(&File.exists?/1)
     |> Enum.map(&File.read!/1)
     |> List.insert_at(0, @default_config_file)


Test patch:
diff --git a/test/credo/config_file_test.exs b/test/credo/config_file_test.exs
index 32d5b357..d198bb7e 100644
--- a/test/credo/config_file_test.exs
+++ b/test/credo/config_file_test.exs
@@ -168,4 +168,15 @@ defmodule Credo.ConfigFileTest do
 
     assert config_subdir_count > 1
   end
+
+  test "loads custom config file and merges with default" do
+    config_file = Path.join([File.cwd!(), "test", "fixtures", "custom-config.exs"])
+
+    configs = ConfigFile.read_from_file_path(".", config_file)
+    # from default
+    assert(Enum.member?(configs.checks, {Credo.Check.Readability.ModuleNames, []}))
+
+    # from custom file
+    assert(Enum.member?(configs.checks, {Credo.Check.Readability.ModuleDoc, false}))
+  end
 end
diff --git a/test/fixtures/custom-config.exs b/test/fixtures/custom-config.exs
new file mode 100644
index 00000000..de252482
--- /dev/null
+++ b/test/fixtures/custom-config.exs
@@ -0,0 +1,14 @@
+%{
+  configs: [
+    %{
+      name: "default",
+      files: %{
+        included: ["lib/", "src/", "web/", "apps/"],
+        excluded: []
+      },
+      checks: [
+        {Credo.Check.Readability.ModuleDoc, false}
+      ]
+    }
+  ]
+}