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

nshkrdotcom / ElixirScope / e3c85b2dec2a55971a334d0061997410177723b4

28 May 2025 09:47AM UTC coverage: 59.568% (-4.1%) from 63.697%
e3c85b2dec2a55971a334d0061997410177723b4

push

github

NSHkr
Add new AST enhanced features with tests. 892 tests, 0 failures, 76 excluded

1752 of 3271 new or added lines in 17 files covered. (53.56%)

3 existing lines in 1 file now uncovered.

4797 of 8053 relevant lines covered (59.57%)

3798.33 hits per line

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

0.0
/lib/elixir_scope/ast_repository/enhanced_module_data.ex
1
defmodule ElixirScope.ASTRepository.EnhancedModuleData do
2
  @moduledoc """
3
  Comprehensive module representation with full AST and analysis metadata.
4
  
5
  This structure extends the basic ModuleData to support advanced AST analysis,
6
  comprehensive dependency tracking, and integration with CFG/DFG/CPG analysis.
7
  
8
  ## Features
9
  - Complete AST storage with size and depth metrics
10
  - Enhanced dependency tracking (imports, aliases, requires, uses)
11
  - OTP pattern detection and callback analysis
12
  - Comprehensive complexity metrics and code quality analysis
13
  - Security risk assessment
14
  - Efficient serialization for ETS storage
15
  
16
  ## Usage
17
  
18
      iex> module_data = EnhancedModuleData.new(MyModule, ast, source_file: "lib/my_module.ex")
19
      iex> EnhancedModuleData.validate(module_data)
20
      :ok
21
  """
22
  
23
  alias ElixirScope.ASTRepository.Enhanced.{
24
    EnhancedFunctionData,
25
    ComplexityMetrics
26
  }
27
  alias ElixirScope.ASTRepository.Enhanced.SupportingStructures.{
28
    MacroData,
29
    TypespecData,
30
    ModuleDependency,
31
    BehaviourUsage,
32
    CallbackData,
33
    ChildSpecData,
34
    CodeSmell,
35
    SecurityRisk
36
  }
37
  
38
  @type t :: %__MODULE__{
39
    # Identity
40
    module_name: atom(),
41
    file_path: String.t(),
42
    file_hash: String.t(),  # SHA256 for change detection
43
    
44
    # AST Data
45
    ast: Macro.t(),  # Complete module AST
46
    ast_size: non_neg_integer(),  # Number of AST nodes
47
    ast_depth: non_neg_integer(),  # Maximum AST depth
48
    
49
    # Module Components
50
    functions: [EnhancedFunctionData.t()],
51
    macros: [MacroData.t()],
52
    module_attributes: %{atom() => term()},
53
    typespecs: [TypespecData.t()],
54
    
55
    # Dependencies & Relationships
56
    imports: [ModuleDependency.t()],
57
    aliases: [ModuleDependency.t()],
58
    requires: [ModuleDependency.t()],
59
    uses: [BehaviourUsage.t()],
60
    
61
    # OTP Patterns
62
    behaviours: [atom()],
63
    callbacks_implemented: [CallbackData.t()],
64
    child_specs: [ChildSpecData.t()],
65
    
66
    # Analysis Metadata
67
    complexity_metrics: ComplexityMetrics.t(),
68
    code_smells: [CodeSmell.t()],
69
    security_risks: [SecurityRisk.t()],
70
    
71
    # Timestamps
72
    last_modified: DateTime.t(),
73
    last_analyzed: DateTime.t(),
74
    
75
    # Extensible Metadata
76
    metadata: map()
77
  }
78
  
79
  defstruct [
80
    # Identity
81
    :module_name,
82
    :file_path,
83
    :file_hash,
84
    
85
    # AST Data
86
    :ast,
87
    :ast_size,
88
    :ast_depth,
89
    
90
    # Module Components
91
    :functions,
92
    :macros,
93
    :module_attributes,
94
    :typespecs,
95
    
96
    # Dependencies & Relationships
97
    :imports,
98
    :aliases,
99
    :requires,
100
    :uses,
101
    
102
    # OTP Patterns
103
    :behaviours,
104
    :callbacks_implemented,
105
    :child_specs,
106
    
107
    # Analysis Metadata
108
    :complexity_metrics,
109
    :code_smells,
110
    :security_risks,
111
    
112
    # Timestamps
113
    :last_modified,
114
    :last_analyzed,
115
    
116
    # Extensible Metadata
117
    :metadata
118
  ]
119
  
