Repository: jeremyjh/dialyxir (elixir)

Issue:
Crashes if `mix.lock` does not exist
### Precheck

- [x] Take a look at the open issues and be sure that your issue is not already covered.
- [x] Be sure your versions of Dialyxir and Erlex are up to date.

### Environment

* Elixir & Erlang/OTP versions (elixir --version):

```
Erlang/OTP 24 [erts-12.3.2.6] [source] [64-bit] [smp:12:12] [ds:12:12:10] [async-threads:1] [jit]

Elixir 1.14.1 (compiled with Erlang/OTP 24)
```

* Which version of Dialyxir are you using? (cat mix.lock | grep dialyxir): 1.2.0

### Current behavior
I have `mix dialyzer` installed globally as an archive task. If I make a new project with `mix new`, and then run `mix dialyzer`, `mix crashes with 

```
$ mix dialyzer
Finding suitable PLTs
Checking PLT...
[:compiler, :elixir, :kernel, :logger, :stdlib]
** (File.Error) could not read file "mix.lock": no such file or directory
    (elixir 1.14.1) lib/file.ex:358: File.read!/1
    lib/mix/tasks/dialyzer.ex:385: Mix.Tasks.Dialyzer.dependency_hash/0
    lib/mix/tasks/dialyzer.ex:249: Mix.Tasks.Dialyzer.check_plt/1
    lib/mix/tasks/dialyzer.ex:174: Mix.Tasks.Dialyzer.run/1
    (mix 1.14.1) lib/mix/task.ex:421: anonymous fn/3 in Mix.Task.run_task/4
    (mix 1.14.1) lib/mix/cli.ex:84: Mix.CLI.run_task/2
```

It looks like if I create a `mix.lock` file containing `%{}`, then it runs as expected.

### Expected behavior
I'd expect it not to crash with an empty `mix.lock`. 


Pull request:
Fix crash when mix.lock was missing
Closes #472

See that issue for more details on the issue. A non-existent lockfile is probably ok to be non-fatal (especially considering by default `mix` projects do not include one), so I just chose to treat it the same as an empty file. 

Patch:
diff --git a/lib/mix/tasks/dialyzer.ex b/lib/mix/tasks/dialyzer.ex
index 9d240ab..9f02390 100644
--- a/lib/mix/tasks/dialyzer.ex
+++ b/lib/mix/tasks/dialyzer.ex
@@ -386,7 +386,23 @@ defmodule Mix.Tasks.Dialyzer do
     {apps, hash}
   end
 
-  def lock_file() do
-    Mix.Project.config()[:lockfile] |> File.read!()
+  defp lock_file() do
+    lockfile = Mix.Project.config()[:lockfile]
+    read_res = File.read(lockfile)
+
+    case read_res do
+      {:ok, data} ->
+        data
+
+      {:error, :enoent} ->
+        # If there is no lock file, an empty bitstring will do to indicate there is none there
+        <<>>
+
+      {:error, reason} ->
+        raise File.Error,
+          reason: reason,
+          action: "read file",
+          path: lockfile
+    end
   end
 end


Test patch:
diff --git a/test/fixtures/no_lockfile/mix.exs b/test/fixtures/no_lockfile/mix.exs
new file mode 100644
index 0000000..641a55a
--- /dev/null
+++ b/test/fixtures/no_lockfile/mix.exs
@@ -0,0 +1,10 @@
+defmodule NoLockfile.Mixfile do
+  use Mix.Project
+
+  def project do
+    [
+      app: :no_lockfile,
+      version: "1.0.0"
+    ]
+  end
+end
diff --git a/test/mix/tasks/dialyzer_test.exs b/test/mix/tasks/dialyzer_test.exs
index 772d653..460a712 100644
--- a/test/mix/tasks/dialyzer_test.exs
+++ b/test/mix/tasks/dialyzer_test.exs
@@ -42,6 +42,14 @@ defmodule Mix.Tasks.DialyzerTest do
     end)
   end
 
+  test "Does not crash when running on project without mix.lock" do
+    in_project(:no_lockfile, fn ->
+      fun = fn -> Mix.Tasks.Dialyzer.clean([], &no_delete_plt/4) end
+      capture_io(fun)
+      # does not assert anything, we just need to ensure this doesn't crash.
+    end)
+  end
+
   @tag :output_tests
   test "Informational output is suppressed with --quiet" do
     args = ["dialyzer", "--quiet"]