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

nshkrdotcom / ElixirScope / c7479091597c3382f92c87a4fd0395db3ee30193

30 May 2025 03:48PM UTC coverage: 57.42% (-0.3%) from 57.672%
c7479091597c3382f92c87a4fd0395db3ee30193

push

github

NSHkr
refactor expression_processors

98 of 221 new or added lines in 6 files covered. (44.34%)

28 existing lines in 2 files now uncovered.

6210 of 10815 relevant lines covered (57.42%)

3137.51 hits per line

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

55.0
/lib/elixir_scope/ast_repository/enhanced/cfg_generator/expression_processors/basic_processors.ex
1
defmodule ElixirScope.ASTRepository.Enhanced.CFGGenerator.ExpressionProcessors.BasicProcessors do
2
  @moduledoc """
3
  Processors for basic expressions like variables, literals, operations, and statement sequences.
4
  """
5

6
  alias ElixirScope.ASTRepository.Enhanced.{CFGNode, CFGEdge}
7

8
  # Get dependencies from application config for testability
9
  defp state_manager do
10
    Application.get_env(:elixir_scope, :state_manager,
1,197✔
11
      ElixirScope.ASTRepository.Enhanced.CFGGenerator.StateManager)
12
  end
13

14
  defp ast_utilities do
15
    Application.get_env(:elixir_scope, :ast_utilities,
1,162✔
16
      ElixirScope.ASTRepository.Enhanced.CFGGenerator.ASTUtilities)
17
  end
18

19
  defp ast_processor do
20
    Application.get_env(:elixir_scope, :ast_processor,
300✔
21
      ElixirScope.ASTRepository.Enhanced.CFGGenerator.ASTProcessor)
22
  end
23

24
  @doc """
25
  Processes a statement sequence (block).
26
  """
27
  def process_statement_sequence(statements, _meta, state) do
28
    # Process statements sequentially, connecting them in order
29
    {all_nodes, all_edges, final_exits, all_scopes, final_state} =
100✔
30
      Enum.reduce(statements, {%{}, [], [], %{}, state}, fn stmt, {nodes, edges, prev_exits, scopes, acc_state} ->
31
        {stmt_nodes, stmt_edges, stmt_exits, stmt_scopes, new_state} =
300✔
32
          ast_processor().process_ast_node(stmt, acc_state)
33

34
        # Connect previous statement exits to current statement entries
35
        connection_edges = if prev_exits == [] do
300✔
36
          # For the first statement, we'll connect it later in process_function_body
37
          []
38
        else
39
          stmt_entry_nodes = get_entry_nodes(stmt_nodes)
201✔
40
          if stmt_entry_nodes == [] do
201✔
41
            # If no entry nodes, create a direct connection from prev exits to stmt exits
42
            []
43
          else
44
            Enum.flat_map(prev_exits, fn prev_exit ->
201✔
45
              Enum.map(stmt_entry_nodes, fn stmt_entry ->
202✔
46
                %CFGEdge{
670✔
47
                  from_node_id: prev_exit,
48
                  to_node_id: stmt_entry,
49
                  type: :sequential,
50
                  condition: nil,
51
                  probability: 1.0,
52
                  metadata: %{}
53
                }
54
              end)
55
            end)
56
          end
57
        end
58

59
        merged_nodes = Map.merge(nodes, stmt_nodes)
300✔
60
        merged_edges = edges ++ stmt_edges ++ connection_edges
300✔
61
        merged_scopes = Map.merge(scopes, stmt_scopes)
300✔
62

63
        # Use stmt_exits as the new prev_exits for the next iteration
64
        new_prev_exits = if stmt_exits == [], do: prev_exits, else: stmt_exits
300✔
65

66
        {merged_nodes, merged_edges, new_prev_exits, merged_scopes, new_state}
300✔
67
      end)
68

69
    {all_nodes, all_edges, final_exits, all_scopes, final_state}
100✔
70
  end
71

72
  @doc """
73
  Processes a binary operation.
74
  """
75
  def process_binary_operation(op, left, right, meta, state) do
NEW
76
    {op_id, updated_state} = state_manager().generate_node_id("binary_op", state)
×
77

78
    # Process left operand first
NEW
79
    {left_nodes, left_edges, left_exits, left_scopes, left_state} =
×
80
      ast_processor().process_ast_node(left, updated_state)
81

82
    # Process right operand
NEW
83
    {right_nodes, right_edges, right_exits, right_scopes, right_state} =
×
84
      ast_processor().process_ast_node(right, left_state)
85

86
    # Create binary operation node
NEW
87
    op_node = %CFGNode{
×
88
      id: op_id,
89
      type: :binary_operation,
90
      ast_node_id: ast_utilities().get_ast_node_id(meta),
91
      line: ast_utilities().get_line_number(meta),
NEW
92
      scope_id: state.current_scope,
×
93
      expression: {op, meta, [left, right]},
94
      predecessors: left_exits ++ right_exits,
95
      successors: [],
96
      metadata: %{operator: op, left: left, right: right}
97
    }
98

99
    # Create edges from operands to operation