120
  @doc """
121
  Creates a new EnhancedModuleData structure from parsed AST.
122
  
123
  ## Parameters
124
  - `module_name` - The module name (atom)
125
  - `ast` - The parsed AST
126
  - `opts` - Optional parameters including file_path, functions, etc.
127
  
128
  ## Examples
129
  
130
      iex> ast = quote do: defmodule MyModule, do: def hello, do: :world
131
      iex> data = EnhancedModuleData.new(MyModule, ast, file_path: "lib/my_module.ex")
132
      iex> data.module_name
133
      MyModule
134
  """
135
  @spec new(atom(), Macro.t(), keyword()) :: t()
136
  def new(module_name, ast, opts \\ []) do
NEW
137
    now = DateTime.utc_now()
×
NEW
138
    file_path = Keyword.get(opts, :file_path, "")
×
139
    
NEW
140
    %__MODULE__{
×
141
      # Identity
142
      module_name: module_name,
143
      file_path: file_path,
144
      file_hash: generate_file_hash(ast, file_path),
145
      
146
      # AST Data
147
      ast: ast,
148
      ast_size: calculate_ast_size(ast),
149
      ast_depth: calculate_ast_depth(ast),
150
      
151
      # Module Components
152
      functions: Keyword.get(opts, :functions, []),
153
      macros: extract_macros(ast),
154
      module_attributes: extract_module_attributes(ast),
155
      typespecs: extract_typespecs(ast),
156
      
157
      # Dependencies & Relationships
158
      imports: extract_imports(ast),
159
      aliases: extract_aliases(ast),
160
      requires: extract_requires(ast),
161
      uses: extract_uses(ast),
162
      
163
      # OTP Patterns
164
      behaviours: extract_behaviours(ast),
165
      callbacks_implemented: extract_callbacks_implemented(ast),
166
      child_specs: extract_child_specs(ast),
167
      
168
      # Analysis Metadata
169
      complexity_metrics: calculate_complexity_metrics(ast),
170
      code_smells: detect_code_smells(ast),
171
      security_risks: assess_security_risks(ast),
172
      
173
      # Timestamps
174
      last_modified: now,
175
      last_analyzed: now,
176
      
177
      # Extensible Metadata
178
      metadata: Keyword.get(opts, :metadata, %{})
179
    }
180
  end
181
  
182
  @doc """
183
  Validates the structure and data integrity of an EnhancedModuleData.
184
  
185
  ## Returns
186
  - `:ok` if valid
187
  - `{:error, reason}` if invalid
188
  """
189
  @spec validate(t()) :: :ok | {:error, term()}
190
  def validate(%__MODULE__{} = data) do
NEW
191
    with :ok <- validate_required_fields(data),
×
NEW
192
         :ok <- validate_ast_consistency(data),
×
NEW
193
         :ok <- validate_dependencies(data),
×
NEW
194
         :ok <- validate_timestamps(data) do
×
195
      :ok
196
    end
197
  end
198
  
199
  @doc """
200
  Converts the structure to a format suitable for ETS storage.
201
  
202
  This function optimizes the data for storage by:
203
  - Compressing large AST structures
204
  - Converting datetime to timestamps
205
  - Optimizing nested structures
206
  """
207
  @spec to_ets_format(t()) :: {atom(), binary()}
208
  def to_ets_format(%__MODULE__{} = data) do
NEW
209
    compressed_data = %{
×
NEW
210
      module_name: data.module_name,
×
NEW
211
      file_path: data.file_path,
×
NEW
212
      file_hash: data.file_hash,
×
NEW
213
      ast_compressed: compress_ast(data.ast),
×
NEW
214
      ast_size: data.ast_size,
×
NEW
215
      ast_depth: data.ast_depth,
×
NEW
216
      functions_count: length(data.functions),
×
NEW
217
      macros_count: length(data.macros),
×
NEW
218
      complexity_score: get_complexity_score(data.complexity_metrics),
×
NEW
219
      last_modified_ts: DateTime.to_unix(data.last_modified),
×
NEW
220
      last_analyzed_ts: DateTime.to_unix(data.last_analyzed),
×
NEW
221
      metadata: data.metadata
×
222
    }
223
    
NEW
224
    {data.module_name, :erlang.term_to_binary(compressed_data, [:compressed])}
×
225
  end
226
  
227
  @doc """
228
  Converts from ETS storage format back to full structure.
229
  """
230
  @spec from_ets_format({atom(), binary()}) :: t()
231
  def from_ets_format({module_name, binary_data}) do
NEW
232
    compressed_data = :erlang.binary_to_term(binary_data)
×
233
    
