1+ """Controller class for coordinating multiple code analysis tools."""
2+
13# pyright: reportOptionalMemberAccess=false
24from pathlib import Path
5+ import traceback
36from typing import Callable , Any
47
5- from ..data_types .smell_record import SmellRecord
6-
7- from ..config import CONFIG
8-
9- from ..data_types .smell import Smell
8+ from ecooptimizer .data_types .smell_record import SmellRecord
9+ from ecooptimizer .config import CONFIG
10+ from ecooptimizer .data_types .smell import Smell
11+ from ecooptimizer .analyzers .pylint_analyzer import PylintAnalyzer
12+ from ecooptimizer .analyzers .ast_analyzer import ASTAnalyzer
13+ from ecooptimizer .analyzers .astroid_analyzer import AstroidAnalyzer
14+ from ecooptimizer .utils .smells_registry import retrieve_smell_registry
1015
11- from .pylint_analyzer import PylintAnalyzer
12- from .ast_analyzer import ASTAnalyzer
13- from .astroid_analyzer import AstroidAnalyzer
14-
15- from ..utils .smells_registry import retrieve_smell_registry
16+ logger = CONFIG ["detectLogger" ]
1617
1718
1819class AnalyzerController :
20+ """Orchestrates multiple code analysis tools and aggregates their results."""
21+
1922 def __init__ (self ):
20- """Initializes analyzers for different analysis methods."""
23+ """Initializes analyzers for Pylint, AST, and Astroid analysis methods."""
2124 self .pylint_analyzer = PylintAnalyzer ()
2225 self .ast_analyzer = ASTAnalyzer ()
2326 self .astroid_analyzer = AstroidAnalyzer ()
2427
25- def run_analysis (self , file_path : Path , selected_smells : str | list [str ] = "ALL" ):
26- """
27- Runs multiple analysis tools on the given Python file and logs the results.
28- Returns a list of detected code smells.
29- """
28+ def run_analysis (
29+ self , file_path : Path , enabled_smells : dict [str , dict [str , int | str ]] | list [str ]
30+ ) -> list [Smell ]:
31+ """Runs configured analyzers on a file and returns aggregated results.
32+
33+ Args:
34+ file_path: Path to the Python file to analyze
35+ enabled_smells: Dictionary or list specifying which smells to detect
3036
37+ Returns:
38+ list[Smell]: All detected code smells
39+
40+ Raises:
41+ TypeError: If no smells are selected for detection
42+ Exception: Any errors during analysis are logged and re-raised
43+ """
3144 smells_data : list [Smell ] = []
3245
33- if not selected_smells :
34- raise TypeError ("At least 1 smell must be selected for detection" )
46+ if not enabled_smells :
47+ raise TypeError ("At least one smell must be selected for detection. " )
3548
36- SMELL_REGISTRY = retrieve_smell_registry (selected_smells )
49+ SMELL_REGISTRY = retrieve_smell_registry (enabled_smells )
3750
3851 try :
3952 pylint_smells = self .filter_smells_by_method (SMELL_REGISTRY , "pylint" )
4053 ast_smells = self .filter_smells_by_method (SMELL_REGISTRY , "ast" )
4154 astroid_smells = self .filter_smells_by_method (SMELL_REGISTRY , "astroid" )
4255
43- CONFIG [ "detectLogger" ] .info ("🟢 Starting analysis process" )
44- CONFIG [ "detectLogger" ] .info (f"📂 Analyzing file: { file_path } " )
56+ logger .info ("🟢 Starting analysis process" )
57+ logger .info (f"📂 Analyzing file: { file_path } " )
4558
4659 if pylint_smells :
47- CONFIG [ "detectLogger" ] .info (f"🔍 Running Pylint analysis on { file_path } " )
60+ logger .info (f"🔍 Running Pylint analysis on { file_path } " )
4861 pylint_options = self .generate_pylint_options (pylint_smells )
4962 pylint_results = self .pylint_analyzer .analyze (file_path , pylint_options )
5063 smells_data .extend (pylint_results )
51- CONFIG ["detectLogger" ].info (
52- f"✅ Pylint analysis completed. { len (pylint_results )} smells detected."
53- )
64+ logger .info (f"✅ Pylint analysis completed. { len (pylint_results )} smells detected." )
5465
5566 if ast_smells :
56- CONFIG [ "detectLogger" ] .info (f"🔍 Running AST analysis on { file_path } " )
67+ logger .info (f"🔍 Running AST analysis on { file_path } " )
5768 ast_options = self .generate_custom_options (ast_smells )
58- ast_results = self .ast_analyzer .analyze (file_path , ast_options )
69+ ast_results = self .ast_analyzer .analyze (file_path , ast_options ) # type: ignore
5970 smells_data .extend (ast_results )
60- CONFIG ["detectLogger" ].info (
61- f"✅ AST analysis completed. { len (ast_results )} smells detected."
62- )
71+ logger .info (f"✅ AST analysis completed. { len (ast_results )} smells detected." )
6372
6473 if astroid_smells :
65- CONFIG [ "detectLogger" ] .info (f"🔍 Running Astroid analysis on { file_path } " )
74+ logger .info (f"🔍 Running Astroid analysis on { file_path } " )
6675 astroid_options = self .generate_custom_options (astroid_smells )
67- astroid_results = self .astroid_analyzer .analyze (file_path , astroid_options )
76+ astroid_results = self .astroid_analyzer .analyze (file_path , astroid_options ) # type: ignore
6877 smells_data .extend (astroid_results )
69- CONFIG [ "detectLogger" ] .info (
78+ logger .info (
7079 f"✅ Astroid analysis completed. { len (astroid_results )} smells detected."
7180 )
7281
7382 if smells_data :
74- CONFIG [ "detectLogger" ] .info ("⚠️ Detected Code Smells:" )
83+ logger .info ("⚠️ Detected Code Smells:" )
7584 for smell in smells_data :
7685 if smell .occurences :
7786 first_occurrence = smell .occurences [0 ]
@@ -84,54 +93,69 @@ def run_analysis(self, file_path: Path, selected_smells: str | list[str] = "ALL"
8493 else :
8594 line_info = ""
8695
87- CONFIG [ "detectLogger" ] .info (f" • { smell .symbol } { line_info } : { smell .message } " )
96+ logger .info (f" • { smell .symbol } { line_info } : { smell .message } " )
8897 else :
89- CONFIG [ "detectLogger" ] .info ("🎉 No code smells detected." )
98+ logger .info ("🎉 No code smells detected." )
9099
91100 except Exception as e :
92- CONFIG ["detectLogger" ].error (f"❌ Error during analysis: { e !s} " )
101+ logger .error (f"❌ Error during analysis: { e !s} " )
102+ traceback .print_exc ()
103+ raise e
93104
94105 return smells_data
95106
96107 @staticmethod
97108 def filter_smells_by_method (
98109 smell_registry : dict [str , SmellRecord ], method : str
99110 ) -> dict [str , SmellRecord ]:
100- filtered = {
111+ """Filters smell registry by analysis method.
112+
113+ Args:
114+ smell_registry: Dictionary of all available smells
115+ method: Analysis method to filter by ('pylint', 'ast', or 'astroid')
116+
117+ Returns:
118+ dict[str, SmellRecord]: Filtered dictionary of smells for the specified method
119+ """
120+ return {
101121 name : smell
102122 for name , smell in smell_registry .items ()
103- if smell ["enabled" ] and ( method == smell ["analyzer_method" ])
123+ if smell ["enabled" ] and smell ["analyzer_method" ] == method
104124 }
105- return filtered
106125
107126 @staticmethod
108127 def generate_pylint_options (filtered_smells : dict [str , SmellRecord ]) -> list [str ]:
109- pylint_smell_symbols = []
110- extra_pylint_options = [
111- "--disable=all" ,
112- ]
128+ """Generates Pylint command-line options from enabled smells.
113129
114- for symbol , smell in zip ( filtered_smells . keys (), filtered_smells . values ()) :
115- pylint_smell_symbols . append ( symbol )
130+ Args :
131+ filtered_smells: Dictionary of smells enabled for Pylint analysis
116132
133+ Returns:
134+ list[str]: Pylint command-line arguments
135+ """
136+ pylint_options = ["--disable=all" ]
137+
138+ for _smell_name , smell in filtered_smells .items ():
117139 if len (smell ["analyzer_options" ]) > 0 :
118140 for param_data in smell ["analyzer_options" ].values ():
119141 flag = param_data ["flag" ]
120142 value = param_data ["value" ]
121143 if value :
122- extra_pylint_options .append (f"{ flag } ={ value } " )
144+ pylint_options .append (f"{ flag } ={ value } " )
123145
124- extra_pylint_options .append (f"--enable={ ',' .join (pylint_smell_symbols )} " )
125- return extra_pylint_options
146+ pylint_options .append (f"--enable={ ',' .join (filtered_smells . keys () )} " )
147+ return pylint_options
126148
127149 @staticmethod
128150 def generate_custom_options (
129151 filtered_smells : dict [str , SmellRecord ],
130- ) -> list [tuple [Callable , dict [str , Any ]]]: # type: ignore
131- ast_options = []
132- for smell in filtered_smells .values ():
133- method = smell ["checker" ]
134- options = smell ["analyzer_options" ]
135- ast_options .append ((method , options ))
136-
137- return ast_options
152+ ) -> list [tuple [Callable | None , dict [str , Any ]]]: # type: ignore
153+ """Generates options for custom AST/Astroid analyzers.
154+
155+ Args:
156+ filtered_smells: Dictionary of smells enabled for custom analysis
157+
158+ Returns:
159+ list[tuple]: List of (checker_function, options_dict) pairs
160+ """
161+ return [(smell ["checker" ], smell ["analyzer_options" ]) for smell in filtered_smells .values ()]
0 commit comments