NEW
100
    operand_edges = (Enum.map(left_exits, fn exit_id ->
×
NEW
101
      %CFGEdge{
×
102
        from_node_id: exit_id,
103
        to_node_id: op_id,
104
        type: :sequential,
105
        condition: nil,
106
        probability: 1.0,
107
        metadata: %{operand: :left}
108
      }
109
    end) ++ Enum.map(right_exits, fn exit_id ->
NEW
110
      %CFGEdge{
×
111
        from_node_id: exit_id,
112
        to_node_id: op_id,
113
        type: :sequential,
114
        condition: nil,
115
        probability: 1.0,
116
        metadata: %{operand: :right}
117
      }
118
    end))
119

NEW
120
    all_nodes = left_nodes
×
121
    |> Map.merge(right_nodes)
122
    |> Map.put(op_id, op_node)
123

NEW
124
    all_edges = left_edges ++ right_edges ++ operand_edges
×
NEW
125
    all_scopes = Map.merge(left_scopes, right_scopes)
×
126

NEW
127
    {all_nodes, all_edges, [op_id], all_scopes, right_state}
×
128
  end
129

130
  @doc """
131
  Processes a unary operation.
132
  """
133
  def process_unary_operation(op, operand, meta, state) do
NEW
134
    {op_id, updated_state} = state_manager().generate_node_id("unary_op", state)
×
135

NEW
136
    op_node = %CFGNode{
×
137
      id: op_id,
138
      type: :unary_operation,
139
      ast_node_id: ast_utilities().get_ast_node_id(meta),
140
      line: ast_utilities().get_line_number(meta),
NEW
141
      scope_id: state.current_scope,
×
142
      expression: {op, meta, [operand]},
143
      predecessors: [],
144
      successors: [],
145
      metadata: %{operator: op, operand: operand}
146
    }
147

NEW
148
    nodes = %{op_id => op_node}
×
NEW
149
    {nodes, [], [op_id], %{}, updated_state}
×
150
  end
151

152
  @doc """
153
  Processes a variable reference.
154
  """
155
  def process_variable_reference(var_name, meta, state) do
NEW
156
    {var_id, updated_state} = state_manager().generate_node_id("variable", state)
×
157

NEW
158
    var_node = %CFGNode{
×
159
      id: var_id,
160
      type: :variable,
161
      ast_node_id: ast_utilities().get_ast_node_id(meta),
162
      line: ast_utilities().get_line_number(meta),
NEW
163
      scope_id: state.current_scope,
×
164
      expression: {var_name, meta, nil},
165
      predecessors: [],
166
      successors: [],
167
      metadata: %{variable: var_name}
168
    }
169

NEW
170
    nodes = %{var_id => var_node}
×
NEW
171
    {nodes, [], [var_id], %{}, updated_state}
×
172
  end
173

174
  @doc """
175
  Processes a literal value.
176
  """
177
  def process_literal_value(literal, state) do
178
    {literal_id, updated_state} = state_manager().generate_node_id("literal", state)
1,162✔
179

180
    literal_node = %CFGNode{
1,162✔
181
      id: literal_id,
182
      type: :literal,
183
      ast_node_id: nil,
184
      line: 1,
185
      scope_id: state.current_scope,
1,162✔
186
      expression: literal,
187
      predecessors: [],
188
      successors: [],
189
      metadata: %{value: literal, type: ast_utilities().get_literal_type(literal)}
190
    }
191

192
    nodes = %{literal_id => literal_node}
1,162✔
193
    {nodes, [], [literal_id], %{}, updated_state}
1,162✔
194
  end
195

196
  @doc """
197
  Processes a simple expression (fallback).
198
  """
199
  def process_simple_expression(ast, state) do
200
    {expr_id, updated_state} = state_manager().generate_node_id("expression", state)
35✔
201

202
    # Determine the expression type based on the AST structure
203
    expr_type = case ast do
35✔
NEW
204
      {:=, _, _} -> :assignment  # Assignment that didn't match the specific pattern
×
NEW
205
      {op, _, _} when op in [:+, :-, :*, :/, :==, :!=, :<, :>, :<=, :>=] -> :binary_operation
×
NEW
206
      {var, _, nil} when is_atom(var) -> :variable_reference
×
207
      _ -> :expression
35✔
208
    end
209

210
    expr_node = %CFGNode{
35✔
211
      id: expr_id,
212
      type: expr_type,
213
      ast_node_id: nil,
214
      line: 1,
215
      scope_id: state.current_scope,
35✔
216
      expression: ast,
217
      predecessors: [],
218
      successors: [],
219
      metadata: %{expression: ast, fallback: true}
220
    }
221

222
    nodes = %{expr_id => expr_node}
35✔
223
    {nodes, [], [expr_id], %{}, updated_state}
35✔
224
  end
225

226
  # Private helper functions
227

NEW
228
  defp get_entry_nodes(nodes) when map_size(nodes) == 0, do: []
×
229
  defp get_entry_nodes(nodes) do
230
    nodes
231
    |> Map.values()
232
    |> Enum.filter(fn node -> length(node.predecessors) == 0 end)
671✔
233
    |> Enum.map(& &1.id)
669✔
234
    |> case do
201✔
NEW
235
      [] -> [nodes |> Map.keys() |> List.first()]
×
236
      entry_nodes -> entry_nodes
201✔
237
    end
238
  end
239
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