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

1from typing import Dict, List 

2from ..analyzer.base import AnalysisResult 

3 

4def format_analysis(result: AnalysisResult) -> str: 

5 """Format analysis results with tree structure.""" 

6 sections = [] 

7 

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 ]) 

21 

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 ]) 

32 

33 # NEW: Configuration Summary 

34 if 'configuration' in result.summary: 

35 sections.extend([ 

36 "PROJECT CONFIGURATION:", 

37 _format_configuration(result.summary['configuration']), 

38 "", 

39 ]) 

40 

41 # Key Insights 

42 if result.insights: 

43 sections.extend([ 

44 "KEY INSIGHTS:", 

45 *[f"- {insight}" for insight in result.insights], 

46 "", 

47 ]) 

48 

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 ]) 

62 

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 ]) 

70 

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 ]) 

78 

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 ]) 

85 

86 # File Analysis 

87 sections.append("PROJECT STRUCTURE AND CODE INSIGHTS:") 

88 

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) 

99 

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 ]) 

108 

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 ) 

117 

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 

123 

124 sections.extend(_format_file_analysis(filename, analysis)) 

125 sections.append("") # Empty line between files 

126 

127 return '\n'.join(sections) 

128 

129def _format_configuration(config: dict) -> str: 

130 """Format configuration information.""" 

131 config_lines = [] 

132 

133 for config_file, info in config.items(): 

134 if info and 'error' not in info: 

135 config_lines.append(f" {config_file}:") 

136 

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") 

144 

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']}") 

152 

153 elif config_file == 'README.md': 

154 config_lines.append(f" Summary: {info['summary'][:100]}...") 

155 

156 elif info and 'error' in info: 

157 config_lines.append(f" {config_file}: {info['error']}") 

158 

159 return '\n'.join(config_lines) if config_lines else " No configuration files found" 

160 

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', {}) 

165 

166 # Basic metrics 

167 sections.append(f" Lines: {metrics.get('loc', 0)}") 

168 if 'complexity' in metrics: 

169 sections.append(f" Complexity: {metrics['complexity']}") 

170 

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']}") 

179 

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)) 

187 

188 # Format imports 

189 if analysis.get('imports'): 

190 sections.append("\n IMPORTS:") 

191 sections.extend(f" {imp}" for imp in sorted(analysis['imports'])) 

192 

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']}") 

198 

199 return sections 

200 

201def _format_python_file(analysis: dict) -> List[str]: 

202 """Format Python-specific file information with better method grouping.""" 

203 sections = [] 

204 

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]}") 

215 

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 = [] 

225 

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']) 

235 

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)}") 

248 

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") 

275 

276 return sections 

277 

278def _format_sql_file(analysis: dict) -> List[str]: 

279 """Format SQL-specific file information with enhanced object handling.""" 

280 sections = [] 

281 

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 

290 

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)) 

298 

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) 

310 

311 # Format dependencies 

312 if analysis.get('dependencies'): 

313 sections.append("\n DEPENDENCIES:") 

314 sections.extend(f" {dep}" for dep in sorted(analysis['dependencies'])) 

315 

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']}") 

321 

322 return sections 

323 

324def _format_js_file(analysis: dict) -> List[str]: 

325 """Format JavaScript-specific file information with enhanced data.""" 

326 sections = [] 

327 

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'])) 

332 

333 if analysis.get('exports'): 

334 sections.append('\n EXPORTS:') 

335 sections.extend(f' {exp}' for exp in sorted(analysis['exports'])) 

336 

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"]}') 

344 

345 # Enhanced: React Hooks 

346 if analysis.get('hooks'): 

347 sections.append('\n REACT HOOKS:') 

348 built_in_hooks = [] 

349 custom_hooks = [] 

350 

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) 

357 

358 if built_in_hooks: 

359 sections.append(' Built-in hooks:') 

360 sections.extend(f' {hook}' for hook in built_in_hooks) 

361 

362 if custom_hooks: 

363 sections.append(' Custom hooks:') 

364 sections.extend(f' {hook}' for hook in custom_hooks) 

365 

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"]}') 

372 

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"]}') 

378 

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']}") 

389 

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"])}') 

401 

402 return sections 

403 

404def _format_todo(todo: dict) -> str: 

405 """Format a TODO entry.""" 

406 return f"- [{todo['priority']}] {todo['file']}: {todo['text']}"