Coverage for src\llm_code_lens\formatters\llm.py: 4%
239 statements
« prev ^ index » next coverage.py v7.7.0, created at 2025-05-25 12:07 +0300
« prev ^ index » next coverage.py v7.7.0, created at 2025-05-25 12:07 +0300
1from typing import Dict, List
2from ..analyzer.base import AnalysisResult
4def format_analysis(result: AnalysisResult) -> str:
5 """Format analysis results with tree structure."""
6 sections = []
8 # Project Overview (existing)
9 sections.extend([
10 "CODEBASE SUMMARY:",
11 f"This project contains {result.summary['project_stats']['total_files']} files:",
12 "File types: " + ", ".join(
13 f"{ext}: {count}"
14 for ext, count in result.summary['project_stats']['by_type'].items()
15 ),
16 f"Total lines of code: {result.summary['project_stats']['lines_of_code']}",
17 f"Average file size: {result.summary['project_stats']['avg_file_size']:.1f} lines",
18 f"Overall complexity: {sum(f.get('metrics', {}).get('complexity', 0) for f in result.files.values())}",
19 "",
20 ])
22 # NEW: Project Tree Structure
23 if 'project_tree' in result.summary.get('structure', {}):
24 sections.extend([
25 "PROJECT STRUCTURE:",
26 result.summary['structure']['project_tree'],
27 "",
28 "STRUCTURE SUMMARY:",
29 result.summary['structure']['tree_summary'],
30 "",
31 ])
33 # NEW: Configuration Summary
34 if 'configuration' in result.summary:
35 sections.extend([
36 "PROJECT CONFIGURATION:",
37 _format_configuration(result.summary['configuration']),
38 "",
39 ])
41 # Key Insights
42 if result.insights:
43 sections.extend([
44 "KEY INSIGHTS:",
45 *[f"- {insight}" for insight in result.insights],
46 "",
47 ])
49 # Code Metrics
50 sections.extend([
51 "CODE METRICS:",
52 f"Functions: {result.summary['code_metrics']['functions']['count']} "
53 f"({result.summary['code_metrics']['functions']['with_docs']} documented, "
54 f"{result.summary['code_metrics']['functions']['complex']} complex)",
55 f"Classes: {result.summary['code_metrics']['classes']['count']} "
56 f"({result.summary['code_metrics']['classes']['with_docs']} documented)",
57 f"Documentation coverage: {result.summary['maintenance']['doc_coverage']:.1f}%",
58 f"Total imports: {result.summary['code_metrics']['imports']['count']} "
59 f"({len(result.summary['code_metrics']['imports']['unique'])} unique)",
60 "",
61 ])
63 # Maintenance Info
64 if result.summary['maintenance']['todos']:
65 sections.extend([
66 "TODOS:",
67 *[_format_todo(todo) for todo in result.summary['maintenance']['todos']],
68 "",
69 ])
71 # Structure Info
72 if result.summary['structure']['entry_points']:
73 sections.extend([
74 "ENTRY POINTS:",
75 *[f"- {entry}" for entry in result.summary['structure']['entry_points']],
76 "",
77 ])
79 if result.summary['structure']['core_files']:
80 sections.extend([
81 "CORE FILES:",
82 *[f"- {file}" for file in result.summary['structure']['core_files']],
83 "",
84 ])
86 # File Analysis
87 sections.append("PROJECT STRUCTURE AND CODE INSIGHTS:")
89 # Group files by directory
90 by_directory = {}
91 total_by_dir = {}
92 for file_path, analysis in result.files.items():
93 dir_path = '/'.join(file_path.split('\\')[:-1]) or '.'
94 if dir_path not in by_directory:
95 by_directory[dir_path] = {}
96 total_by_dir[dir_path] = 0
97 by_directory[dir_path][file_path.split('\\')[-1]] = analysis
98 total_by_dir[dir_path] += analysis.get('metrics', {}).get('loc', 0)
100 # Format each directory
101 for dir_path, files in sorted(by_directory.items()):
102 sections.extend([
103 "", # Empty line before directory
104 "=" * 80, # Separator line
105 f"{dir_path}/ ({total_by_dir[dir_path]} lines)",
106 "=" * 80,
107 ])
109 # Sort files by importance (non-empty before empty)
110 sorted_files = sorted(
111 files.items(),
112 key=lambda x: (
113 x[1].get('metrics', {}).get('loc', 0) == 0,
114 x[0]
115 )
116 )
118 for filename, analysis in sorted_files:
119 # Skip empty files or show them in compact form
120 if analysis.get('metrics', {}).get('loc', 0) == 0:
121 sections.append(f" {filename} (empty)")
122 continue
124 sections.extend(_format_file_analysis(filename, analysis))
125 sections.append("") # Empty line between files
127 return '\n'.join(sections)
129def _format_configuration(config: dict) -> str:
130 """Format configuration information."""
131 config_lines = []
133 for config_file, info in config.items():
134 if info and 'error' not in info:
135 config_lines.append(f" {config_file}:")
137 if config_file == 'package.json':
138 if info.get('framework_indicators'):
139 config_lines.append(f" Frameworks: {', '.join(info['framework_indicators'])}")
140 if info.get('scripts'):
141 config_lines.append(f" Scripts: {', '.join(info['scripts'])}")
142 if info.get('dependencies'):
143 config_lines.append(f" Dependencies: {len(info['dependencies'])} packages")
145 elif config_file == 'tsconfig.json':
146 if info.get('target'):
147 config_lines.append(f" Target: {info['target']}")
148 if info.get('jsx'):
149 config_lines.append(f" JSX: {info['jsx']}")
150 if info.get('strict'):
151 config_lines.append(f" Strict mode: {info['strict']}")
153 elif config_file == 'README.md':
154 config_lines.append(f" Summary: {info['summary'][:100]}...")
156 elif info and 'error' in info:
157 config_lines.append(f" {config_file}: {info['error']}")
159 return '\n'.join(config_lines) if config_lines else " No configuration files found"
161def _format_file_analysis(filename: str, analysis: dict) -> List[str]:
162 """Format file analysis with improved error handling."""
163 sections = [f" {filename}"]
164 metrics = analysis.get('metrics', {})
166 # Basic metrics
167 sections.append(f" Lines: {metrics.get('loc', 0)}")
168 if 'complexity' in metrics:
169 sections.append(f" Complexity: {metrics['complexity']}")
171 # Handle errors with standardized format
172 if analysis.get('errors'):
173 sections.append("\n ERRORS:")
174 for error in analysis['errors']:
175 if 'line' in error:
176 sections.append(f" Line {error['line']}: {error['text']}")
177 else:
178 sections.append(f" {error['type'].replace('_', ' ').title()}: {error['text']}")
180 # Type-specific information
181 if analysis['type'] == 'python':
182 sections.extend(_format_python_file(analysis))
183 elif analysis['type'] == 'sql':
184 sections.extend(_format_sql_file(analysis))
185 elif analysis['type'] == 'javascript':
186 sections.extend(_format_js_file(analysis))
188 # Format imports
189 if analysis.get('imports'):
190 sections.append("\n IMPORTS:")
191 sections.extend(f" {imp}" for imp in sorted(analysis['imports']))
193 # Format TODOs
194 if analysis.get('todos'):
195 sections.append("\n TODOS:")
196 for todo in sorted(analysis['todos'], key=lambda x: x['line']):
197 sections.append(f" Line {todo['line']}: {todo['text']}")
199 return sections
201def _format_python_file(analysis: dict) -> List[str]:
202 """Format Python-specific file information with better method grouping."""
203 sections = []
205 if analysis.get('classes'):
206 sections.append('\nCLASSES:')
207 for cls in sorted(analysis['classes'], key=lambda x: x.get('line_number', 0)):
208 sections.append(f" {cls['name']}:")
209 if 'line_number' in cls:
210 sections.append(f" Line: {cls['line_number']}")
211 if cls.get('bases'):
212 sections.append(f" Inherits: {', '.join(cls['bases'])}")
213 if cls.get('docstring'):
214 sections.append(f" Doc: {cls['docstring'].split(chr(10))[0]}")
216 # Handle different method types
217 if cls.get('methods'):
218 methods = cls['methods']
219 if isinstance(methods[0], dict):
220 # Group methods by type
221 instance_methods = []
222 class_methods = []
223 static_methods = []
224 property_methods = []
226 for method in methods:
227 if method.get('type') == 'class' or method.get('is_classmethod'):
228 class_methods.append(method['name'])
229 elif method.get('type') == 'static' or method.get('is_staticmethod'):
230 static_methods.append(method['name'])
231 elif method.get('type') == 'property' or method.get('is_property'):
232 property_methods.append(method['name'])
233 else:
234 instance_methods.append(method['name'])
236 # Add each method type if present
237 if instance_methods:
238 sections.append(f" Instance methods: {', '.join(instance_methods)}")
239 if class_methods:
240 sections.append(f" Class methods: {', '.join(class_methods)}")
241 if static_methods:
242 sections.append(f" Static methods: {', '.join(static_methods)}")
243 if property_methods:
244 sections.append(f" Properties: {', '.join(property_methods)}")
245 else:
246 # Handle simple string method list
247 sections.append(f" Methods: {', '.join(methods)}")
249 if analysis.get('functions'):
250 sections.append('\nFUNCTIONS:')
251 for func in sorted(analysis['functions'], key=lambda x: x.get('line_number', 0)):
252 sections.append(f" {func['name']}:")
253 if 'line_number' in func:
254 sections.append(f" Line: {func['line_number']}")
255 if func.get('args'):
256 # Handle both string and dict arguments
257 args_list = []
258 for arg in func['args']:
259 if isinstance(arg, dict):
260 arg_str = f"{arg['name']}: {arg['type']}" if 'type' in arg else arg['name']
261 args_list.append(arg_str)
262 else:
263 args_list.append(arg)
264 sections.append(f" Args: {', '.join(args_list)}")
265 if func.get('return_type'):
266 sections.append(f" Returns: {func['return_type']}")
267 if func.get('docstring'):
268 sections.append(f" Doc: {func['docstring'].split(chr(10))[0]}")
269 if func.get('decorators'):
270 sections.append(f" Decorators: {', '.join(func['decorators'])}")
271 if func.get('complexity'):
272 sections.append(f" Complexity: {func['complexity']}")
273 if func.get('is_async'):
274 sections.append(" Async: Yes")
276 return sections
278def _format_sql_file(analysis: dict) -> List[str]:
279 """Format SQL-specific file information with enhanced object handling."""
280 sections = []
282 def format_metrics(obj: Dict) -> List[str]:
283 """Helper to format metrics consistently."""
284 result = []
285 if 'lines' in obj.get('metrics', {}):
286 result.append(f" Lines: {obj['metrics']['lines']}")
287 if 'complexity' in obj.get('metrics', {}):
288 result.append(f" Complexity: {obj['metrics']['complexity']}")
289 return result
291 # Format SQL objects
292 for obj in sorted(analysis.get('objects', []), key=lambda x: x['name']):
293 sections.extend([
294 f"\n {obj['type'].upper()}:",
295 f" Name: {obj['name']}"
296 ])
297 sections.extend(format_metrics(obj))
299 # Format parameters with improved handling
300 if analysis.get('parameters'):
301 sections.append("\n PARAMETERS:")
302 for param in sorted(analysis['parameters'], key=lambda x: x['name']):
303 param_text = f" @{param['name']} ({param['data_type']}"
304 if 'default' in param:
305 param_text += f", default={param['default']}"
306 param_text += ")"
307 if 'description' in param:
308 param_text += f" -- {param['description']}"
309 sections.append(param_text)
311 # Format dependencies
312 if analysis.get('dependencies'):
313 sections.append("\n DEPENDENCIES:")
314 sections.extend(f" {dep}" for dep in sorted(analysis['dependencies']))
316 # Format comments
317 if analysis.get('comments'):
318 sections.append("\n COMMENTS:")
319 for comment in sorted(analysis['comments'], key=lambda x: x['line']):
320 sections.append(f" Line {comment['line']}: {comment['text']}")
322 return sections
324def _format_js_file(analysis: dict) -> List[str]:
325 """Format JavaScript-specific file information with enhanced data."""
326 sections = []
328 # Existing imports/exports
329 if analysis.get('imports'):
330 sections.append('\n IMPORTS:')
331 sections.extend(f' {imp}' for imp in sorted(analysis['imports']))
333 if analysis.get('exports'):
334 sections.append('\n EXPORTS:')
335 sections.extend(f' {exp}' for exp in sorted(analysis['exports']))
337 # Enhanced: React Components
338 if analysis.get('components'):
339 sections.append('\n REACT COMPONENTS:')
340 for comp in analysis['components']:
341 comp_type = comp.get('type', 'function')
342 jsx_status = ' (with JSX)' if comp.get('has_jsx') else ''
343 sections.append(f' {comp["name"]} ({comp_type}{jsx_status}) - Line {comp["line_number"]}')
345 # Enhanced: React Hooks
346 if analysis.get('hooks'):
347 sections.append('\n REACT HOOKS:')
348 built_in_hooks = []
349 custom_hooks = []
351 for hook in analysis['hooks']:
352 hook_info = f'{hook["name"]} - Line {hook["line_number"]}'
353 if hook.get('is_custom'):
354 custom_hooks.append(hook_info)
355 else:
356 built_in_hooks.append(hook_info)
358 if built_in_hooks:
359 sections.append(' Built-in hooks:')
360 sections.extend(f' {hook}' for hook in built_in_hooks)
362 if custom_hooks:
363 sections.append(' Custom hooks:')
364 sections.extend(f' {hook}' for hook in custom_hooks)
366 # Enhanced: TypeScript Interfaces
367 if analysis.get('interfaces'):
368 sections.append('\n INTERFACES:')
369 for interface in analysis['interfaces']:
370 extends_part = f' extends {interface["extends"]}' if interface.get('extends') else ''
371 sections.append(f' {interface["name"]}{extends_part} - Line {interface["line_number"]}')
373 # Enhanced: TypeScript Types
374 if analysis.get('types'):
375 sections.append('\n TYPE ALIASES:')
376 for type_def in analysis['types']:
377 sections.append(f' {type_def["name"]} = {type_def["definition"]} - Line {type_def["line_number"]}')
379 # Enhanced functions with more details
380 if analysis.get('functions'):
381 sections.append("\n FUNCTIONS:")
382 for func in analysis['functions']:
383 func_signature = f"{func['name']}({', '.join(func.get('params', []))})"
384 if func.get('return_type'):
385 func_signature += f": {func['return_type']}"
386 if func.get('is_async'):
387 func_signature = f"async {func_signature}"
388 sections.append(f" {func_signature} - Line {func['line_number']}")
390 # Existing classes with enhancements
391 if analysis.get('classes'):
392 sections.append('\n CLASSES:')
393 for cls in analysis['classes']:
394 sections.append(f' {cls["name"]}:')
395 if 'line_number' in cls:
396 sections.append(f' Line: {cls["line_number"]}')
397 if cls.get('extends'):
398 sections.append(f' Extends: {cls["extends"]}')
399 if cls.get('methods'):
400 sections.append(f' Methods: {", ".join(cls["methods"])}')
402 return sections
404def _format_todo(todo: dict) -> str:
405 """Format a TODO entry."""
406 return f"- [{todo['priority']}] {todo['file']}: {todo['text']}"