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

exercism / elixir-analyzer / 5626c6af278dabcb04cfe255b2f259942ba446f9

20 Jun 2026 07:43AM UTC coverage: 98.512% (-0.01%) from 98.524%
5626c6af278dabcb04cfe255b2f259942ba446f9

push

github

web-flow
Update to Elixir 1.20 (#466)

* update to 1.20

* update elixir

* update credo and fix warning

* update elixir submodule

927 of 941 relevant lines covered (98.51%)

15221.76 hits per line

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

98.39
/lib/elixir_analyzer/exercise_test/feature/compiler.ex
1
defmodule ElixirAnalyzer.ExerciseTest.Feature.Compiler do
2
  @moduledoc false
3

4
  alias ElixirAnalyzer.QuoteUtil
5
  alias ElixirAnalyzer.Comment
6
  alias ElixirAnalyzer.ExerciseTest.Feature.Compiler
7

8
  def compile({feature_data, feature_forms}, code_ast) do
9
    name = Keyword.fetch!(feature_data, :name)
66✔
10
    comment = Keyword.fetch!(feature_data, :comment)
66✔
11
    type = Keyword.get(feature_data, :type, :informative)
66✔
12
    find_type = Keyword.get(feature_data, :find, :all)
66✔
13
    find_at_depth = Keyword.get(feature_data, :depth, nil)
66✔
14
    suppress_if = Keyword.get(feature_data, :suppress_if, [])
66✔
15

16
    form_expr =
66✔
17
      feature_forms
18
      |> Enum.map(&compile_form(&1, find_at_depth, code_ast))
122✔
19
      |> Enum.reduce(:start, &combine_compiled_forms(find_type, &1, &2))
122✔
20
      |> handle_combined_compiled_forms(find_type)
21

22
    test_description =
66✔
23
      Macro.escape(%Comment{
24
        name: name,
25
        comment: comment,
26
        type: type,
27
        suppress_if: suppress_if
28
      })
29

30
    quote do
66✔
31
      if unquote(form_expr) do
32
        {:pass, unquote(test_description)}
33
      else
34
        {:fail, unquote(test_description)}
35
      end
36
    end
37
  end
38

39
  def compile_form(form, find_at_depth, code_ast) do
40
    find_ast = Keyword.fetch!(form, :find_ast)
122✔
41
    block_params = Keyword.fetch!(form, :block_params)
122✔
42

43
    find_ast = Macro.escape(find_ast)
122✔
44

45
    # create the walk function, determined if the form to find
46
    # is multiple first level entries in a code block
47
    walk_fn = get_compile_form_prewalk_fn(block_params, find_ast, find_at_depth)
122✔
48

49
    quote do
122✔
50
      (fn ast ->
51
         {_, result} = QuoteUtil.prewalk(ast, false, unquote(walk_fn))
52

53
         result
54
       end).(unquote(code_ast))
55
    end
56
  end
57

58
  defp get_compile_form_prewalk_fn(block_params, find_ast, find_at_depth)
59
       when is_integer(block_params) do
60
    quote do
1✔
61
      fn
62
        # If the node matches a block, then chunk the block contents
63
        # to the size of the form block, then pattern match on each chunk
64
        # return true if a match
65
        {:__block__, _, params} = node, false, depth ->
66
          finding_depth = unquote(find_at_depth) in [nil, depth]
67

68
          if finding_depth and is_list(params) do
69
            found =
70
              params
71
              |> Enum.chunk_every(unquote(block_params), 1, :discard)
72
              |> Enum.any?(&Compiler.form_match?(unquote(find_ast), &1))
73

74
            {node, found}
75
          else
76
            {node, false}
77
          end
78

79
        # If not a block, then we know it can't match, so pass
80
        # along the accumulator
81
        node, val, _depth ->
82
          {node, val}
83
      end
84
    end
85
  end
86

87
  defp get_compile_form_prewalk_fn(false, find_ast, find_at_depth) do
88
    quote do
121✔
89
      fn
90
        node, false, depth ->
91
          finding_depth = unquote(find_at_depth) in [nil, depth]
92

93
          if finding_depth do
94
            {node, Compiler.form_match?(unquote(find_ast), node)}
95
          else
96
            {node, false}
97
          end
98

99
        node, true, _depth ->
100
          {node, true}
101
      end
102
    end
103
  end
104

105
  def combine_compiled_forms(:any, form, :start) do
106
    # start the disjunction with false
107
    combine_compiled_forms(:any, form, quote(do: false))
2✔
108
  end
109

110
  def combine_compiled_forms(:any, form, expr) do
111
    quote do: unquote(form) or unquote(expr)
4✔
112
  end
113

114
  def combine_compiled_forms(:all, form, :start) do
115
    # start the conjunction with true
116
    combine_compiled_forms(:all, form, quote(do: true))
10✔
117
  end
118

119
  def combine_compiled_forms(:all, form, expr) do
120
    quote do: unquote(form) and unquote(expr)
12✔
121
  end
122

123
  def combine_compiled_forms(type, form, :start) when type in [:one, :none] do
124
    combine_compiled_forms(type, form, quote(do: 0))
54✔
125
  end
126

127
  def combine_compiled_forms(type, form, expr) when type in [:one, :none] do
128
    quote do
106✔
129
      value =
130
        (fn form_val ->
131
           if form_val do
132
             1
133
           else
134
             0
135
           end
136
         end).(unquote(form))
137

138
      value + unquote(expr)
139
    end
140
  end
141

142
  def handle_combined_compiled_forms(combined_expr, find_type) do
143
    case find_type do
66✔
144
      type when type in [:all, :any] -> combined_expr
12✔
145
      type when type in [:none] -> quote do: unquote(combined_expr) == 0
54✔
146
      type when type in [:one] -> quote do: unquote(combined_expr) == 1
×
147
    end
148
  end
149

150
  def form_match?(item, item) do
13,101✔
151
    true
152
  end
153

154
  def form_match?({:_ignore, _, _}, _) do
3,636✔
155
    true
156
  end
157

158
  def form_match?({:_shallow_ignore, _, form_params}, {_, _, params}) do
159
    form_match?(form_params, params)
84✔
160
  end
161

162
  def form_match?([:_ignore], meta) when is_list(meta) do
7,981✔
163
    true
164
  end
165

166
  def form_match?(
167
        {:_block_includes, _, [[do: {:__block__, _, form_params}]]},
168
        {:__block__, _, params}
169
      ) do
170
    all_forms_are_found?(form_params, params)
95✔
171
  end
172

173
  def form_match?({:_block_includes, _, [[do: form_params]]}, params)
174
      when is_list(form_params) do
175
    all_forms_are_found?(form_params, params)
1,008✔
176
  end
177

178
  def form_match?({:_block_includes, _, [[do: form_params]]}, params) do
179
    case params do
4,129✔
180
      {:__block__, _, params} ->
181
        Enum.any?(params, fn param -> form_match?(form_params, param) end)
214✔
182

183
      _ ->
184
        form_match?(form_params, params)
3,915✔
185
    end
186
  end
187

188
  def form_match?(
189
        {:_block_ends_with, _, [[do: {:__block__, _, form_params}]]},
190
        {:__block__, _, params}
191
      ) do
192
    form_match?(List.last(form_params), List.last(params)) and
106✔
193
      all_forms_are_found?(form_params, params)
22✔
194
  end
195

196
  def form_match?({:_block_ends_with, _, [[do: form_params]]}, params)
197
      when is_list(form_params) do
198
    form_match?(List.last(form_params), List.last(List.wrap(params))) and
1,301✔
199
      all_forms_are_found?(form_params, params)
10✔
200
  end
201

202
  def form_match?({:_block_ends_with, _, [[do: form_params]]}, params) do
203
    case params do
7,660✔
204
      {:__block__, _, block_params} ->
205
        form_match?(form_params, List.last(block_params))
788✔
206

207
      _ ->
208
        form_match?(form_params, List.last(List.wrap(params)))
6,872✔
209
    end
210
  end
211

212
  # functions called with or without parentheses should match
213
  def form_match?({function, form_params, []}, {function, params, atom}) when is_atom(atom) do
214
    form_match?(form_params, params)
13✔
215
  end
216

217
  def form_match?({function, form_params, atom}, {function, params, []}) when is_atom(atom) do
218
    form_match?(form_params, params)
8✔
219
  end
220

221
  # Pipes are a special case, when pipes are in the form, they must be in the code
222
  def form_match?({:|>, form_meta, form_params}, {:|>, meta, params}) do
223
    form_match?(form_meta, meta) and form_match?(form_params, params)
102✔
224
  end
225

226
  # When pipes are not in the form but in the code, we un-pipe the code
227
  def form_match?(form_params, {:|>, _, [params, {function, function_meta, function_params}]}) do
228
    if is_atom(function_params) do
4,018✔
229
      form_match?(form_params, {function, function_meta, [params]})
243✔
230
    else
231
      form_match?(form_params, {function, function_meta, [params | function_params]})
3,775✔
232
    end
233
  end
234

235
  def form_match?(form_params, params) when is_list(form_params) and is_list(params) do
236
    length(form_params) == length(params) and
10,516✔
237
      Enum.zip_with(form_params, params, &form_match?/2)
238
      |> Enum.all?()
10,009✔
239
  end
240

241
  def form_match?({form_name, form_meta, form_params}, {name, meta, params}) do
242
    form_match?(form_name, name) and
235,893✔
243
      form_match?(form_meta, meta) and
243,751✔
244
      form_match?(form_params, params)
7,858✔
245
  end
246

247
  def form_match?({form_a, form_b}, {a, b}) do
248
    form_match?(form_a, a) and form_match?(form_b, b)
3,620✔
249
  end
250

251
  def form_match?(_, _) do
431,178✔
252
    false
253
  end
254

255
  defp all_forms_are_found?(form_params, params) when is_list(form_params) and is_list(params) do
256
    Enum.reduce_while(params, form_params, fn
257
      _, [] ->
14✔
258
        {:halt, []}
259

260
      line, [form_head | form_tail] = form ->
436✔
261
        {:cont, if(form_match?(form_head, line), do: form_tail, else: form)}
436✔
262
    end)
263
    |> Enum.empty?()
267✔
264
  end
265

266
  defp all_forms_are_found?(form_params, _params) when is_list(form_params) do
868✔
267
    false
268
  end
269
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