NEW
234
    %__MODULE__{
×
235
      module_name: module_name,
NEW
236
      file_path: compressed_data.file_path,
×
NEW
237
      file_hash: compressed_data.file_hash,
×
NEW
238
      ast: decompress_ast(compressed_data.ast_compressed),
×
NEW
239
      ast_size: compressed_data.ast_size,
×
NEW
240
      ast_depth: compressed_data.ast_depth,
×
241
      functions: [],  # Loaded separately for performance
242
      macros: [],
243
      module_attributes: %{},
244
      typespecs: [],
245
      imports: [],
246
      aliases: [],
247
      requires: [],
248
      uses: [],
249
      behaviours: [],
250
      callbacks_implemented: [],
251
      child_specs: [],
NEW
252
      complexity_metrics: %ComplexityMetrics{score: compressed_data.complexity_score},
×
253
      code_smells: [],
254
      security_risks: [],
NEW
255
      last_modified: DateTime.from_unix!(compressed_data.last_modified_ts),
×
NEW
256
      last_analyzed: DateTime.from_unix!(compressed_data.last_analyzed_ts),
×
NEW
257
      metadata: compressed_data.metadata
×
258
    }
259
  end
260
  
261
  @doc """
262
  Updates the module with new function data.
263
  """
264
  @spec add_function(t(), EnhancedFunctionData.t()) :: t()
265
  def add_function(%__MODULE__{} = data, function_data) do
NEW
266
    %{data | 
×
NEW
267
      functions: [function_data | data.functions],
×
268
      last_analyzed: DateTime.utc_now()
269
    }
270
  end
271
  
272
  @doc """
273
  Gets all function names and arities for this module.
274
  """
275
  @spec get_function_signatures(t()) :: [{atom(), non_neg_integer()}]
276
  def get_function_signatures(%__MODULE__{} = data) do
NEW
277
    Enum.map(data.functions, fn func -> {func.function_name, func.arity} end)
×
278
  end
279
  
280
  @doc """
281
  Checks if the module has been modified since last analysis.
282
  """
283
  @spec needs_reanalysis?(t(), String.t()) :: boolean()
284
  def needs_reanalysis?(%__MODULE__{} = data, current_file_hash) do
NEW
285
    data.file_hash != current_file_hash
×
286
  end
287
  
288
  @doc """
289
  Gets the total complexity score for the module.
290
  """
291
  @spec get_total_complexity(t()) :: float()
292
  def get_total_complexity(%__MODULE__{} = data) do
NEW
293
    module_complexity = get_complexity_score(data.complexity_metrics)
×
NEW
294
    function_complexity = 
×
NEW
295
      data.functions
×
NEW
296
      |> Enum.map(& &1.complexity_score)
×
297
      |> Enum.sum()
298
    
NEW
299
    module_complexity + function_complexity
×
300
  end
301
  
302
  @doc """
303
  Migrates from basic ModuleData to EnhancedModuleData.
304
  """
305
  @spec from_module_data(ElixirScope.ASTRepository.ModuleData.t()) :: t()
306
  def from_module_data(module_data) do
NEW
307
    now = DateTime.utc_now()
×
308
    
NEW
309
    %__MODULE__{
×
NEW
310
      module_name: module_data.module_name,
×
NEW
311
      file_path: module_data.source_file || "",
×
NEW
312
      file_hash: module_data.compilation_hash || "",
×
NEW
313
      ast: module_data.ast,
×
NEW
314
      ast_size: calculate_ast_size(module_data.ast),
×
NEW
315
      ast_depth: calculate_ast_depth(module_data.ast),
×
316
      functions: [],  # Will be populated separately
317
      macros: [],
318
      module_attributes: %{},
319
      typespecs: [],
320
      imports: [],
321
      aliases: [],
322
      requires: [],
323
      uses: [],
324
      behaviours: [],
325
      callbacks_implemented: [],
326
      child_specs: [],
NEW
327
      complexity_metrics: migrate_complexity_metrics(module_data.complexity_metrics),
×
328
      code_smells: [],
329
      security_risks: [],
NEW
330
      last_modified: DateTime.from_unix!(module_data.updated_at, :microsecond),
×
331
      last_analyzed: now,
332
      metadata: %{
333
        migrated_from: "ModuleData",
334
        migration_timestamp: DateTime.to_iso8601(now),
NEW
335
        original_version: module_data.version
×
336
      }
337
    }
338
  end
339
  
340
  # Private helper functions
341
  
342
  defp generate_file_hash(ast, file_path) do
NEW
343
    content = "#{inspect(ast)}#{file_path}"
×
NEW
344
    :crypto.hash(:sha256, content) |> Base.encode16(case: :lower)
×
345
  end
346
  
347
  defp calculate_ast_size(ast) do
348
    ast
NEW
349
    |> Macro.prewalk(0, fn node, acc -> {node, acc + 1} end)
×
NEW
350
    |> elem(1)
