Skip to content

Commit 63ec628

Browse files
authored
Merge pull request #77 from esl/allow-multiple-specs
Allow multiple specs above a single function clause
2 parents 3bfa4d0 + 1bd3066 commit 63ec628

File tree

7 files changed

+29
-16
lines changed

7 files changed

+29
-16
lines changed

lib/gradient/elixir_checker.ex

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,15 +59,16 @@ defmodule Gradient.ElixirChecker do
5959
# Spec name doesn't match the function name
6060
{fun, [{:spec_error, :wrong_spec_name, anno, n, a} | errors]}
6161

62-
{:spec, {n, a}, anno} = s1, {{:spec, _, _}, errors} ->
63-
# Only one spec per function clause is allowed
64-
{s1, [{:spec_error, :spec_after_spec, anno, n, a} | errors]}
62+
{:spec, {n, a}, anno} = s1, {{:spec, {n2, a2}, _}, errors} when n != n2 or a != a2 ->
63+
# Specs with diffrent name/arity are mixed
64+
{s1, [{:spec_error, :mixed_specs, anno, n, a} | errors]}
6565

6666
x, {_, errors} ->
6767
{x, errors}
6868
end)
6969
|> elem(1)
7070
|> Enum.map(&{file, &1})
71+
|> Enum.reverse()
7172
end
7273

7374
# Filter out __info__ and other generated functions with the same name pattern

lib/gradient/elixir_fmt.ex

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,9 +119,9 @@ defmodule Gradient.ElixirFmt do
119119
)
120120
end
121121

122-
def format_type_error({:spec_error, :spec_after_spec, anno, name, arity}, opts) do
122+
def format_type_error({:spec_error, :mixed_specs, anno, name, arity}, opts) do
123123
:io_lib.format(
124-
"~sThe spec ~p/~p~s follows another spec, but only one spec per function clause is allowed~n",
124+
"~sThe spec ~p/~p~s follows a spec with different name/arity~n",
125125
[
126126
format_location(anno, :brief, opts),
127127
name,

test/examples/spec_correct.ex

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,13 @@ defmodule CorrectSpec do
33
def convert(int) when is_integer(int), do: int / 1
44
@spec convert(atom()) :: binary()
55
def convert(atom) when is_atom(atom), do: to_string(atom)
6+
7+
@spec encode(integer()) :: float()
8+
@spec encode(atom()) :: binary()
9+
def encode(val) do
10+
case val do
11+
_ when is_integer(val) -> val / 1
12+
_ when is_atom(val) -> to_string(val)
13+
end
14+
end
615
end
Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1-
defmodule SpecAfterSpec do
1+
defmodule SpecMixed do
22
@spec convert(integer()) :: float()
3-
@spec convert(atom()) :: binary()
3+
@spec encode(atom()) :: binary()
44
def convert(int) when is_integer(int), do: int / 1
55
def convert(atom) when is_atom(atom), do: to_string(atom)
6+
7+
def encode(atom) when is_atom(atom), do: to_string(atom)
68
end

test/gradient/elixir_checker_test.exs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,16 +23,17 @@ defmodule Gradient.ElixirCheckerTest do
2323
ast = load("Elixir.SpecWrongName.beam")
2424

2525
assert [
26-
{_, {:spec_error, :wrong_spec_name, 11, :last_two, 1}},
27-
{_, {:spec_error, :wrong_spec_name, 5, :convert, 1}}
26+
{_, {:spec_error, :wrong_spec_name, 5, :convert, 1}},
27+
{_, {:spec_error, :wrong_spec_name, 11, :last_two, 1}}
2828
] = ElixirChecker.check(ast, [])
2929
end
3030

31-
test "more than one spec per function clause is not allowed" do
32-
ast = load("Elixir.SpecAfterSpec.beam")
31+
test "mixing specs names is not allowed" do
32+
ast = load("Elixir.SpecMixed.beam")
3333

3434
assert [
35-
{_, {:spec_error, :spec_after_spec, 3, :convert, 1}}
35+
{_, {:spec_error, :mixed_specs, 3, :encode, 1}},
36+
{_, {:spec_error, :wrong_spec_name, 3, :encode, 1}}
3637
] = ElixirChecker.check(ast, [])
3738
end
3839
end

test/gradient/elixir_fmt_test.exs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -251,11 +251,11 @@ defmodule Gradient.ElixirFmtTest do
251251

252252
test "follows another spec" do
253253
msg =
254-
{:spec_error, :spec_after_spec, 3, :convert, 1}
254+
{:spec_error, :mixed_specs, 3, :encode, 1}
255255
|> ElixirFmt.format_error([])
256256
|> :erlang.iolist_to_binary()
257257

258-
assert "The spec convert/1 on line 3 follows another spec, but only one spec per function clause is allowed\n" =
258+
assert "The spec encode/1 on line 3 follows a spec with different name/arity\n" =
259259
msg
260260
end
261261
end

test/mix/tasks/gradient_test.exs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,8 +93,8 @@ defmodule Mix.Tasks.GradientTest do
9393
end
9494

9595
test "--no-ex-check option" do
96-
beam = Path.join(@build_path, "Elixir.SpecAfterSpec.beam")
97-
ex_spec_error_msg = "The spec convert/1 on line"
96+
beam = Path.join(@build_path, "Elixir.SpecMixed.beam")
97+
ex_spec_error_msg = "The spec encode/1 on line"
9898

9999
output = run_task(test_opts([beam]))
100100
assert String.contains?(output, ex_spec_error_msg)

0 commit comments

Comments
 (0)