• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

exercism / elixir-analyzer / f3e6767372ca686eb9f450eee5f33070f3a2c7eb

30 Jun 2024 11:56PM UTC coverage: 98.184% (-0.3%) from 98.524%
f3e6767372ca686eb9f450eee5f33070f3a2c7eb

push

github

web-flow
 Update to Elixir 1.17 (#430)

* update submodule

* update to 1.17

* typo

* more typo

* update deps

* typo again

* fix credo complaints

1 of 1 new or added line in 1 file covered. (100.0%)

3 existing lines in 3 files now uncovered.

865 of 881 relevant lines covered (98.18%)

14520.91 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

97.22
/lib/elixir_analyzer/exercise_test/common_checks/function_capture.ex
1
defmodule ElixirAnalyzer.ExerciseTest.CommonChecks.FunctionCapture do
2
  @moduledoc """
3
  Check if anonymous functions are used where function capture can be used instead
4
  """
5

6
  alias ElixirAnalyzer.Constants
7
  alias ElixirAnalyzer.Comment
8

9
  @spec run(Macro.t()) :: [{:pass | :fail, Comment.t()}]
10
  def run(code_ast) do
11
    acc = %{capture_depth: 0, functions: []}
1,010✔
12

13
    {_, %{functions: functions}} =
1,010✔
14
      Macro.traverse(code_ast, acc, &annotate(&1, &2), fn ast, acc ->
15
        find_anonymous(ast, acc)
45,044✔
16
      end)
17

18
    case functions |> Enum.map(&format_function/1) |> Enum.reverse() do
1,010✔
19
      [] ->
989✔
20
        []
21

22
      [{wrong_function, correct_function} | _] ->
21✔
23
        [
24
          {:fail,
25
           %Comment{
26
             type: :informative,
27
             name: Constants.solution_use_function_capture(),
28
             comment: Constants.solution_use_function_capture(),
29
             params: %{
30
               expected: correct_function,
31
               actual: wrong_function
32
             }
33
           }}
34
        ]
35
    end
36
  end
37

38
  defp annotate({:&, _, [{_name, _, _args}]} = node, %{capture_depth: depth} = acc) do
87✔
39
    {node, %{acc | capture_depth: depth + 1}}
40
  end
41

42
  defp annotate(node, acc), do: {node, acc}
44,957✔
43

44
  @exceptions [:<<>>, :{}, :%{}]
45
  defp find_anonymous(
46
         {:&, _, [{name, _, args}]} = node,
47
         %{capture_depth: depth, functions: functions}
48
       ) do
49
    wrong_use? =
87✔
50
      args
51
      |> Enum.with_index(1)
52
      |> Enum.all?(&match?({{:&, _, [index]}, index}, &1))
120✔
53

54
    depth = depth - 1
87✔
55

56
    functions =
87✔
UNCOV
57
      if depth <= 0 and wrong_use? and actual_function?(name) and name not in @exceptions do
×
58
        [{:&, name, length(args)} | functions]
59
      else
60
        functions
78✔
61
      end
62

63
    {node, %{capture_depth: depth, functions: functions}}
64
  end
65

66
  # fn -> foo end
67
  defp find_anonymous(
2✔
68
         {:fn, _, [{:->, _, [[], {name, _, atom}]}]} = node,
69
         %{capture_depth: 0, functions: functions} = acc
70
       )
71
       when is_atom(atom) do
72
    {node, %{acc | functions: [{:fn, name, nil} | functions]}}
73
  end
74

75
  defp find_anonymous(
76
         {:fn, _, [{:->, _, [args, {name, _, args}]}]} = node,
77
         %{capture_depth: 0, functions: functions} = acc
78
       )
79
       when name not in @exceptions do
80
    args = Enum.map(args, fn {var, _, _} -> var end)
11✔
81

82
    if actual_function?(name) do
11✔
83
      {node, %{acc | functions: [{:fn, name, args} | functions]}}
84
    else
85
      {node, acc}
86
    end
87
  end
88

89
  defp find_anonymous(node, acc) do
44,944✔
90
    {node, acc}
91
  end
92

93
  defp actual_function?(name) when is_atom(name), do: true
13✔
94

95
  defp actual_function?({:., _, [{:__aliases__, _, _module_path}, name]}) when is_atom(name) do
8✔
96
    true
97
  end
98

99
  defp actual_function?({:., _, [module, name]}) when is_atom(module) and is_atom(name) do
4✔
100
    true
101
  end
102

103
  # motivation for this check: fn string -> unquote(parser).(string) end
104
  defp actual_function?(_), do: false
1✔
105

106
  defp format_function({:&, name, arity}) do
107
    name = format_function_name(name)
9✔
108
    correct = "&#{name}/#{arity}"
9✔
109
    args = Enum.map_join(1..arity, ", ", fn n -> "&#{n}" end)
9✔
110
    wrong = "&#{name}(#{args})"
9✔
111
    {wrong, correct}
112
  end
113

114
  defp format_function({:fn, name, nil}) do
115
    correct = "&#{name}/0"
2✔
116
    wrong = "fn -> #{name} end"
2✔
117
    {wrong, correct}
118
  end
119

120
  defp format_function({:fn, name, args}) do
121
    name = format_function_name(name)
10✔
122
    correct = "&#{name}/#{length(args)}"
10✔
123
    space = if Enum.empty?(args), do: "", else: " "
10✔
124
    args = Enum.join(args, ", ")
10✔
125
    wrong = "fn #{args}#{space}-> #{name}(#{args}) end"
10✔
126
    {wrong, correct}
127
  end
128

129
  defp format_function_name(name) when is_atom(name), do: name
7✔
130

131
  defp format_function_name({:., _, [{:__aliases__, _, module_path}, name]}) do
132
    "#{Enum.map_join(module_path, ".", &to_string/1)}.#{name}"
8✔
133
  end
134

135
  # Erlang functions
136
  defp format_function_name({:., _, [module, name]}) do
137
    ":#{module}.#{name}"
4✔
138
  end
139
end
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc