Skip to content

Commit f6aca62

Browse files
NSHkrNSHkr
authored andcommitted
refactor project_populator
1 parent 112b416 commit f6aca62

13 files changed

Lines changed: 1004 additions & 772 deletions

File tree

lib/elixir_scope/ast_repository/enhanced/project_populator.ex

Lines changed: 41 additions & 664 deletions
Large diffs are not rendered by default.
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
defmodule ElixirScope.ASTRepository.Enhanced.ProjectPopulator.ASTExtractor do
2+
@moduledoc """
3+
Extracts information from AST structures.
4+
5+
Provides functionality to:
6+
- Extract module names from AST
7+
- Extract functions from modules
8+
- Extract dependencies, exports, and attributes
9+
"""
10+
11+
@doc """
12+
Extracts module name from AST.
13+
"""
14+
def extract_module_name(ast) do
15+
case ast do
16+
{:defmodule, _, [module_alias, _body]} ->
17+
case module_alias do
18+
{:__aliases__, _, parts} -> Module.concat(parts)
19+
atom when is_atom(atom) -> atom
20+
_ -> nil
21+
end
22+
_ -> nil
23+
end
24+
end
25+
26+
@doc """
27+
Extracts functions from module AST.
28+
"""
29+
def extract_functions_from_module(ast) do
30+
case ast do
31+
{:defmodule, _, [_module_name, [do: body]]} ->
32+
extract_functions_from_body(body, [])
33+
_ -> []
34+
end
35+
end
36+
37+
@doc """
38+
Extracts module dependencies from AST.
39+
"""
40+
def extract_module_dependencies(_ast) do
41+
# Extract alias, import, use, and require statements
42+
dependencies = []
43+
44+
# This would be a more sophisticated implementation
45+
# For now, return empty list
46+
dependencies
47+
end
48+
49+
@doc """
50+
Extracts module exports from AST.
51+
"""
52+
def extract_module_exports(_ast) do
53+
# Extract @spec and public function definitions
54+
exports = []
55+
56+
# This would be a more sophisticated implementation
57+
# For now, return empty list
58+
exports
59+
end
60+
61+
@doc """
62+
Extracts module attributes from AST.
63+
"""
64+
def extract_module_attributes(_ast) do
65+
# Extract module attributes like @moduledoc, @doc, @spec, etc.
66+
attributes = %{}
67+
68+
# This would be a more sophisticated implementation
69+
# For now, return empty map
70+
attributes
71+
end
72+
73+
# Private functions
74+
75+
defp extract_functions_from_body({:__block__, _, statements}, acc) do
76+
Enum.reduce(statements, acc, &extract_function_from_statement/2)
77+
end
78+
defp extract_functions_from_body(statement, acc) do
79+
extract_function_from_statement(statement, acc)
80+
end
81+
82+
defp extract_function_from_statement({:def, meta, [{:when, _, [{name, _, args}, _guard]}, body]}, acc) do
83+
arity = if is_list(args), do: length(args), else: 0
84+
[{name, arity, {:def, meta, [{name, [], args || []}, body]}} | acc]
85+
end
86+
defp extract_function_from_statement({:defp, meta, [{:when, _, [{name, _, args}, _guard]}, body]}, acc) do
87+
arity = if is_list(args), do: length(args), else: 0
88+
[{name, arity, {:defp, meta, [{name, [], args || []}, body]}} | acc]
89+
end
90+
defp extract_function_from_statement({:def, meta, [{name, _, args}, body]}, acc) do
91+
arity = if is_list(args), do: length(args), else: 0
92+
[{name, arity, {:def, meta, [{name, [], args || []}, body]}} | acc]
93+
end
94+
defp extract_function_from_statement({:defp, meta, [{name, _, args}, body]}, acc) do
95+
arity = if is_list(args), do: length(args), else: 0
96+
[{name, arity, {:defp, meta, [{name, [], args || []}, body]}} | acc]
97+
end
98+
defp extract_function_from_statement(_, acc), do: acc
99+
end
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
defmodule ElixirScope.ASTRepository.Enhanced.ProjectPopulator.ComplexityAnalyzer do
2+
@moduledoc """
3+
Analyzes complexity metrics for modules and functions.
4+
5+
Provides functionality to:
6+
- Calculate module complexity metrics
7+
- Calculate function complexity metrics
8+
- Analyze performance characteristics
9+
- Detect complexity patterns
10+
"""
11+
12+
@doc """
13+
Calculates complexity metrics for a module.
14+
"""
15+
def calculate_module_complexity_metrics(ast, functions) do
16+
function_complexities = functions
17+
|> Map.values()
18+
|> Enum.map(fn func -> func.complexity_metrics.combined_complexity || 1.0 end)
19+
20+
%{
21+
combined_complexity: Enum.sum(function_complexities),
22+
average_function_complexity: if(length(function_complexities) > 0, do: Enum.sum(function_complexities) / length(function_complexities), else: 1.0),
23+
max_function_complexity: Enum.max(function_complexities ++ [1.0]),
24+
function_count: map_size(functions),
25+
lines_of_code: count_ast_lines(ast)
26+
}
27+
end
28+
29+
@doc """
30+
Calculates complexity metrics for a function.
31+
"""
32+
def calculate_function_complexity_metrics(func_ast, cfg_data, dfg_data) do
33+
cfg_complexity = if cfg_data, do: cfg_data.complexity_metrics.cyclomatic || 1, else: 1
34+
# DFG doesn't have complexity metrics, calculate based on data flow complexity
35+
dfg_complexity = if dfg_data do
36+
# Calculate data flow complexity based on number of variables and flows
37+
# Handle both map and list formats for variables (defensive programming)
38+
variable_count = case dfg_data.variables do
39+
variables when is_map(variables) -> map_size(variables)
40+
variables when is_list(variables) -> length(variables)
41+
nil -> 0
42+
_ -> 0
43+
end
44+
flow_count = length(dfg_data.data_flows || [])
45+
max(1, variable_count + flow_count)
46+
else
47+
1
48+
end
49+
50+
%{
51+
combined_complexity: Float.round(cfg_complexity * 0.7 + dfg_complexity * 0.3, 2),
52+
cyclomatic_complexity: cfg_complexity,
53+
data_flow_complexity: dfg_complexity,
54+
cognitive_complexity: calculate_cognitive_complexity(func_ast),
55+
nesting_depth: calculate_nesting_depth(func_ast)
56+
}
57+
end
58+
59+
@doc """
60+
Analyzes performance characteristics of a function.
61+
"""
62+
def analyze_function_performance_characteristics(func_ast, cfg_data, _dfg_data) do
63+
# Simplified performance analysis
64+
has_loops = detect_loops_in_ast(func_ast)
65+
has_recursion = detect_recursion_in_ast(func_ast)
66+
complexity = if cfg_data, do: cfg_data.complexity_metrics.cyclomatic || 1, else: 1
67+
68+
%{
69+
has_issues: complexity > 10 or has_loops or has_recursion,
70+
bottlenecks: [],
71+
performance_score: max(0.0, 100.0 - complexity * 5),
72+
optimization_potential: if(complexity > 5, do: :high, else: :low)
73+
}
74+
end
75+
76+
# Private functions
77+
78+
defp count_ast_lines(ast) do
79+
# Simplified line counting from AST
80+
# In practice, this would traverse the AST and count unique line numbers
81+
50 # Placeholder
82+
end
83+
84+
defp calculate_cognitive_complexity(_ast) do
85+
# Simplified cognitive complexity calculation
86+
5 # Placeholder
87+
end
88+
89+
defp calculate_nesting_depth(ast) do
90+
# Simplified nesting depth calculation
91+
calculate_nesting_recursive(ast, 0)
92+
end
93+
94+
defp calculate_nesting_recursive(ast, current_depth) do
95+
case ast do
96+
{:if, _, _} -> current_depth + 1
97+
{:case, _, _} -> current_depth + 1
98+
{:cond, _, _} -> current_depth + 1
99+
{:try, _, _} -> current_depth + 1
100+
{:with, _, _} -> current_depth + 1
101+
{:for, _, _} -> current_depth + 1
102+
{:__block__, _, statements} ->
103+
Enum.map(statements, &calculate_nesting_recursive(&1, current_depth))
104+
|> Enum.max(fn -> current_depth end)
105+
{_, _, children} when is_list(children) ->
106+
Enum.map(children, &calculate_nesting_recursive(&1, current_depth))
107+
|> Enum.max(fn -> current_depth end)
108+
_ -> current_depth
109+
end
110+
end
111+
112+
defp detect_loops_in_ast(ast) do
113+
# Simplified loop detection
114+
case ast do
115+
{:for, _, _} -> true
116+
{:while, _, _} -> true
117+
{:__block__, _, statements} -> Enum.any?(statements, &detect_loops_in_ast/1)
118+
{_, _, children} when is_list(children) -> Enum.any?(children, &detect_loops_in_ast/1)
119+
_ -> false
120+
end
121+
end
122+
123+
defp detect_recursion_in_ast(ast) do
124+
# Simplified recursion detection
125+
# In practice, this would analyze function calls
126+
false
127+
end
128+
end
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
defmodule ElixirScope.ASTRepository.Enhanced.ProjectPopulator.DependencyAnalyzer do
2+
@moduledoc """
3+
Analyzes dependencies between modules.
4+
5+
Provides functionality to:
6+
- Build dependency graphs
7+
- Detect dependency cycles
8+
- Calculate dependency levels
9+
- Analyze dependency patterns
10+
"""
11+
12+
require Logger
13+
14+
@doc """
15+
Builds dependency graph from analyzed modules.
16+
"""
17+
def build_dependency_graph(analyzed_modules, opts \\ []) do
18+
track_dependencies = Keyword.get(opts, :track_dependencies, true)
19+
20+
if track_dependencies do
21+
try do
22+
dependency_graph = %{
23+
nodes: Map.keys(analyzed_modules),
24+
edges: extract_dependency_edges(analyzed_modules),
25+
cycles: detect_dependency_cycles(analyzed_modules),
26+
levels: calculate_dependency_levels(analyzed_modules)
27+
}
28+
29+
{:ok, dependency_graph}
30+
rescue
31+
e ->
32+
Logger.warning("Failed to build dependency graph: #{Exception.message(e)}")
33+
{:ok, %{nodes: [], edges: [], cycles: [], levels: %{}}}
34+
end
35+
else
36+
{:ok, %{nodes: [], edges: [], cycles: [], levels: %{}}}
37+
end
38+
end
39+
40+
# Private functions
41+
42+
defp extract_dependency_edges(analyzed_modules) do
43+
# Extract module-to-module dependencies
44+
Enum.flat_map(analyzed_modules, fn {module_name, module_data} ->
45+
Enum.map(module_data.dependencies, fn dep ->
46+
{module_name, dep}
47+
end)
48+
end)
49+
end
50+
51+
defp detect_dependency_cycles(_analyzed_modules) do
52+
# Simplified cycle detection
53+
# In a real implementation, this would use graph algorithms
54+
[]
55+
end
56+
57+
defp calculate_dependency_levels(analyzed_modules) do
58+
# Calculate dependency levels for topological ordering
59+
# Simplified implementation
60+
analyzed_modules
61+
|> Map.keys()
62+
|> Enum.with_index()
63+
|> Enum.into(%{}, fn {module, index} -> {module, index} end)
64+
end
65+
end
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
defmodule ElixirScope.ASTRepository.Enhanced.ProjectPopulator.FileDiscovery do
2+
@moduledoc """
3+
Handles discovery of Elixir files in a project directory.
4+
5+
Provides functionality to:
6+
- Discover files based on include/exclude patterns
7+
- Filter files by size limits
8+
- Handle directory validation
9+
"""
10+
11+
require Logger
12+
13+
@doc """
14+
Discovers all Elixir files in a project directory.
15+
"""
16+
def discover_elixir_files(project_path, opts \\ []) do
17+
include_patterns = Keyword.get(opts, :include_patterns, ["**/*.ex", "**/*.exs"])
18+
exclude_patterns = Keyword.get(opts, :exclude_patterns, [])
19+
max_file_size = Keyword.get(opts, :max_file_size, 1_000_000)
20+
21+
# Check if directory exists
22+
if not (File.exists?(project_path) and File.dir?(project_path)) do
23+
{:error, :directory_not_found}
24+
else
25+
try do
26+
files = include_patterns
27+
|> Enum.flat_map(fn pattern ->
28+
Path.wildcard(Path.join(project_path, pattern))
29+
end)
30+
|> Enum.uniq()
31+
|> Enum.reject(fn file ->
32+
Enum.any?(exclude_patterns, fn pattern ->
33+
String.contains?(file, pattern)
34+
end)
35+
end)
36+
|> Enum.filter(fn file ->
37+
case File.stat(file) do
38+
{:ok, %{size: size}} when size <= max_file_size -> true
39+
_ -> false
40+
end
41+
end)
42+
|> Enum.sort()
43+
44+
Logger.debug("Discovered #{length(files)} Elixir files")
45+
{:ok, files}
46+
rescue
47+
e ->
48+
{:error, {:file_discovery_failed, Exception.message(e)}}
49+
end
50+
end
51+
end
52+
end

0 commit comments

Comments
 (0)