×
351
  end
352
  
353
  defp calculate_ast_depth(ast) do
354
    ast
355
    |> Macro.prewalk(0, fn 
356
      {_, _, children}, depth when is_list(children) -> 
NEW
357
        max_child_depth = children
×
NEW
358
        |> Enum.map(fn child -> calculate_ast_depth(child) end)
×
NEW
359
        |> Enum.max(fn -> 0 end)
×
360
        {{}, depth + 1 + max_child_depth}
NEW
361
      _, depth -> 
×
362
        {{}, depth}
363
    end)
NEW
364
    |> elem(1)
×
365
  end
366
  
NEW
367
  defp extract_macros(_ast), do: []  # TODO: Implement macro extraction
×
NEW
368
  defp extract_module_attributes(_ast), do: %{}  # TODO: Implement attribute extraction
×
NEW
369
  defp extract_typespecs(_ast), do: []  # TODO: Implement typespec extraction
×
NEW
370
  defp extract_imports(_ast), do: []  # TODO: Implement import extraction
×
NEW
371
  defp extract_aliases(_ast), do: []  # TODO: Implement alias extraction
×
NEW
372
  defp extract_requires(_ast), do: []  # TODO: Implement require extraction
×
NEW
373
  defp extract_uses(_ast), do: []  # TODO: Implement use extraction
×
NEW
374
  defp extract_behaviours(_ast), do: []  # TODO: Implement behaviour extraction
×
NEW
375
  defp extract_callbacks_implemented(_ast), do: []  # TODO: Implement callback extraction
×
NEW
376
  defp extract_child_specs(_ast), do: []  # TODO: Implement child spec extraction
×
377
  
378
  defp calculate_complexity_metrics(_ast) do
NEW
379
    %ComplexityMetrics{
×
380
      score: 1.0,
381
      cyclomatic: 1,
382
      cognitive: 1,
383
      halstead: %{},
384
      maintainability_index: 100.0
385
    }
386
  end
387
  
NEW
388
  defp detect_code_smells(_ast), do: []  # TODO: Implement code smell detection
×
NEW
389
  defp assess_security_risks(_ast), do: []  # TODO: Implement security risk assessment
×
390
  
NEW
391
  defp validate_required_fields(%__MODULE__{module_name: nil}), do: {:error, :missing_module_name}
×
NEW
392
  defp validate_required_fields(%__MODULE__{ast: nil}), do: {:error, :missing_ast}
×
NEW
393
  defp validate_required_fields(_), do: :ok
×
394
  
NEW
395
  defp validate_ast_consistency(%__MODULE__{ast_size: size}) when size <= 0, do: {:error, :invalid_ast_size}
×
NEW
396
  defp validate_ast_consistency(%__MODULE__{ast_depth: depth}) when depth <= 0, do: {:error, :invalid_ast_depth}
×
NEW
397
  defp validate_ast_consistency(_), do: :ok
×
398
  
NEW
399
  defp validate_dependencies(_), do: :ok  # TODO: Implement dependency validation
×
400
  
NEW
401
  defp validate_timestamps(%__MODULE__{last_modified: nil}), do: {:error, :missing_last_modified}
×
NEW
402
  defp validate_timestamps(%__MODULE__{last_analyzed: nil}), do: {:error, :missing_last_analyzed}
×
NEW
403
  defp validate_timestamps(_), do: :ok
×
404
  
405
  defp compress_ast(ast) do
406
    ast
407
    |> :erlang.term_to_binary([:compressed])
NEW
408
    |> Base.encode64()
×
409
  end
410
  
411
  defp decompress_ast(compressed_string) do
412
    compressed_string
413
    |> Base.decode64!()
NEW
414
    |> :erlang.binary_to_term()
×
415
  end
416
  
NEW
417
  defp get_complexity_score(%ComplexityMetrics{score: score}), do: score
×
NEW
418
  defp get_complexity_score(_), do: 1.0
×
419
  
420
  defp migrate_complexity_metrics(nil) do
NEW
421
    %ComplexityMetrics{score: 1.0, cyclomatic: 1, cognitive: 1, halstead: %{}, maintainability_index: 100.0}
×
422
  end
423
  defp migrate_complexity_metrics(old_metrics) do
NEW
424
    %ComplexityMetrics{
×
425
      score: Map.get(old_metrics, :score, 1.0),
426
      cyclomatic: Map.get(old_metrics, :cyclomatic, 1),
427
      cognitive: Map.get(old_metrics, :cognitive, 1),
428
      halstead: Map.get(old_metrics, :halstead, %{}),
429
      maintainability_index: Map.get(old_metrics, :maintainability_index, 100.0)
430
    }
431
  end
432
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