qzx 0.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- qzx-0.2/Commands/DevCommands/AnalyzeComplexity.py +797 -0
- qzx-0.2/Commands/DevCommands/CompareFiles.py +228 -0
- qzx-0.2/Commands/DevCommands/CreateDocTemplatePython.py +781 -0
- qzx-0.2/Commands/DevCommands/__init__.py +8 -0
- qzx-0.2/Commands/FileCommands/ChangePermissions.py +196 -0
- qzx-0.2/Commands/FileCommands/CopyFile.py +149 -0
- qzx-0.2/Commands/FileCommands/CountLinesInFile.py +327 -0
- qzx-0.2/Commands/FileCommands/CreateDirectory.py +64 -0
- qzx-0.2/Commands/FileCommands/DeleteFile.py +238 -0
- qzx-0.2/Commands/FileCommands/DownloadFile.py +206 -0
- qzx-0.2/Commands/FileCommands/FindFiles.py +948 -0
- qzx-0.2/Commands/FileCommands/FindLargeFiles.py +325 -0
- qzx-0.2/Commands/FileCommands/FindText.py +521 -0
- qzx-0.2/Commands/FileCommands/GetHumanLanguageStatsFromFile.py +733 -0
- qzx-0.2/Commands/FileCommands/GetProgrammingLanguageStatsFromFile.py +1056 -0
- qzx-0.2/Commands/FileCommands/ListFiles.py +295 -0
- qzx-0.2/Commands/FileCommands/MoveFile.py +122 -0
- qzx-0.2/Commands/FileCommands/ReadFile.py +137 -0
- qzx-0.2/Commands/FileCommands/TouchFile.py +141 -0
- qzx-0.2/Commands/FileCommands/WonderFileTypeMagic.py +297 -0
- qzx-0.2/Commands/FileCommands/WonderIfDirectoryEmpty.py +112 -0
- qzx-0.2/Commands/FileCommands/WonderIfFileEmpty.py +142 -0
- qzx-0.2/Commands/FileCommands/WonderIfFileIsBinary.py +320 -0
- qzx-0.2/Commands/FileCommands/__init__.py +8 -0
- qzx-0.2/Commands/SystemCommands/CommandsBridge.py +442 -0
- qzx-0.2/Commands/SystemCommands/CurrentDir.py +117 -0
- qzx-0.2/Commands/SystemCommands/GetCPULoad.py +179 -0
- qzx-0.2/Commands/SystemCommands/GetCurrentDate.py +71 -0
- qzx-0.2/Commands/SystemCommands/GetCurrentTime.py +73 -0
- qzx-0.2/Commands/SystemCommands/GetCurrentUser.py +147 -0
- qzx-0.2/Commands/SystemCommands/GetDiskInfo.py +183 -0
- qzx-0.2/Commands/SystemCommands/GetDiskName.py +305 -0
- qzx-0.2/Commands/SystemCommands/GetGPULoad.py +430 -0
- qzx-0.2/Commands/SystemCommands/GetRAMInfo.py +176 -0
- qzx-0.2/Commands/SystemCommands/GetSmartValues.py +153 -0
- qzx-0.2/Commands/SystemCommands/KillProcess.py +190 -0
- qzx-0.2/Commands/SystemCommands/ListProcesses.py +252 -0
- qzx-0.2/Commands/SystemCommands/QZXVersion.py +108 -0
- qzx-0.2/Commands/SystemCommands/RunScript.py +158 -0
- qzx-0.2/Commands/SystemCommands/Shell.py +50 -0
- qzx-0.2/Commands/SystemCommands/SystemInfo.py +158 -0
- qzx-0.2/Commands/SystemCommands/Term.py +58 -0
- qzx-0.2/Commands/SystemCommands/Terminal.py +414 -0
- qzx-0.2/Commands/SystemCommands/TerminalWelcome.py +321 -0
- qzx-0.2/Commands/SystemCommands/Welcome.py +103 -0
- qzx-0.2/Commands/SystemCommands/WonderCommandsAmount.py +116 -0
- qzx-0.2/Commands/SystemCommands/WonderIfAdmin.py +243 -0
- qzx-0.2/Commands/SystemCommands/__init__.py +8 -0
- qzx-0.2/Commands/__init__.py +8 -0
- qzx-0.2/Core/__init__.py +11 -0
- qzx-0.2/Core/command_base.py +144 -0
- qzx-0.2/Core/command_loader.py +258 -0
- qzx-0.2/Core/qzx_core.py +114 -0
- qzx-0.2/Documentation/Documentation-BuildingGreatCommands.md +506 -0
- qzx-0.2/Documentation/Documentation-CheckList-VerboseIsGold.csv +44 -0
- qzx-0.2/Documentation/Documentation-Commands.md +1253 -0
- qzx-0.2/Documentation/Documentation-JSON-Checklist.CSV +102 -0
- qzx-0.2/Documentation/Documentation-Philosophy.md +286 -0
- qzx-0.2/Documentation/Documentation-WildcardsAndRecursiveCommands-Support.csv +8 -0
- qzx-0.2/Documentation/PodmanTestChecklist.csv +10 -0
- qzx-0.2/MANIFEST.in +8 -0
- qzx-0.2/PKG-INFO +125 -0
- qzx-0.2/README-English.md +210 -0
- qzx-0.2/README.md +96 -0
- qzx-0.2/Resources/FunctionWords/cantonese.json +25 -0
- qzx-0.2/Resources/FunctionWords/english.json +18 -0
- qzx-0.2/Resources/FunctionWords/french.json +21 -0
- qzx-0.2/Resources/FunctionWords/german.json +21 -0
- qzx-0.2/Resources/FunctionWords/italian.json +24 -0
- qzx-0.2/Resources/FunctionWords/mandarin.json +25 -0
- qzx-0.2/Resources/FunctionWords/portuguese.json +20 -0
- qzx-0.2/Resources/FunctionWords/spanish.json +20 -0
- qzx-0.2/Resources/ProgrammingLanguages/c.json +53 -0
- qzx-0.2/Resources/ProgrammingLanguages/cpp.json +64 -0
- qzx-0.2/Resources/ProgrammingLanguages/csharp.json +72 -0
- qzx-0.2/Resources/ProgrammingLanguages/css.json +106 -0
- qzx-0.2/Resources/ProgrammingLanguages/html.json +79 -0
- qzx-0.2/Resources/ProgrammingLanguages/java.json +113 -0
- qzx-0.2/Resources/ProgrammingLanguages/javascript.json +62 -0
- qzx-0.2/Resources/ProgrammingLanguages/kotlin.json +108 -0
- qzx-0.2/Resources/ProgrammingLanguages/python.json +59 -0
- qzx-0.2/Resources/ProgrammingLanguages/rust.json +92 -0
- qzx-0.2/Resources/ProgrammingLanguages/typescript.json +89 -0
- qzx-0.2/pyproject.toml +12 -0
- qzx-0.2/qzx.bat +17 -0
- qzx-0.2/qzx.egg-info/PKG-INFO +125 -0
- qzx-0.2/qzx.egg-info/SOURCES.txt +96 -0
- qzx-0.2/qzx.egg-info/dependency_links.txt +1 -0
- qzx-0.2/qzx.egg-info/entry_points.txt +2 -0
- qzx-0.2/qzx.egg-info/requires.txt +8 -0
- qzx-0.2/qzx.egg-info/top_level.txt +3 -0
- qzx-0.2/qzx.py +333 -0
- qzx-0.2/qzx.sh +20 -0
- qzx-0.2/setup.cfg +4 -0
- qzx-0.2/setup.py +50 -0
- qzx-0.2/tests/test_code_with_comments.py +118 -0
- qzx-0.2/tests/test_terminal.py +20 -0
- qzx-0.2/tests/test_undocumented.py +163 -0
|
@@ -0,0 +1,797 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
AnalyzeComplexity Command - Analyzes code complexity for development environments
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import os
|
|
9
|
+
import re
|
|
10
|
+
import math
|
|
11
|
+
import ast
|
|
12
|
+
from Core.command_base import CommandBase
|
|
13
|
+
|
|
14
|
+
class AnalyzeComplexityCommand(CommandBase):
|
|
15
|
+
"""
|
|
16
|
+
Command to analyze code complexity metrics for development environments
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
name = "analyzeComplexity"
|
|
20
|
+
description = "Analyzes code complexity metrics for files or projects"
|
|
21
|
+
category = "dev"
|
|
22
|
+
|
|
23
|
+
parameters = [
|
|
24
|
+
{
|
|
25
|
+
'name': 'file_path',
|
|
26
|
+
'description': 'Path to the file or directory to analyze',
|
|
27
|
+
'required': True
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
'name': 'recursive',
|
|
31
|
+
'description': 'Whether to recursively analyze directories',
|
|
32
|
+
'required': False,
|
|
33
|
+
'default': False
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
'name': 'format',
|
|
37
|
+
'description': 'Output format: "detailed" or "summary"',
|
|
38
|
+
'required': False,
|
|
39
|
+
'default': 'detailed'
|
|
40
|
+
}
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
examples = [
|
|
44
|
+
{
|
|
45
|
+
'command': 'qzx analyzeComplexity "main.py"',
|
|
46
|
+
'description': 'Analyze complexity of a single Python file'
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
'command': 'qzx analyzeComplexity "src/components" True',
|
|
50
|
+
'description': 'Recursively analyze all code files in a directory'
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
'command': 'qzx analyzeComplexity "project/" True "summary"',
|
|
54
|
+
'description': 'Generate a summary report for an entire project'
|
|
55
|
+
}
|
|
56
|
+
]
|
|
57
|
+
|
|
58
|
+
# File extensions to analyze
|
|
59
|
+
SUPPORTED_EXTENSIONS = {
|
|
60
|
+
'.py': 'python',
|
|
61
|
+
'.js': 'javascript',
|
|
62
|
+
'.jsx': 'javascript',
|
|
63
|
+
'.ts': 'typescript',
|
|
64
|
+
'.tsx': 'typescript',
|
|
65
|
+
'.java': 'java',
|
|
66
|
+
'.c': 'c',
|
|
67
|
+
'.cpp': 'cpp',
|
|
68
|
+
'.h': 'c',
|
|
69
|
+
'.hpp': 'cpp',
|
|
70
|
+
'.cs': 'csharp',
|
|
71
|
+
'.php': 'php',
|
|
72
|
+
'.rb': 'ruby',
|
|
73
|
+
'.go': 'go',
|
|
74
|
+
'.rs': 'rust'
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
def execute(self, file_path, recursive=False, format='detailed'):
|
|
78
|
+
"""
|
|
79
|
+
Analyzes code complexity of files or directories
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
file_path: Path to file or directory to analyze
|
|
83
|
+
recursive: Whether to recursively analyze directories
|
|
84
|
+
format: Output format (detailed or summary)
|
|
85
|
+
|
|
86
|
+
Returns:
|
|
87
|
+
Analysis results as string
|
|
88
|
+
"""
|
|
89
|
+
# Check if path exists
|
|
90
|
+
if not os.path.exists(file_path):
|
|
91
|
+
return f"Error: Path '{file_path}' does not exist"
|
|
92
|
+
|
|
93
|
+
results = []
|
|
94
|
+
total_files = 0
|
|
95
|
+
analyzed_files = 0
|
|
96
|
+
|
|
97
|
+
# Process a single file
|
|
98
|
+
if os.path.isfile(file_path):
|
|
99
|
+
analysis = self._analyze_file(file_path)
|
|
100
|
+
if analysis:
|
|
101
|
+
analyzed_files += 1
|
|
102
|
+
results.append(analysis)
|
|
103
|
+
total_files = 1
|
|
104
|
+
|
|
105
|
+
# Process a directory
|
|
106
|
+
elif os.path.isdir(file_path):
|
|
107
|
+
for root, dirs, files in os.walk(file_path):
|
|
108
|
+
if not recursive and root != file_path:
|
|
109
|
+
continue
|
|
110
|
+
|
|
111
|
+
for file in files:
|
|
112
|
+
total_files += 1
|
|
113
|
+
full_path = os.path.join(root, file)
|
|
114
|
+
analysis = self._analyze_file(full_path)
|
|
115
|
+
if analysis:
|
|
116
|
+
analyzed_files += 1
|
|
117
|
+
results.append(analysis)
|
|
118
|
+
|
|
119
|
+
# Format and return results
|
|
120
|
+
if not results:
|
|
121
|
+
return f"No analyzable files found in '{file_path}'"
|
|
122
|
+
|
|
123
|
+
return self._format_results(results, total_files, analyzed_files, format)
|
|
124
|
+
|
|
125
|
+
def _analyze_file(self, file_path):
|
|
126
|
+
"""
|
|
127
|
+
Analyzes a single file for complexity metrics
|
|
128
|
+
|
|
129
|
+
Args:
|
|
130
|
+
file_path: Path to the file to analyze
|
|
131
|
+
|
|
132
|
+
Returns:
|
|
133
|
+
Dict with analysis results or None if file can't be analyzed
|
|
134
|
+
"""
|
|
135
|
+
# Check if file type is supported
|
|
136
|
+
_, ext = os.path.splitext(file_path)
|
|
137
|
+
if ext.lower() not in self.SUPPORTED_EXTENSIONS:
|
|
138
|
+
return None
|
|
139
|
+
|
|
140
|
+
language = self.SUPPORTED_EXTENSIONS[ext.lower()]
|
|
141
|
+
|
|
142
|
+
try:
|
|
143
|
+
# Read file content
|
|
144
|
+
with open(file_path, 'r', encoding='utf-8', errors='replace') as f:
|
|
145
|
+
content = f.read()
|
|
146
|
+
|
|
147
|
+
# Get basic metrics
|
|
148
|
+
metrics = {
|
|
149
|
+
'file_path': file_path,
|
|
150
|
+
'language': language,
|
|
151
|
+
'size_bytes': os.path.getsize(file_path),
|
|
152
|
+
'line_count': content.count('\n') + 1,
|
|
153
|
+
'character_count': len(content)
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
# Get language-specific metrics
|
|
157
|
+
metrics.update(self._get_language_metrics(content, language))
|
|
158
|
+
|
|
159
|
+
# Calculate complexity score
|
|
160
|
+
metrics['complexity_score'] = self._calculate_complexity_score(metrics)
|
|
161
|
+
|
|
162
|
+
return metrics
|
|
163
|
+
except Exception as e:
|
|
164
|
+
return {
|
|
165
|
+
'file_path': file_path,
|
|
166
|
+
'language': language,
|
|
167
|
+
'error': str(e)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
def _get_language_metrics(self, content, language):
|
|
171
|
+
"""
|
|
172
|
+
Gets language-specific metrics for code
|
|
173
|
+
|
|
174
|
+
Args:
|
|
175
|
+
content: File content
|
|
176
|
+
language: Programming language
|
|
177
|
+
|
|
178
|
+
Returns:
|
|
179
|
+
Dict with language-specific metrics
|
|
180
|
+
"""
|
|
181
|
+
metrics = {
|
|
182
|
+
'comment_count': 0,
|
|
183
|
+
'function_count': 0,
|
|
184
|
+
'class_count': 0,
|
|
185
|
+
'condition_count': 0,
|
|
186
|
+
'loop_count': 0,
|
|
187
|
+
'avg_line_length': 0,
|
|
188
|
+
'max_line_length': 0,
|
|
189
|
+
'cyclomatic_complexity': 1, # Base complexity
|
|
190
|
+
'halstead_metrics': {},
|
|
191
|
+
'maintainability_index': 0
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
# Count non-empty lines
|
|
195
|
+
lines = [line for line in content.split('\n') if line.strip()]
|
|
196
|
+
metrics['non_empty_lines'] = len(lines)
|
|
197
|
+
|
|
198
|
+
if not lines:
|
|
199
|
+
return metrics
|
|
200
|
+
|
|
201
|
+
# Calculate line length metrics
|
|
202
|
+
line_lengths = [len(line) for line in lines]
|
|
203
|
+
metrics['avg_line_length'] = sum(line_lengths) / len(line_lengths)
|
|
204
|
+
metrics['max_line_length'] = max(line_lengths)
|
|
205
|
+
|
|
206
|
+
# Python-specific analysis
|
|
207
|
+
if language == 'python':
|
|
208
|
+
return self._analyze_python(content, metrics)
|
|
209
|
+
|
|
210
|
+
# JavaScript/TypeScript analysis
|
|
211
|
+
elif language in ['javascript', 'typescript']:
|
|
212
|
+
return self._analyze_js_ts(content, metrics)
|
|
213
|
+
|
|
214
|
+
# Generic analysis for other languages
|
|
215
|
+
else:
|
|
216
|
+
return self._analyze_generic(content, metrics, language)
|
|
217
|
+
|
|
218
|
+
def _analyze_python(self, content, metrics):
|
|
219
|
+
"""
|
|
220
|
+
Analyzes Python code metrics
|
|
221
|
+
|
|
222
|
+
Args:
|
|
223
|
+
content: File content
|
|
224
|
+
metrics: Metrics dictionary to update
|
|
225
|
+
|
|
226
|
+
Returns:
|
|
227
|
+
Updated metrics dictionary
|
|
228
|
+
"""
|
|
229
|
+
try:
|
|
230
|
+
# Parse Python code
|
|
231
|
+
tree = ast.parse(content)
|
|
232
|
+
|
|
233
|
+
# Count functions and classes
|
|
234
|
+
for node in ast.walk(tree):
|
|
235
|
+
if isinstance(node, ast.FunctionDef) or isinstance(node, ast.AsyncFunctionDef):
|
|
236
|
+
metrics['function_count'] += 1
|
|
237
|
+
|
|
238
|
+
# Count conditions and loops within function
|
|
239
|
+
for child in ast.walk(node):
|
|
240
|
+
if isinstance(child, (ast.If, ast.IfExp)):
|
|
241
|
+
metrics['condition_count'] += 1
|
|
242
|
+
metrics['cyclomatic_complexity'] += 1
|
|
243
|
+
elif isinstance(child, (ast.For, ast.AsyncFor, ast.While)):
|
|
244
|
+
metrics['loop_count'] += 1
|
|
245
|
+
metrics['cyclomatic_complexity'] += 1
|
|
246
|
+
elif isinstance(child, ast.BoolOp):
|
|
247
|
+
# Each boolean operator (and, or) adds complexity
|
|
248
|
+
metrics['cyclomatic_complexity'] += len(child.values) - 1
|
|
249
|
+
|
|
250
|
+
elif isinstance(node, ast.ClassDef):
|
|
251
|
+
metrics['class_count'] += 1
|
|
252
|
+
|
|
253
|
+
# Count comments (approximate)
|
|
254
|
+
comment_pattern = r'^\s*#.*$'
|
|
255
|
+
metrics['comment_count'] = len(re.findall(comment_pattern, content, re.MULTILINE))
|
|
256
|
+
|
|
257
|
+
# Calculate Halstead metrics
|
|
258
|
+
metrics['halstead_metrics'] = self._calculate_halstead_metrics(content, 'python')
|
|
259
|
+
|
|
260
|
+
# Calculate maintainability index
|
|
261
|
+
metrics['maintainability_index'] = self._calculate_maintainability_index(
|
|
262
|
+
metrics['cyclomatic_complexity'],
|
|
263
|
+
metrics['halstead_metrics'].get('volume', 0),
|
|
264
|
+
metrics['line_count']
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
return metrics
|
|
268
|
+
except SyntaxError:
|
|
269
|
+
# If unable to parse, fall back to generic analysis
|
|
270
|
+
return self._analyze_generic(content, metrics, 'python')
|
|
271
|
+
|
|
272
|
+
def _analyze_js_ts(self, content, metrics):
|
|
273
|
+
"""
|
|
274
|
+
Analyzes JavaScript/TypeScript code metrics
|
|
275
|
+
|
|
276
|
+
Args:
|
|
277
|
+
content: File content
|
|
278
|
+
metrics: Metrics dictionary to update
|
|
279
|
+
|
|
280
|
+
Returns:
|
|
281
|
+
Updated metrics dictionary
|
|
282
|
+
"""
|
|
283
|
+
# Count functions
|
|
284
|
+
function_patterns = [
|
|
285
|
+
r'function\s+\w+\s*\(', # Named functions
|
|
286
|
+
r'const\s+\w+\s*=\s*function', # Function expressions
|
|
287
|
+
r'const\s+\w+\s*=\s*\([^)]*\)\s*=>', # Arrow functions
|
|
288
|
+
r'\w+\s*:\s*function', # Object methods
|
|
289
|
+
]
|
|
290
|
+
for pattern in function_patterns:
|
|
291
|
+
metrics['function_count'] += len(re.findall(pattern, content))
|
|
292
|
+
|
|
293
|
+
# Count classes
|
|
294
|
+
class_pattern = r'class\s+\w+'
|
|
295
|
+
metrics['class_count'] = len(re.findall(class_pattern, content))
|
|
296
|
+
|
|
297
|
+
# Count conditionals
|
|
298
|
+
condition_patterns = [r'\bif\s*\(', r'\bswitch\s*\(', r'\?']
|
|
299
|
+
for pattern in condition_patterns:
|
|
300
|
+
count = len(re.findall(pattern, content))
|
|
301
|
+
metrics['condition_count'] += count
|
|
302
|
+
metrics['cyclomatic_complexity'] += count
|
|
303
|
+
|
|
304
|
+
# Count loops
|
|
305
|
+
loop_patterns = [r'\bfor\s*\(', r'\bwhile\s*\(', r'\bdo\s*\{']
|
|
306
|
+
for pattern in loop_patterns:
|
|
307
|
+
count = len(re.findall(pattern, content))
|
|
308
|
+
metrics['loop_count'] += count
|
|
309
|
+
metrics['cyclomatic_complexity'] += count
|
|
310
|
+
|
|
311
|
+
# Count boolean operators
|
|
312
|
+
bool_op_count = len(re.findall(r'&&|\|\|', content))
|
|
313
|
+
metrics['cyclomatic_complexity'] += bool_op_count
|
|
314
|
+
|
|
315
|
+
# Count comments
|
|
316
|
+
comment_patterns = [r'\/\/.*$', r'\/\*[\s\S]*?\*\/']
|
|
317
|
+
for pattern in comment_patterns:
|
|
318
|
+
metrics['comment_count'] += len(re.findall(pattern, content, re.MULTILINE))
|
|
319
|
+
|
|
320
|
+
# Calculate Halstead metrics
|
|
321
|
+
metrics['halstead_metrics'] = self._calculate_halstead_metrics(content, 'javascript')
|
|
322
|
+
|
|
323
|
+
# Calculate maintainability index
|
|
324
|
+
metrics['maintainability_index'] = self._calculate_maintainability_index(
|
|
325
|
+
metrics['cyclomatic_complexity'],
|
|
326
|
+
metrics['halstead_metrics'].get('volume', 0),
|
|
327
|
+
metrics['line_count']
|
|
328
|
+
)
|
|
329
|
+
|
|
330
|
+
return metrics
|
|
331
|
+
|
|
332
|
+
def _analyze_generic(self, content, metrics, language):
|
|
333
|
+
"""
|
|
334
|
+
Performs generic code analysis for unsupported languages
|
|
335
|
+
|
|
336
|
+
Args:
|
|
337
|
+
content: File content
|
|
338
|
+
metrics: Metrics dictionary to update
|
|
339
|
+
language: Programming language
|
|
340
|
+
|
|
341
|
+
Returns:
|
|
342
|
+
Updated metrics dictionary
|
|
343
|
+
"""
|
|
344
|
+
# Generic patterns for various languages
|
|
345
|
+
patterns = {
|
|
346
|
+
'function': {
|
|
347
|
+
'python': r'def\s+\w+\s*\(',
|
|
348
|
+
'javascript': r'function\s+\w+|const\s+\w+\s*=\s*function|\w+\s*:\s*function|\([^)]*\)\s*=>',
|
|
349
|
+
'typescript': r'function\s+\w+|const\s+\w+\s*=\s*function|\w+\s*:\s*function|\([^)]*\)\s*=>',
|
|
350
|
+
'java': r'(public|private|protected|static|\s) +[\w\<\>\[\]]+\s+(\w+) *\([^\)]*\)',
|
|
351
|
+
'c': r'\w+\s+\w+\s*\([^;]*\)\s*\{',
|
|
352
|
+
'cpp': r'[\w\<\>\[\]]+\s+\w+\s*\([^;]*\)\s*\{',
|
|
353
|
+
'csharp': r'(public|private|protected|static|\s) +[\w\<\>\[\]]+\s+(\w+) *\([^\)]*\)',
|
|
354
|
+
'php': r'function\s+\w+\s*\(',
|
|
355
|
+
'ruby': r'def\s+\w+',
|
|
356
|
+
'go': r'func\s+\w+',
|
|
357
|
+
'rust': r'fn\s+\w+'
|
|
358
|
+
},
|
|
359
|
+
'class': {
|
|
360
|
+
'python': r'class\s+\w+',
|
|
361
|
+
'javascript': r'class\s+\w+',
|
|
362
|
+
'typescript': r'class\s+\w+|interface\s+\w+',
|
|
363
|
+
'java': r'class\s+\w+|interface\s+\w+',
|
|
364
|
+
'cpp': r'class\s+\w+',
|
|
365
|
+
'csharp': r'class\s+\w+|interface\s+\w+',
|
|
366
|
+
'php': r'class\s+\w+',
|
|
367
|
+
'ruby': r'class\s+\w+',
|
|
368
|
+
'rust': r'struct\s+\w+|enum\s+\w+|trait\s+\w+'
|
|
369
|
+
},
|
|
370
|
+
'condition': {
|
|
371
|
+
'generic': r'\bif\s*\(|\bswitch\s*\(|\?|else\s+if'
|
|
372
|
+
},
|
|
373
|
+
'loop': {
|
|
374
|
+
'generic': r'\bfor\s*\(|\bwhile\s*\(|\bdo\s*\{|\bforeach'
|
|
375
|
+
},
|
|
376
|
+
'comment': {
|
|
377
|
+
'python': r'#.*$',
|
|
378
|
+
'c_style': r'\/\/.*$|\/\*[\s\S]*?\*\/',
|
|
379
|
+
'ruby': r'#.*$',
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
# Count functions
|
|
384
|
+
func_pattern = patterns['function'].get(language, r'\b\w+\s+\w+\s*\(')
|
|
385
|
+
metrics['function_count'] = len(re.findall(func_pattern, content, re.MULTILINE))
|
|
386
|
+
|
|
387
|
+
# Count classes
|
|
388
|
+
class_pattern = patterns['class'].get(language, r'class\s+\w+')
|
|
389
|
+
metrics['class_count'] = len(re.findall(class_pattern, content, re.MULTILINE))
|
|
390
|
+
|
|
391
|
+
# Count conditions and update cyclomatic complexity
|
|
392
|
+
condition_count = len(re.findall(patterns['condition']['generic'], content))
|
|
393
|
+
metrics['condition_count'] = condition_count
|
|
394
|
+
metrics['cyclomatic_complexity'] += condition_count
|
|
395
|
+
|
|
396
|
+
# Count loops and update cyclomatic complexity
|
|
397
|
+
loop_count = len(re.findall(patterns['loop']['generic'], content))
|
|
398
|
+
metrics['loop_count'] = loop_count
|
|
399
|
+
metrics['cyclomatic_complexity'] += loop_count
|
|
400
|
+
|
|
401
|
+
# Count comments
|
|
402
|
+
if language == 'python' or language == 'ruby':
|
|
403
|
+
comment_pattern = patterns['comment']['python']
|
|
404
|
+
else:
|
|
405
|
+
comment_pattern = patterns['comment']['c_style']
|
|
406
|
+
|
|
407
|
+
metrics['comment_count'] = len(re.findall(comment_pattern, content, re.MULTILINE))
|
|
408
|
+
|
|
409
|
+
# Approximate Halstead metrics
|
|
410
|
+
metrics['halstead_metrics'] = self._calculate_halstead_metrics(content, language)
|
|
411
|
+
|
|
412
|
+
# Calculate maintainability index
|
|
413
|
+
metrics['maintainability_index'] = self._calculate_maintainability_index(
|
|
414
|
+
metrics['cyclomatic_complexity'],
|
|
415
|
+
metrics['halstead_metrics'].get('volume', 0),
|
|
416
|
+
metrics['line_count']
|
|
417
|
+
)
|
|
418
|
+
|
|
419
|
+
return metrics
|
|
420
|
+
|
|
421
|
+
def _calculate_halstead_metrics(self, content, language):
|
|
422
|
+
"""
|
|
423
|
+
Calculates Halstead complexity metrics
|
|
424
|
+
|
|
425
|
+
Args:
|
|
426
|
+
content: File content
|
|
427
|
+
language: Programming language
|
|
428
|
+
|
|
429
|
+
Returns:
|
|
430
|
+
Dict with Halstead metrics
|
|
431
|
+
"""
|
|
432
|
+
# Define operators and operands based on language
|
|
433
|
+
operators = {
|
|
434
|
+
'python': ['+', '-', '*', '/', '%', '**', '//', '=', '+=', '-=', '*=', '/=', '%=', '**=', '//=',
|
|
435
|
+
'==', '!=', '>', '<', '>=', '<=', 'and', 'or', 'not', 'in', 'is', 'lambda'],
|
|
436
|
+
'javascript': ['+', '-', '*', '/', '%', '**', '=', '+=', '-=', '*=', '/=', '%=', '**=',
|
|
437
|
+
'==', '===', '!=', '!==', '>', '<', '>=', '<=', '&&', '||', '!', 'typeof', 'instanceof']
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
# Get appropriate operators or use a generic set
|
|
441
|
+
op_list = operators.get(language, operators['javascript'])
|
|
442
|
+
|
|
443
|
+
# Count unique operators
|
|
444
|
+
n1 = len(set(re.findall('|'.join(re.escape(op) for op in op_list), content)))
|
|
445
|
+
if n1 == 0:
|
|
446
|
+
n1 = 1 # Avoid division by zero
|
|
447
|
+
|
|
448
|
+
# Count total operators
|
|
449
|
+
N1 = len(re.findall('|'.join(re.escape(op) for op in op_list), content))
|
|
450
|
+
|
|
451
|
+
# Approximate unique operands (identifiers and literals)
|
|
452
|
+
identifier_pattern = r'\b[a-zA-Z_]\w*\b'
|
|
453
|
+
string_literal_pattern = r'"[^"]*"|\'[^\']*\''
|
|
454
|
+
number_pattern = r'\b\d+(\.\d+)?\b'
|
|
455
|
+
|
|
456
|
+
identifiers = set(re.findall(identifier_pattern, content))
|
|
457
|
+
strings = set(re.findall(string_literal_pattern, content))
|
|
458
|
+
numbers = set(re.findall(number_pattern, content))
|
|
459
|
+
|
|
460
|
+
# Remove keywords from identifiers based on language
|
|
461
|
+
keywords = {
|
|
462
|
+
'python': ['def', 'class', 'if', 'else', 'elif', 'for', 'while', 'try', 'except', 'finally',
|
|
463
|
+
'with', 'as', 'import', 'from', 'return', 'yield', 'break', 'continue', 'pass',
|
|
464
|
+
'True', 'False', 'None'],
|
|
465
|
+
'javascript': ['function', 'class', 'if', 'else', 'for', 'while', 'try', 'catch', 'finally',
|
|
466
|
+
'with', 'switch', 'case', 'default', 'break', 'continue', 'return', 'throw',
|
|
467
|
+
'typeof', 'instanceof', 'new', 'this', 'super', 'true', 'false', 'null', 'undefined']
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
lang_keywords = keywords.get(language, keywords['javascript'])
|
|
471
|
+
identifiers = identifiers - set(lang_keywords)
|
|
472
|
+
|
|
473
|
+
n2 = len(identifiers) + len(strings) + len(numbers)
|
|
474
|
+
if n2 == 0:
|
|
475
|
+
n2 = 1 # Avoid division by zero
|
|
476
|
+
|
|
477
|
+
# Count total operands (approximate)
|
|
478
|
+
N2 = len(re.findall(identifier_pattern, content)) + len(re.findall(string_literal_pattern, content)) + len(re.findall(number_pattern, content))
|
|
479
|
+
|
|
480
|
+
# Calculate metrics
|
|
481
|
+
N = N1 + N2
|
|
482
|
+
n = n1 + n2
|
|
483
|
+
|
|
484
|
+
# Calculate volume
|
|
485
|
+
volume = N * math.log2(n) if n > 0 else 0
|
|
486
|
+
|
|
487
|
+
# Calculate difficulty
|
|
488
|
+
difficulty = (n1 / 2) * (N2 / n2) if n2 > 0 else 0
|
|
489
|
+
|
|
490
|
+
# Calculate effort
|
|
491
|
+
effort = difficulty * volume
|
|
492
|
+
|
|
493
|
+
return {
|
|
494
|
+
'unique_operators': n1,
|
|
495
|
+
'unique_operands': n2,
|
|
496
|
+
'total_operators': N1,
|
|
497
|
+
'total_operands': N2,
|
|
498
|
+
'vocabulary': n,
|
|
499
|
+
'length': N,
|
|
500
|
+
'volume': volume,
|
|
501
|
+
'difficulty': difficulty,
|
|
502
|
+
'effort': effort
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
def _calculate_maintainability_index(self, cyclomatic_complexity, halstead_volume, line_count):
|
|
506
|
+
"""
|
|
507
|
+
Calculates the Maintainability Index
|
|
508
|
+
|
|
509
|
+
Args:
|
|
510
|
+
cyclomatic_complexity: Cyclomatic complexity
|
|
511
|
+
halstead_volume: Halstead volume
|
|
512
|
+
line_count: Number of lines of code
|
|
513
|
+
|
|
514
|
+
Returns:
|
|
515
|
+
Maintainability Index (0-100)
|
|
516
|
+
"""
|
|
517
|
+
# Original formula: 171 - 5.2 * ln(V) - 0.23 * G - 16.2 * ln(LOC)
|
|
518
|
+
# Normalized to 0-100 scale
|
|
519
|
+
|
|
520
|
+
if halstead_volume <= 0 or line_count <= 0:
|
|
521
|
+
return 100 # Perfect score for empty files
|
|
522
|
+
|
|
523
|
+
mi = 171 - 5.2 * math.log(halstead_volume) - 0.23 * cyclomatic_complexity - 16.2 * math.log(line_count)
|
|
524
|
+
|
|
525
|
+
# Normalize to 0-100
|
|
526
|
+
normalized_mi = max(0, min(100, mi * 100 / 171))
|
|
527
|
+
|
|
528
|
+
return normalized_mi
|
|
529
|
+
|
|
530
|
+
def _calculate_complexity_score(self, metrics):
|
|
531
|
+
"""
|
|
532
|
+
Calculates an overall complexity score based on multiple metrics
|
|
533
|
+
|
|
534
|
+
Args:
|
|
535
|
+
metrics: Dictionary of code metrics
|
|
536
|
+
|
|
537
|
+
Returns:
|
|
538
|
+
Complexity score from 0-10
|
|
539
|
+
"""
|
|
540
|
+
# Define weights for each metric
|
|
541
|
+
weights = {
|
|
542
|
+
'cyclomatic_complexity': 0.3,
|
|
543
|
+
'halstead_effort': 0.2,
|
|
544
|
+
'line_count': 0.1,
|
|
545
|
+
'function_count': 0.1,
|
|
546
|
+
'class_count': 0.1,
|
|
547
|
+
'maintainability': 0.2
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
# Normalize metrics to 0-10 scale
|
|
551
|
+
normalized = {
|
|
552
|
+
'cyclomatic_complexity': min(10, metrics['cyclomatic_complexity'] / 5),
|
|
553
|
+
'halstead_effort': min(10, metrics['halstead_metrics'].get('effort', 0) / 10000),
|
|
554
|
+
'line_count': min(10, metrics['line_count'] / 300),
|
|
555
|
+
'function_count': min(10, metrics['function_count'] / 20),
|
|
556
|
+
'class_count': min(10, metrics['class_count'] / 10),
|
|
557
|
+
'maintainability': 10 - (metrics['maintainability_index'] / 10)
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
# Calculate weighted score
|
|
561
|
+
score = 0
|
|
562
|
+
for key, weight in weights.items():
|
|
563
|
+
score += normalized[key] * weight
|
|
564
|
+
|
|
565
|
+
return score
|
|
566
|
+
|
|
567
|
+
def _format_results(self, results, total_files, analyzed_files, format_type):
|
|
568
|
+
"""
|
|
569
|
+
Formats the analysis results
|
|
570
|
+
|
|
571
|
+
Args:
|
|
572
|
+
results: List of analysis results
|
|
573
|
+
total_files: Total number of files found
|
|
574
|
+
analyzed_files: Number of files successfully analyzed
|
|
575
|
+
format_type: Output format type (detailed or summary)
|
|
576
|
+
|
|
577
|
+
Returns:
|
|
578
|
+
Formatted analysis results as string
|
|
579
|
+
"""
|
|
580
|
+
# Sort results by complexity score (descending)
|
|
581
|
+
results.sort(key=lambda x: x.get('complexity_score', 0), reverse=True)
|
|
582
|
+
|
|
583
|
+
if format_type.lower() == 'summary':
|
|
584
|
+
return self._format_summary(results, total_files, analyzed_files)
|
|
585
|
+
else:
|
|
586
|
+
return self._format_detailed(results, total_files, analyzed_files)
|
|
587
|
+
|
|
588
|
+
def _format_detailed(self, results, total_files, analyzed_files):
|
|
589
|
+
"""
|
|
590
|
+
Formats results in detailed format
|
|
591
|
+
|
|
592
|
+
Args:
|
|
593
|
+
results: List of analysis results
|
|
594
|
+
total_files: Total number of files found
|
|
595
|
+
analyzed_files: Number of files successfully analyzed
|
|
596
|
+
|
|
597
|
+
Returns:
|
|
598
|
+
Detailed formatted analysis results
|
|
599
|
+
"""
|
|
600
|
+
output = [
|
|
601
|
+
"Code Complexity Analysis Report (Detailed)",
|
|
602
|
+
"=" * 50,
|
|
603
|
+
f"Files Analyzed: {analyzed_files}/{total_files}",
|
|
604
|
+
""
|
|
605
|
+
]
|
|
606
|
+
|
|
607
|
+
for i, result in enumerate(results, 1):
|
|
608
|
+
# Skip files with errors
|
|
609
|
+
if 'error' in result:
|
|
610
|
+
output.append(f"File {i}: {result['file_path']}")
|
|
611
|
+
output.append(f"Error: {result['error']}")
|
|
612
|
+
output.append("")
|
|
613
|
+
continue
|
|
614
|
+
|
|
615
|
+
# Add file info
|
|
616
|
+
output.append(f"File {i}: {result['file_path']}")
|
|
617
|
+
output.append(f"Language: {result['language']}")
|
|
618
|
+
output.append(f"Complexity Score: {result['complexity_score']:.2f}/10")
|
|
619
|
+
|
|
620
|
+
# Complexity classification
|
|
621
|
+
score = result['complexity_score']
|
|
622
|
+
if score < 2:
|
|
623
|
+
complexity = "Very Low"
|
|
624
|
+
elif score < 4:
|
|
625
|
+
complexity = "Low"
|
|
626
|
+
elif score < 6:
|
|
627
|
+
complexity = "Moderate"
|
|
628
|
+
elif score < 8:
|
|
629
|
+
complexity = "High"
|
|
630
|
+
else:
|
|
631
|
+
complexity = "Very High"
|
|
632
|
+
|
|
633
|
+
output.append(f"Complexity Rating: {complexity}")
|
|
634
|
+
|
|
635
|
+
# Add basic metrics
|
|
636
|
+
output.append("")
|
|
637
|
+
output.append("Basic Metrics:")
|
|
638
|
+
output.append(f"- Lines of Code: {result['line_count']}")
|
|
639
|
+
output.append(f"- Non-Empty Lines: {result.get('non_empty_lines', 'N/A')}")
|
|
640
|
+
output.append(f"- File Size: {result['size_bytes']} bytes")
|
|
641
|
+
output.append(f"- Average Line Length: {result['avg_line_length']:.2f} characters")
|
|
642
|
+
output.append(f"- Maximum Line Length: {result['max_line_length']} characters")
|
|
643
|
+
|
|
644
|
+
# Add structural metrics
|
|
645
|
+
output.append("")
|
|
646
|
+
output.append("Structural Metrics:")
|
|
647
|
+
output.append(f"- Functions/Methods: {result['function_count']}")
|
|
648
|
+
output.append(f"- Classes/Interfaces: {result['class_count']}")
|
|
649
|
+
output.append(f"- Conditional Statements: {result['condition_count']}")
|
|
650
|
+
output.append(f"- Loops: {result['loop_count']}")
|
|
651
|
+
output.append(f"- Comments: {result['comment_count']}")
|
|
652
|
+
|
|
653
|
+
# Add complexity metrics
|
|
654
|
+
output.append("")
|
|
655
|
+
output.append("Complexity Metrics:")
|
|
656
|
+
output.append(f"- Cyclomatic Complexity: {result['cyclomatic_complexity']}")
|
|
657
|
+
output.append(f"- Maintainability Index: {result['maintainability_index']:.2f}/100")
|
|
658
|
+
|
|
659
|
+
# Add Halstead metrics
|
|
660
|
+
halstead = result['halstead_metrics']
|
|
661
|
+
output.append("")
|
|
662
|
+
output.append("Halstead Metrics:")
|
|
663
|
+
output.append(f"- Vocabulary: {halstead.get('vocabulary', 'N/A')}")
|
|
664
|
+
output.append(f"- Length: {halstead.get('length', 'N/A')}")
|
|
665
|
+
output.append(f"- Volume: {halstead.get('volume', 'N/A'):.2f}")
|
|
666
|
+
output.append(f"- Difficulty: {halstead.get('difficulty', 'N/A'):.2f}")
|
|
667
|
+
output.append(f"- Effort: {halstead.get('effort', 'N/A'):.2f}")
|
|
668
|
+
|
|
669
|
+
# Add recommendations
|
|
670
|
+
output.append("")
|
|
671
|
+
output.append("Recommendations:")
|
|
672
|
+
|
|
673
|
+
if result['complexity_score'] >= 7:
|
|
674
|
+
output.append("- Consider refactoring complex sections into smaller functions")
|
|
675
|
+
output.append("- Reduce nesting levels in conditional statements")
|
|
676
|
+
|
|
677
|
+
if result['function_count'] > 0 and result['comment_count'] / result['function_count'] < 0.5:
|
|
678
|
+
output.append("- Add more comments to improve code documentation")
|
|
679
|
+
|
|
680
|
+
if result['avg_line_length'] > 80:
|
|
681
|
+
output.append("- Reduce average line length for better readability")
|
|
682
|
+
|
|
683
|
+
if result['max_line_length'] > 100:
|
|
684
|
+
output.append("- Break up long lines of code")
|
|
685
|
+
|
|
686
|
+
if result['maintainability_index'] < 65:
|
|
687
|
+
output.append("- Improve maintainability by simplifying complex methods")
|
|
688
|
+
|
|
689
|
+
# Separator between files
|
|
690
|
+
output.append("")
|
|
691
|
+
output.append("-" * 50)
|
|
692
|
+
output.append("")
|
|
693
|
+
|
|
694
|
+
# Add overall summary
|
|
695
|
+
output.append("")
|
|
696
|
+
output.append("Overall Summary:")
|
|
697
|
+
|
|
698
|
+
if results:
|
|
699
|
+
avg_complexity = sum(r.get('complexity_score', 0) for r in results) / len(results)
|
|
700
|
+
avg_maintainability = sum(r.get('maintainability_index', 0) for r in results if 'maintainability_index' in r) / len([r for r in results if 'maintainability_index' in r]) if any('maintainability_index' in r for r in results) else 0
|
|
701
|
+
|
|
702
|
+
output.append(f"- Average Complexity Score: {avg_complexity:.2f}/10")
|
|
703
|
+
output.append(f"- Average Maintainability Index: {avg_maintainability:.2f}/100")
|
|
704
|
+
|
|
705
|
+
# Most complex file
|
|
706
|
+
most_complex = max(results, key=lambda x: x.get('complexity_score', 0))
|
|
707
|
+
output.append(f"- Most Complex File: {most_complex['file_path']} (Score: {most_complex['complexity_score']:.2f})")
|
|
708
|
+
|
|
709
|
+
return "\n".join(output)
|
|
710
|
+
|
|
711
|
+
def _format_summary(self, results, total_files, analyzed_files):
|
|
712
|
+
"""
|
|
713
|
+
Formats results in summary format
|
|
714
|
+
|
|
715
|
+
Args:
|
|
716
|
+
results: List of analysis results
|
|
717
|
+
total_files: Total number of files found
|
|
718
|
+
analyzed_files: Number of files successfully analyzed
|
|
719
|
+
|
|
720
|
+
Returns:
|
|
721
|
+
Summary formatted analysis results
|
|
722
|
+
"""
|
|
723
|
+
output = [
|
|
724
|
+
"Code Complexity Analysis Report (Summary)",
|
|
725
|
+
"=" * 50,
|
|
726
|
+
f"Files Analyzed: {analyzed_files}/{total_files}",
|
|
727
|
+
""
|
|
728
|
+
]
|
|
729
|
+
|
|
730
|
+
# Calculate overall metrics
|
|
731
|
+
if results:
|
|
732
|
+
total_loc = sum(r.get('line_count', 0) for r in results)
|
|
733
|
+
total_functions = sum(r.get('function_count', 0) for r in results)
|
|
734
|
+
total_classes = sum(r.get('class_count', 0) for r in results)
|
|
735
|
+
|
|
736
|
+
avg_complexity = sum(r.get('complexity_score', 0) for r in results) / len(results)
|
|
737
|
+
avg_cyclomatic = sum(r.get('cyclomatic_complexity', 0) for r in results) / len(results)
|
|
738
|
+
avg_maintainability = sum(r.get('maintainability_index', 0) for r in results if 'maintainability_index' in r) / len([r for r in results if 'maintainability_index' in r]) if any('maintainability_index' in r for r in results) else 0
|
|
739
|
+
|
|
740
|
+
# Add overall metrics
|
|
741
|
+
output.append("Overall Metrics:")
|
|
742
|
+
output.append(f"- Total Lines of Code: {total_loc}")
|
|
743
|
+
output.append(f"- Total Functions/Methods: {total_functions}")
|
|
744
|
+
output.append(f"- Total Classes/Interfaces: {total_classes}")
|
|
745
|
+
output.append(f"- Average Complexity Score: {avg_complexity:.2f}/10")
|
|
746
|
+
output.append(f"- Average Cyclomatic Complexity: {avg_cyclomatic:.2f}")
|
|
747
|
+
output.append(f"- Average Maintainability Index: {avg_maintainability:.2f}/100")
|
|
748
|
+
output.append("")
|
|
749
|
+
|
|
750
|
+
# Distribution by complexity
|
|
751
|
+
low = len([r for r in results if r.get('complexity_score', 0) < 4])
|
|
752
|
+
medium = len([r for r in results if 4 <= r.get('complexity_score', 0) < 7])
|
|
753
|
+
high = len([r for r in results if r.get('complexity_score', 0) >= 7])
|
|
754
|
+
|
|
755
|
+
output.append("Complexity Distribution:")
|
|
756
|
+
output.append(f"- Low Complexity (0-3.99): {low} files ({low/len(results)*100:.1f}%)")
|
|
757
|
+
output.append(f"- Medium Complexity (4-6.99): {medium} files ({medium/len(results)*100:.1f}%)")
|
|
758
|
+
output.append(f"- High Complexity (7-10): {high} files ({high/len(results)*100:.1f}%)")
|
|
759
|
+
output.append("")
|
|
760
|
+
|
|
761
|
+
# Top 5 most complex files
|
|
762
|
+
output.append("Top 5 Most Complex Files:")
|
|
763
|
+
|
|
764
|
+
for i, result in enumerate(results[:5], 1):
|
|
765
|
+
score = result.get('complexity_score', 0)
|
|
766
|
+
cyclomatic = result.get('cyclomatic_complexity', 'N/A')
|
|
767
|
+
maintainability = result.get('maintainability_index', 'N/A')
|
|
768
|
+
|
|
769
|
+
if isinstance(maintainability, (int, float)):
|
|
770
|
+
maintainability = f"{maintainability:.2f}/100"
|
|
771
|
+
|
|
772
|
+
output.append(f"{i}. {result['file_path']}")
|
|
773
|
+
output.append(f" Score: {score:.2f}/10, Cyclomatic: {cyclomatic}, Maintainability: {maintainability}")
|
|
774
|
+
|
|
775
|
+
# Add specific recommendations for top files
|
|
776
|
+
if score >= 7:
|
|
777
|
+
output.append(" Recommendation: Consider refactoring into smaller components")
|
|
778
|
+
elif cyclomatic != 'N/A' and cyclomatic > 10:
|
|
779
|
+
output.append(" Recommendation: Reduce complexity by simplifying conditional logic")
|
|
780
|
+
|
|
781
|
+
# Add general recommendations
|
|
782
|
+
output.append("")
|
|
783
|
+
output.append("General Recommendations:")
|
|
784
|
+
|
|
785
|
+
if high > 0:
|
|
786
|
+
output.append("- Refactor highly complex files to improve maintainability")
|
|
787
|
+
|
|
788
|
+
if avg_maintainability < 65:
|
|
789
|
+
output.append("- Improve documentation and code structure to increase maintainability")
|
|
790
|
+
|
|
791
|
+
if avg_cyclomatic > 15:
|
|
792
|
+
output.append("- Reduce conditional logic complexity by extracting methods")
|
|
793
|
+
|
|
794
|
+
if high / len(results) > 0.3:
|
|
795
|
+
output.append("- Consider a code quality review process for complex components")
|
|
796
|
+
|
|
797
|
+
return "\n".join(output)
|