Coverage for src\llm_code_lens\formatters\llm.py: 92%
178 statements
« prev ^ index » next coverage.py v7.6.1, created at 2025-01-12 10:23 +0200
« prev ^ index » next coverage.py v7.6.1, created at 2025-01-12 10:23 +0200
1from typing import Dict, List
2from ..analyzer.base import AnalysisResult
4def format_analysis(result: AnalysisResult) -> str:
5 """Format analysis results in an LLM-friendly text format."""
6 sections = []
8 # Project Overview
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 # Key Insights
23 if result.insights:
24 sections.extend([
25 "KEY INSIGHTS:",
26 *[f"- {insight}" for insight in result.insights],
27 "",
28 ])
30 # Code Metrics
31 sections.extend([
32 "CODE METRICS:",
33 f"Functions: {result.summary['code_metrics']['functions']['count']} "
34 f"({result.summary['code_metrics']['functions']['with_docs']} documented, "
35 f"{result.summary['code_metrics']['functions']['complex']} complex)",
36 f"Classes: {result.summary['code_metrics']['classes']['count']} "
37 f"({result.summary['code_metrics']['classes']['with_docs']} documented)",
38 f"Documentation coverage: {result.summary['maintenance']['doc_coverage']:.1f}%",
39 f"Total imports: {result.summary['code_metrics']['imports']['count']} "
40 f"({len(result.summary['code_metrics']['imports']['unique'])} unique)",
41 "",
42 ])
44 # Maintenance Info
45 if result.summary['maintenance']['todos']:
46 sections.extend([
47 "TODOS:",
48 *[_format_todo(todo) for todo in result.summary['maintenance']['todos']],
49 "",
50 ])
52 # Structure Info
53 if result.summary['structure']['entry_points']:
54 sections.extend([
55 "ENTRY POINTS:",
56 *[f"- {entry}" for entry in result.summary['structure']['entry_points']],
57 "",
58 ])
60 if result.summary['structure']['core_files']:
61 sections.extend([
62 "CORE FILES:",
63 *[f"- {file}" for file in result.summary['structure']['core_files']],
64 "",
65 ])
67 # File Analysis
68 sections.append("PROJECT STRUCTURE AND CODE INSIGHTS:")
70 # Group files by directory
71 by_directory = {}
72 total_by_dir = {}
73 for file_path, analysis in result.files.items():
74 dir_path = '/'.join(file_path.split('\\')[:-1]) or '.'
75 if dir_path not in by_directory:
76 by_directory[dir_path] = {}
77 total_by_dir[dir_path] = 0
78 by_directory[dir_path][file_path.split('\\')[-1]] = analysis
79 total_by_dir[dir_path] += analysis.get('metrics', {}).get('loc', 0)
81 # Format each directory
82 for dir_path, files in sorted(by_directory.items()):
83 sections.extend([
84 "", # Empty line before directory
85 "=" * 80, # Separator line
86 f"{dir_path}/ ({total_by_dir[dir_path]} lines)",
87 "=" * 80,
88 ])
90 # Sort files by importance (non-empty before empty)
91 sorted_files = sorted(
92 files.items(),
93 key=lambda x: (
94 x[1].get('metrics', {}).get('loc', 0) == 0,
95 x[0]
96 )
97 )
99 for filename, analysis in sorted_files:
100 # Skip empty files or show them in compact form
101 if analysis.get('metrics', {}).get('loc', 0) == 0:
102 sections.append(f" {filename} (empty)")
103 continue
105 sections.extend(_format_file_analysis(filename, analysis))
106 sections.append("") # Empty line between files
108 return '\n'.join(sections)
110def _format_file_analysis(filename: str, analysis: dict) -> List[str]:
111 """Format file analysis with improved error handling."""
112 sections = [f" {filename}"]
113 metrics = analysis.get('metrics', {})
115 # Basic metrics
116 sections.append(f" Lines: {metrics.get('loc', 0)}")
117 if 'complexity' in metrics:
118 sections.append(f" Complexity: {metrics['complexity']}")
120 # Handle errors with standardized format
121 if analysis.get('errors'):
122 sections.append("\n ERRORS:")
123 for error in analysis['errors']:
124 if 'line' in error:
125 sections.append(f" Line {error['line']}: {error['text']}")
126 else:
127 sections.append(f" {error['type'].replace('_', ' ').title()}: {error['text']}")
129 # Type-specific information
130 if analysis['type'] == 'python':
131 sections.extend(_format_python_file(analysis))
132 elif analysis['type'] == 'sql':
133 sections.extend(_format_sql_file(analysis))
134 elif analysis['type'] == 'javascript':
135 sections.extend(_format_js_file(analysis))
137 # Format imports
138 if analysis.get('imports'):
139 sections.append("\n IMPORTS:")
140 sections.extend(f" {imp}" for imp in sorted(analysis['imports']))
142 # Format TODOs
143 if analysis.get('todos'):
144 sections.append("\n TODOS:")
145 for todo in sorted(analysis['todos'], key=lambda x: x['line']):
146 sections.append(f" Line {todo['line']}: {todo['text']}")
148 return sections
150def _format_python_file(analysis: dict) -> List[str]:
151 """Format Python-specific file information with better method grouping."""
152 sections = []
154 if analysis.get('classes'):
155 sections.append('\nCLASSES:')
156 for cls in sorted(analysis['classes'], key=lambda x: x.get('line_number', 0)):
157 sections.append(f" {cls['name']}:")
158 if 'line_number' in cls:
159 sections.append(f" Line: {cls['line_number']}")
160 if cls.get('bases'):
161 sections.append(f" Inherits: {', '.join(cls['bases'])}")
162 if cls.get('docstring'):
163 sections.append(f" Doc: {cls['docstring'].split(chr(10))[0]}")
165 # Handle different method types
166 if cls.get('methods'):
167 methods = cls['methods']
168 if isinstance(methods[0], dict):
169 # Group methods by type
170 instance_methods = []
171 class_methods = []
172 static_methods = []
173 property_methods = []
175 for method in methods:
176 if method.get('type') == 'class' or method.get('is_classmethod'):
177 class_methods.append(method['name'])
178 elif method.get('type') == 'static' or method.get('is_staticmethod'):
179 static_methods.append(method['name'])
180 elif method.get('type') == 'property' or method.get('is_property'):
181 property_methods.append(method['name'])
182 else:
183 instance_methods.append(method['name'])
185 # Add each method type if present
186 if instance_methods:
187 sections.append(f" Instance methods: {', '.join(instance_methods)}")
188 if class_methods:
189 sections.append(f" Class methods: {', '.join(class_methods)}")
190 if static_methods:
191 sections.append(f" Static methods: {', '.join(static_methods)}")
192 if property_methods:
193 sections.append(f" Properties: {', '.join(property_methods)}")
194 else:
195 # Handle simple string method list
196 sections.append(f" Methods: {', '.join(methods)}")
198 if analysis.get('functions'):
199 sections.append('\nFUNCTIONS:')
200 for func in sorted(analysis['functions'], key=lambda x: x.get('line_number', 0)):
201 sections.append(f" {func['name']}:")
202 if 'line_number' in func:
203 sections.append(f" Line: {func['line_number']}")
204 if func.get('args'):
205 # Handle both string and dict arguments
206 args_list = []
207 for arg in func['args']:
208 if isinstance(arg, dict):
209 arg_str = f"{arg['name']}: {arg['type']}" if 'type' in arg else arg['name']
210 args_list.append(arg_str)
211 else:
212 args_list.append(arg)
213 sections.append(f" Args: {', '.join(args_list)}")
214 if func.get('return_type'):
215 sections.append(f" Returns: {func['return_type']}")
216 if func.get('docstring'):
217 sections.append(f" Doc: {func['docstring'].split(chr(10))[0]}")
218 if func.get('decorators'):
219 sections.append(f" Decorators: {', '.join(func['decorators'])}")
220 if func.get('complexity'):
221 sections.append(f" Complexity: {func['complexity']}")
222 if func.get('is_async'):
223 sections.append(" Async: Yes")
225 return sections
228def _format_sql_file(analysis: dict) -> List[str]:
229 """Format SQL-specific file information with enhanced object handling."""
230 sections = []
232 def format_metrics(obj: Dict) -> List[str]:
233 """Helper to format metrics consistently."""
234 result = []
235 if 'lines' in obj.get('metrics', {}):
236 result.append(f" Lines: {obj['metrics']['lines']}")
237 if 'complexity' in obj.get('metrics', {}):
238 result.append(f" Complexity: {obj['metrics']['complexity']}")
239 return result
241 # Format SQL objects
242 for obj in sorted(analysis.get('objects', []), key=lambda x: x['name']):
243 sections.extend([
244 f"\n {obj['type'].upper()}:",
245 f" Name: {obj['name']}"
246 ])
247 sections.extend(format_metrics(obj))
249 # Format parameters with improved handling
250 if analysis.get('parameters'):
251 sections.append("\n PARAMETERS:")
252 for param in sorted(analysis['parameters'], key=lambda x: x['name']):
253 param_text = f" @{param['name']} ({param['data_type']}"
254 if 'default' in param:
255 param_text += f", default={param['default']}"
256 param_text += ")"
257 if 'description' in param:
258 param_text += f" -- {param['description']}"
259 sections.append(param_text)
261 # Format dependencies
262 if analysis.get('dependencies'):
263 sections.append("\n DEPENDENCIES:")
264 sections.extend(f" {dep}" for dep in sorted(analysis['dependencies']))
266 # Format comments
267 if analysis.get('comments'):
268 sections.append("\n COMMENTS:")
269 for comment in sorted(analysis['comments'], key=lambda x: x['line']):
270 sections.append(f" Line {comment['line']}: {comment['text']}")
272 return sections
276def _format_js_file(analysis: dict) -> List[str]:
277 """Format JavaScript-specific file information."""
278 sections = []
280 if analysis.get('imports'):
281 sections.append('\n IMPORTS:')
282 sections.extend(f' {imp}' for imp in sorted(analysis['imports']))
284 if analysis.get('exports'):
285 sections.append('\n EXPORTS:')
286 sections.extend(f' {exp}' for exp in sorted(analysis['exports']))
288 if analysis.get('classes'):
289 sections.append('\n CLASSES:')
290 for cls in analysis['classes']:
291 sections.extend([
292 f' {cls["name"]}:'
293 ])
294 if 'line_number' in cls:
295 sections.extend([f' Line: {cls["line_number"]}'])
296 if cls.get('extends'):
297 sections.extend([f' Extends: {cls["extends"]}'])
298 if cls.get('methods'):
299 sections.extend([f' Methods: {", ".join(cls["methods"])}'])
301 if analysis.get('functions'):
302 sections.append("\n FUNCTIONS:")
303 for func in analysis['functions']:
304 sections.extend([
305 f" {func['name']}:",
306 f" Line: {func['line_number']}"
307 ])
308 if func.get('params'):
309 sections.append(f" Parameters: {', '.join(func['params'])}")
311 return sections
313def _format_todo(todo: dict) -> str:
314 """Format a TODO entry."""
315 return f"- [{todo['priority']}] {todo['file']}: {todo['text']}"