tree-sitter-analyzer 0.2.0__py3-none-any.whl → 0.3.0__py3-none-any.whl

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.

Potentially problematic release.


This version of tree-sitter-analyzer might be problematic. Click here for more details.

Files changed (78) hide show
  1. tree_sitter_analyzer/__init__.py +133 -121
  2. tree_sitter_analyzer/__main__.py +11 -12
  3. tree_sitter_analyzer/api.py +531 -539
  4. tree_sitter_analyzer/cli/__init__.py +39 -39
  5. tree_sitter_analyzer/cli/__main__.py +12 -13
  6. tree_sitter_analyzer/cli/commands/__init__.py +26 -27
  7. tree_sitter_analyzer/cli/commands/advanced_command.py +88 -88
  8. tree_sitter_analyzer/cli/commands/base_command.py +160 -155
  9. tree_sitter_analyzer/cli/commands/default_command.py +18 -19
  10. tree_sitter_analyzer/cli/commands/partial_read_command.py +141 -133
  11. tree_sitter_analyzer/cli/commands/query_command.py +81 -82
  12. tree_sitter_analyzer/cli/commands/structure_command.py +138 -121
  13. tree_sitter_analyzer/cli/commands/summary_command.py +101 -93
  14. tree_sitter_analyzer/cli/commands/table_command.py +232 -233
  15. tree_sitter_analyzer/cli/info_commands.py +120 -121
  16. tree_sitter_analyzer/cli_main.py +277 -276
  17. tree_sitter_analyzer/core/__init__.py +15 -20
  18. tree_sitter_analyzer/core/analysis_engine.py +591 -574
  19. tree_sitter_analyzer/core/cache_service.py +320 -330
  20. tree_sitter_analyzer/core/engine.py +557 -560
  21. tree_sitter_analyzer/core/parser.py +293 -288
  22. tree_sitter_analyzer/core/query.py +494 -502
  23. tree_sitter_analyzer/encoding_utils.py +458 -460
  24. tree_sitter_analyzer/exceptions.py +337 -340
  25. tree_sitter_analyzer/file_handler.py +217 -222
  26. tree_sitter_analyzer/formatters/__init__.py +1 -1
  27. tree_sitter_analyzer/formatters/base_formatter.py +167 -168
  28. tree_sitter_analyzer/formatters/formatter_factory.py +78 -74
  29. tree_sitter_analyzer/formatters/java_formatter.py +287 -270
  30. tree_sitter_analyzer/formatters/python_formatter.py +255 -235
  31. tree_sitter_analyzer/interfaces/__init__.py +9 -10
  32. tree_sitter_analyzer/interfaces/cli.py +528 -557
  33. tree_sitter_analyzer/interfaces/cli_adapter.py +322 -319
  34. tree_sitter_analyzer/interfaces/mcp_adapter.py +180 -170
  35. tree_sitter_analyzer/interfaces/mcp_server.py +405 -416
  36. tree_sitter_analyzer/java_analyzer.py +218 -219
  37. tree_sitter_analyzer/language_detector.py +398 -400
  38. tree_sitter_analyzer/language_loader.py +224 -228
  39. tree_sitter_analyzer/languages/__init__.py +10 -11
  40. tree_sitter_analyzer/languages/java_plugin.py +1129 -1113
  41. tree_sitter_analyzer/languages/python_plugin.py +737 -712
  42. tree_sitter_analyzer/mcp/__init__.py +31 -32
  43. tree_sitter_analyzer/mcp/resources/__init__.py +44 -47
  44. tree_sitter_analyzer/mcp/resources/code_file_resource.py +212 -213
  45. tree_sitter_analyzer/mcp/resources/project_stats_resource.py +560 -550
  46. tree_sitter_analyzer/mcp/server.py +333 -345
  47. tree_sitter_analyzer/mcp/tools/__init__.py +30 -31
  48. tree_sitter_analyzer/mcp/tools/analyze_scale_tool.py +621 -557
  49. tree_sitter_analyzer/mcp/tools/analyze_scale_tool_cli_compatible.py +242 -245
  50. tree_sitter_analyzer/mcp/tools/base_tool.py +54 -55
  51. tree_sitter_analyzer/mcp/tools/read_partial_tool.py +300 -302
  52. tree_sitter_analyzer/mcp/tools/table_format_tool.py +362 -359
  53. tree_sitter_analyzer/mcp/tools/universal_analyze_tool.py +543 -476
  54. tree_sitter_analyzer/mcp/utils/__init__.py +105 -106
  55. tree_sitter_analyzer/mcp/utils/error_handler.py +549 -549
  56. tree_sitter_analyzer/models.py +470 -481
  57. tree_sitter_analyzer/output_manager.py +261 -264
  58. tree_sitter_analyzer/plugins/__init__.py +333 -334
  59. tree_sitter_analyzer/plugins/base.py +477 -446
  60. tree_sitter_analyzer/plugins/java_plugin.py +608 -625
  61. tree_sitter_analyzer/plugins/javascript_plugin.py +446 -439
  62. tree_sitter_analyzer/plugins/manager.py +362 -355
  63. tree_sitter_analyzer/plugins/plugin_loader.py +85 -83
  64. tree_sitter_analyzer/plugins/python_plugin.py +606 -598
  65. tree_sitter_analyzer/plugins/registry.py +374 -366
  66. tree_sitter_analyzer/queries/__init__.py +26 -27
  67. tree_sitter_analyzer/queries/java.py +391 -394
  68. tree_sitter_analyzer/queries/javascript.py +148 -149
  69. tree_sitter_analyzer/queries/python.py +285 -286
  70. tree_sitter_analyzer/queries/typescript.py +229 -230
  71. tree_sitter_analyzer/query_loader.py +254 -260
  72. tree_sitter_analyzer/table_formatter.py +468 -448
  73. tree_sitter_analyzer/utils.py +277 -277
  74. {tree_sitter_analyzer-0.2.0.dist-info → tree_sitter_analyzer-0.3.0.dist-info}/METADATA +21 -6
  75. tree_sitter_analyzer-0.3.0.dist-info/RECORD +77 -0
  76. tree_sitter_analyzer-0.2.0.dist-info/RECORD +0 -77
  77. {tree_sitter_analyzer-0.2.0.dist-info → tree_sitter_analyzer-0.3.0.dist-info}/WHEEL +0 -0
  78. {tree_sitter_analyzer-0.2.0.dist-info → tree_sitter_analyzer-0.3.0.dist-info}/entry_points.txt +0 -0
@@ -1,476 +1,543 @@
1
- #!/usr/bin/env python3
2
- # -*- coding: utf-8 -*-
3
- """
4
- Universal Code Analysis Tool for MCP
5
-
6
- This tool provides universal code analysis capabilities for multiple programming
7
- languages using the existing language detection and analysis infrastructure.
8
- """
9
-
10
- import logging
11
- from pathlib import Path
12
- from typing import Any, Dict, List, Optional
13
-
14
- from tree_sitter_analyzer.core.analysis_engine import (
15
- AnalysisRequest,
16
- get_analysis_engine,
17
- )
18
- from ...core.analysis_engine import get_analysis_engine, AnalysisRequest
19
- from ...language_detector import detect_language_from_file, is_language_supported
20
- from ..utils.error_handler import handle_mcp_errors
21
- from ..utils import get_performance_monitor
22
-
23
- logger = logging.getLogger(__name__)
24
-
25
-
26
- class UniversalAnalyzeTool:
27
- """
28
- Universal code analysis tool for multiple programming languages
29
-
30
- This tool automatically detects the programming language and applies
31
- the appropriate analyzer to provide comprehensive code analysis.
32
- """
33
-
34
- def __init__(self) -> None:
35
- """Initialize the universal analysis tool"""
36
- # Use unified analysis engine instead of deprecated AdvancedAnalyzer
37
- self.analysis_engine = get_analysis_engine()
38
-
39
- def get_tool_definition(self) -> Dict[str, Any]:
40
- """
41
- Get MCP tool definition for universal code analysis
42
-
43
- Returns:
44
- Tool definition dictionary
45
- """
46
- return {
47
- "name": "analyze_code_universal",
48
- "description": "Universal code analysis for multiple programming languages with automatic language detection",
49
- "inputSchema": {
50
- "type": "object",
51
- "properties": {
52
- "file_path": {
53
- "type": "string",
54
- "description": "Path to the code file to analyze",
55
- },
56
- "language": {
57
- "type": "string",
58
- "description": "Programming language (optional, auto-detected if not specified)",
59
- },
60
- "analysis_type": {
61
- "type": "string",
62
- "enum": ["basic", "detailed", "structure", "metrics"],
63
- "description": "Type of analysis to perform",
64
- "default": "basic",
65
- },
66
- "include_ast": {
67
- "type": "boolean",
68
- "description": "Include AST information in the analysis",
69
- "default": False,
70
- },
71
- "include_queries": {
72
- "type": "boolean",
73
- "description": "Include available query information",
74
- "default": False,
75
- },
76
- },
77
- "required": ["file_path"],
78
- "additionalProperties": False,
79
- },
80
- }
81
-
82
-
83
- @handle_mcp_errors("universal_analyze")
84
- async def execute(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
85
- """
86
- Execute universal code analysis
87
-
88
- Args:
89
- arguments: Tool arguments containing file_path and optional parameters
90
-
91
- Returns:
92
- Dictionary containing analysis results
93
-
94
- Raises:
95
- ValueError: If required arguments are missing or invalid
96
- FileNotFoundError: If the specified file doesn't exist
97
- """
98
- # Validate required arguments
99
- if "file_path" not in arguments:
100
- raise ValueError("file_path is required")
101
-
102
- file_path = arguments["file_path"]
103
- language = arguments.get("language")
104
- analysis_type = arguments.get("analysis_type", "basic")
105
- include_ast = arguments.get("include_ast", False)
106
- include_queries = arguments.get("include_queries", False)
107
-
108
- # Validate file exists
109
- if not Path(file_path).exists():
110
- raise FileNotFoundError(f"File not found: {file_path}")
111
-
112
- # Detect language if not specified
113
- if not language:
114
- language = detect_language_from_file(file_path)
115
- if language == "unknown":
116
- raise ValueError(f"Could not detect language for file: {file_path}")
117
-
118
- # Check if language is supported
119
- if not is_language_supported(language):
120
- raise ValueError(f"Language '{language}' is not supported by tree-sitter")
121
-
122
- # Validate analysis_type
123
- valid_analysis_types = ["basic", "detailed", "structure", "metrics"]
124
- if analysis_type not in valid_analysis_types:
125
- raise ValueError(
126
- f"Invalid analysis_type '{analysis_type}'. Valid types: {', '.join(valid_analysis_types)}"
127
- )
128
-
129
- logger.info(
130
- f"Analyzing {file_path} (language: {language}, type: {analysis_type})"
131
- )
132
-
133
- try:
134
- monitor = get_performance_monitor()
135
- with monitor.measure_operation("universal_analyze"):
136
- # Get appropriate analyzer
137
- if language == "java":
138
- # Use advanced analyzer for Java
139
- result = await self._analyze_with_advanced_analyzer(
140
- file_path, language, analysis_type, include_ast
141
- )
142
- else:
143
- # Use universal analyzer for other languages
144
- result = await self._analyze_with_universal_analyzer(
145
- file_path, language, analysis_type, include_ast
146
- )
147
-
148
- # Add query information if requested
149
- if include_queries:
150
- result["available_queries"] = await self._get_available_queries(
151
- language
152
- )
153
-
154
- logger.info(f"Successfully analyzed {file_path}")
155
- return result
156
-
157
- except Exception as e:
158
- logger.error(f"Error analyzing {file_path}: {e}")
159
- raise
160
-
161
- async def _analyze_with_advanced_analyzer(
162
- self, file_path: str, language: str, analysis_type: str, include_ast: bool
163
- ) -> Dict[str, Any]:
164
- """
165
- Analyze using the advanced analyzer (Java-specific)
166
-
167
- Args:
168
- file_path: Path to the file to analyze
169
- language: Programming language
170
- analysis_type: Type of analysis to perform
171
- include_ast: Whether to include AST information
172
-
173
- Returns:
174
- Analysis results dictionary
175
- """
176
- # Use unified analysis engine instead of deprecated advanced_analyzer
177
- request = AnalysisRequest(
178
- file_path=file_path,
179
- language=language,
180
- include_complexity=True,
181
- include_details=True
182
- )
183
- analysis_result = await self.analysis_engine.analyze(request)
184
-
185
- if analysis_result is None:
186
- raise RuntimeError(f"Failed to analyze file: {file_path}")
187
-
188
- # Build base result
189
- result: Dict[str, Any] = {
190
- "file_path": file_path,
191
- "language": language,
192
- "analyzer_type": "advanced",
193
- "analysis_type": analysis_type,
194
- }
195
-
196
- if analysis_type == "basic":
197
- result.update(self._extract_basic_metrics(analysis_result))
198
- elif analysis_type == "detailed":
199
- result.update(self._extract_detailed_metrics(analysis_result))
200
- elif analysis_type == "structure":
201
- result.update(self._extract_structure_info(analysis_result))
202
- elif analysis_type == "metrics":
203
- result.update(self._extract_comprehensive_metrics(analysis_result))
204
-
205
- if include_ast:
206
- result["ast_info"] = {
207
- "node_count": getattr(
208
- analysis_result, "line_count", 0
209
- ), # Approximation
210
- "depth": 0, # Advanced analyzer doesn't provide this, use 0 instead of string
211
- }
212
-
213
- return result
214
-
215
- async def _analyze_with_universal_analyzer(
216
- self, file_path: str, language: str, analysis_type: str, include_ast: bool
217
- ) -> Dict[str, Any]:
218
- """
219
- Analyze using the universal analyzer
220
-
221
- Args:
222
- file_path: Path to the file to analyze
223
- language: Programming language
224
- analysis_type: Type of analysis to perform
225
- include_ast: Whether to include AST information
226
-
227
- Returns:
228
- Analysis results dictionary
229
- """
230
- request = AnalysisRequest(
231
- file_path=file_path,
232
- language=language,
233
- include_details=(analysis_type == "detailed"),
234
- )
235
- analysis_result = await self.analysis_engine.analyze(request)
236
-
237
- if not analysis_result or not analysis_result.success:
238
- error_message = (
239
- analysis_result.error_message if analysis_result else "Unknown error"
240
- )
241
- raise RuntimeError(
242
- f"Failed to analyze file: {file_path} - {error_message}"
243
- )
244
-
245
- # Convert AnalysisResult to dictionary for consistent processing
246
- analysis_dict = analysis_result.to_dict()
247
-
248
- # Build base result
249
- result: Dict[str, Any] = {
250
- "file_path": file_path,
251
- "language": language,
252
- "analyzer_type": "universal",
253
- "analysis_type": analysis_type,
254
- }
255
-
256
- if analysis_type == "basic":
257
- result.update(self._extract_universal_basic_metrics(analysis_dict))
258
- elif analysis_type == "detailed":
259
- result.update(self._extract_universal_detailed_metrics(analysis_dict))
260
- elif analysis_type == "structure":
261
- result.update(self._extract_universal_structure_info(analysis_dict))
262
- elif analysis_type == "metrics":
263
- result.update(
264
- self._extract_universal_comprehensive_metrics(analysis_dict)
265
- )
266
-
267
- if include_ast:
268
- result["ast_info"] = analysis_dict.get("ast_info", {})
269
-
270
- return result
271
-
272
- def _extract_basic_metrics(self, analysis_result: Any) -> Dict[str, Any]:
273
- """Extract basic metrics from advanced analyzer result"""
274
- stats = analysis_result.get_statistics()
275
-
276
- return {
277
- "metrics": {
278
- "lines_total": analysis_result.line_count,
279
- "lines_code": stats.get("lines_of_code", 0),
280
- "lines_comment": stats.get("comment_lines", 0),
281
- "lines_blank": stats.get("blank_lines", 0),
282
- "elements": {
283
- "classes": len([e for e in analysis_result.elements if e.__class__.__name__ == 'Class']),
284
- "methods": len([e for e in analysis_result.elements if e.__class__.__name__ == 'Function']),
285
- "fields": len([e for e in analysis_result.elements if e.__class__.__name__ == 'Variable']),
286
- "imports": len([e for e in analysis_result.elements if e.__class__.__name__ == 'Import']),
287
- "annotations": len(getattr(analysis_result, "annotations", [])),
288
- },
289
- }
290
- }
291
-
292
- def _extract_detailed_metrics(self, analysis_result: Any) -> Dict[str, Any]:
293
- """Extract detailed metrics from advanced analyzer result"""
294
- basic = self._extract_basic_metrics(analysis_result)
295
-
296
- # Add complexity metrics
297
- methods = [e for e in analysis_result.elements if e.__class__.__name__ == 'Function']
298
- total_complexity = sum(
299
- getattr(method, 'complexity_score', 0) or 0 for method in methods
300
- )
301
-
302
- basic["metrics"]["complexity"] = {
303
- "total": total_complexity,
304
- "average": (
305
- total_complexity / len(methods)
306
- if methods
307
- else 0
308
- ),
309
- "max": max(
310
- (getattr(method, 'complexity_score', 0) or 0 for method in methods),
311
- default=0,
312
- ),
313
- }
314
-
315
- return basic
316
-
317
- def _extract_structure_info(self, analysis_result: Any) -> Dict[str, Any]:
318
- """Extract structure information from advanced analyzer result"""
319
- return {
320
- "structure": {
321
- "package": (
322
- analysis_result.package.name if analysis_result.package else None
323
- ),
324
- "classes": [cls.to_summary_item() if hasattr(cls, 'to_summary_item') else {'name': getattr(cls, 'name', 'unknown')} for cls in [e for e in analysis_result.elements if e.__class__.__name__ == 'Class']],
325
- "methods": [
326
- method.to_summary_item() if hasattr(method, 'to_summary_item') else {'name': getattr(method, 'name', 'unknown')} for method in [e for e in analysis_result.elements if e.__class__.__name__ == 'Function']
327
- ],
328
- "fields": [field.to_summary_item() if hasattr(field, 'to_summary_item') else {'name': getattr(field, 'name', 'unknown')} for field in [e for e in analysis_result.elements if e.__class__.__name__ == 'Variable']],
329
- "imports": [imp.to_summary_item() if hasattr(imp, 'to_summary_item') else {'name': getattr(imp, 'name', 'unknown')} for imp in [e for e in analysis_result.elements if e.__class__.__name__ == 'Import']],
330
- "annotations": [
331
- ann.to_summary_item() if hasattr(ann, 'to_summary_item') else {'name': getattr(ann, 'name', 'unknown')} for ann in getattr(analysis_result, "annotations", [])
332
- ],
333
- }
334
- }
335
-
336
- def _extract_comprehensive_metrics(self, analysis_result: Any) -> Dict[str, Any]:
337
- """Extract comprehensive metrics from advanced analyzer result"""
338
- detailed = self._extract_detailed_metrics(analysis_result)
339
- structure = self._extract_structure_info(analysis_result)
340
-
341
- # Combine both
342
- result = detailed.copy()
343
- result.update(structure)
344
-
345
- return result
346
-
347
- def _extract_universal_basic_metrics(
348
- self, analysis_result: Dict[str, Any]
349
- ) -> Dict[str, Any]:
350
- """Extract basic metrics from universal analyzer result"""
351
- elements = analysis_result.get("elements", [])
352
- return {
353
- "metrics": {
354
- "lines_total": analysis_result.get("line_count", 0),
355
- "lines_code": analysis_result.get("line_count", 0), # Approximation
356
- "lines_comment": 0, # Not available in universal analyzer
357
- "lines_blank": 0, # Not available in universal analyzer
358
- "elements": {
359
- "classes": len([e for e in elements if e.get("__class__", "") == "Class"]),
360
- "methods": len([e for e in elements if e.get("__class__", "") == "Function"]),
361
- "fields": len([e for e in elements if e.get("__class__", "") == "Variable"]),
362
- "imports": len([e for e in elements if e.get("__class__", "") == "Import"]),
363
- "annotations": 0, # Not available in universal analyzer
364
- },
365
- }
366
- }
367
-
368
- def _extract_universal_detailed_metrics(
369
- self, analysis_result: Dict[str, Any]
370
- ) -> Dict[str, Any]:
371
- """Extract detailed metrics from universal analyzer result"""
372
- basic = self._extract_universal_basic_metrics(analysis_result)
373
-
374
- # Add query results if available
375
- if "query_results" in analysis_result:
376
- basic["query_results"] = analysis_result["query_results"]
377
-
378
- return basic
379
-
380
- def _extract_universal_structure_info(
381
- self, analysis_result: Dict[str, Any]
382
- ) -> Dict[str, Any]:
383
- """Extract structure information from universal analyzer result"""
384
- return {
385
- "structure": analysis_result.get("structure", {}),
386
- "queries_executed": analysis_result.get("queries_executed", []),
387
- }
388
-
389
- def _extract_universal_comprehensive_metrics(
390
- self, analysis_result: Dict[str, Any]
391
- ) -> Dict[str, Any]:
392
- """Extract comprehensive metrics from universal analyzer result"""
393
- detailed = self._extract_universal_detailed_metrics(analysis_result)
394
- structure = self._extract_universal_structure_info(analysis_result)
395
-
396
- # Combine both
397
- result = detailed.copy()
398
- result.update(structure)
399
-
400
- return result
401
-
402
- async def _get_available_queries(self, language: str) -> Dict[str, Any]:
403
- """
404
- Get available queries for the specified language
405
-
406
- Args:
407
- language: Programming language
408
-
409
- Returns:
410
- Dictionary containing available queries information
411
- """
412
- try:
413
- if language == "java":
414
- # For Java, we don't have predefined queries in the advanced analyzer
415
- return {
416
- "language": language,
417
- "queries": [],
418
- "note": "Advanced analyzer uses built-in analysis logic",
419
- }
420
- else:
421
- # For other languages, get from universal analyzer
422
- queries = self.analysis_engine.get_supported_languages()
423
- return {"language": language, "queries": queries, "count": len(queries)}
424
- except Exception as e:
425
- logger.warning(f"Failed to get queries for {language}: {e}")
426
- return {"language": language, "queries": [], "error": str(e)}
427
-
428
- def validate_arguments(self, arguments: Dict[str, Any]) -> bool:
429
- """
430
- Validate tool arguments against the schema.
431
-
432
- Args:
433
- arguments: Arguments to validate
434
-
435
- Returns:
436
- True if arguments are valid
437
-
438
- Raises:
439
- ValueError: If arguments are invalid
440
- """
441
- # Check required fields
442
- if "file_path" not in arguments:
443
- raise ValueError("Required field 'file_path' is missing")
444
-
445
- # Validate file_path
446
- file_path = arguments["file_path"]
447
- if not isinstance(file_path, str):
448
- raise ValueError("file_path must be a string")
449
- if not file_path.strip():
450
- raise ValueError("file_path cannot be empty")
451
-
452
- # Validate optional fields
453
- if "language" in arguments:
454
- language = arguments["language"]
455
- if not isinstance(language, str):
456
- raise ValueError("language must be a string")
457
-
458
- if "analysis_type" in arguments:
459
- analysis_type = arguments["analysis_type"]
460
- if not isinstance(analysis_type, str):
461
- raise ValueError("analysis_type must be a string")
462
- valid_types = ["basic", "detailed", "structure", "metrics"]
463
- if analysis_type not in valid_types:
464
- raise ValueError(f"analysis_type must be one of {valid_types}")
465
-
466
- if "include_ast" in arguments:
467
- include_ast = arguments["include_ast"]
468
- if not isinstance(include_ast, bool):
469
- raise ValueError("include_ast must be a boolean")
470
-
471
- if "include_queries" in arguments:
472
- include_queries = arguments["include_queries"]
473
- if not isinstance(include_queries, bool):
474
- raise ValueError("include_queries must be a boolean")
475
-
476
- return True
1
+ #!/usr/bin/env python3
2
+ """
3
+ Universal Code Analysis Tool for MCP
4
+
5
+ This tool provides universal code analysis capabilities for multiple programming
6
+ languages using the existing language detection and analysis infrastructure.
7
+ """
8
+
9
+ import logging
10
+ from pathlib import Path
11
+ from typing import Any
12
+
13
+ from ...core.analysis_engine import AnalysisRequest, get_analysis_engine
14
+ from ...language_detector import detect_language_from_file, is_language_supported
15
+ from ..utils import get_performance_monitor
16
+ from ..utils.error_handler import handle_mcp_errors
17
+
18
+ logger = logging.getLogger(__name__)
19
+
20
+
21
+ class UniversalAnalyzeTool:
22
+ """
23
+ Universal code analysis tool for multiple programming languages
24
+
25
+ This tool automatically detects the programming language and applies
26
+ the appropriate analyzer to provide comprehensive code analysis.
27
+ """
28
+
29
+ def __init__(self) -> None:
30
+ """Initialize the universal analysis tool"""
31
+ # Use unified analysis engine instead of deprecated AdvancedAnalyzer
32
+ self.analysis_engine = get_analysis_engine()
33
+
34
+ def get_tool_definition(self) -> dict[str, Any]:
35
+ """
36
+ Get MCP tool definition for universal code analysis
37
+
38
+ Returns:
39
+ Tool definition dictionary
40
+ """
41
+ return {
42
+ "name": "analyze_code_universal",
43
+ "description": "Universal code analysis for multiple programming languages with automatic language detection",
44
+ "inputSchema": {
45
+ "type": "object",
46
+ "properties": {
47
+ "file_path": {
48
+ "type": "string",
49
+ "description": "Path to the code file to analyze",
50
+ },
51
+ "language": {
52
+ "type": "string",
53
+ "description": "Programming language (optional, auto-detected if not specified)",
54
+ },
55
+ "analysis_type": {
56
+ "type": "string",
57
+ "enum": ["basic", "detailed", "structure", "metrics"],
58
+ "description": "Type of analysis to perform",
59
+ "default": "basic",
60
+ },
61
+ "include_ast": {
62
+ "type": "boolean",
63
+ "description": "Include AST information in the analysis",
64
+ "default": False,
65
+ },
66
+ "include_queries": {
67
+ "type": "boolean",
68
+ "description": "Include available query information",
69
+ "default": False,
70
+ },
71
+ },
72
+ "required": ["file_path"],
73
+ "additionalProperties": False,
74
+ },
75
+ }
76
+
77
+ @handle_mcp_errors("universal_analyze")
78
+ async def execute(self, arguments: dict[str, Any]) -> dict[str, Any]:
79
+ """
80
+ Execute universal code analysis
81
+
82
+ Args:
83
+ arguments: Tool arguments containing file_path and optional parameters
84
+
85
+ Returns:
86
+ Dictionary containing analysis results
87
+
88
+ Raises:
89
+ ValueError: If required arguments are missing or invalid
90
+ FileNotFoundError: If the specified file doesn't exist
91
+ """
92
+ # Validate required arguments
93
+ if "file_path" not in arguments:
94
+ raise ValueError("file_path is required")
95
+
96
+ file_path = arguments["file_path"]
97
+ language = arguments.get("language")
98
+ analysis_type = arguments.get("analysis_type", "basic")
99
+ include_ast = arguments.get("include_ast", False)
100
+ include_queries = arguments.get("include_queries", False)
101
+
102
+ # Validate file exists
103
+ if not Path(file_path).exists():
104
+ raise FileNotFoundError(f"File not found: {file_path}")
105
+
106
+ # Detect language if not specified
107
+ if not language:
108
+ language = detect_language_from_file(file_path)
109
+ if language == "unknown":
110
+ raise ValueError(f"Could not detect language for file: {file_path}")
111
+
112
+ # Check if language is supported
113
+ if not is_language_supported(language):
114
+ raise ValueError(f"Language '{language}' is not supported by tree-sitter")
115
+
116
+ # Validate analysis_type
117
+ valid_analysis_types = ["basic", "detailed", "structure", "metrics"]
118
+ if analysis_type not in valid_analysis_types:
119
+ raise ValueError(
120
+ f"Invalid analysis_type '{analysis_type}'. Valid types: {', '.join(valid_analysis_types)}"
121
+ )
122
+
123
+ logger.info(
124
+ f"Analyzing {file_path} (language: {language}, type: {analysis_type})"
125
+ )
126
+
127
+ try:
128
+ monitor = get_performance_monitor()
129
+ with monitor.measure_operation("universal_analyze"):
130
+ # Get appropriate analyzer
131
+ if language == "java":
132
+ # Use advanced analyzer for Java
133
+ result = await self._analyze_with_advanced_analyzer(
134
+ file_path, language, analysis_type, include_ast
135
+ )
136
+ else:
137
+ # Use universal analyzer for other languages
138
+ result = await self._analyze_with_universal_analyzer(
139
+ file_path, language, analysis_type, include_ast
140
+ )
141
+
142
+ # Add query information if requested
143
+ if include_queries:
144
+ result["available_queries"] = await self._get_available_queries(
145
+ language
146
+ )
147
+
148
+ logger.info(f"Successfully analyzed {file_path}")
149
+ return result
150
+
151
+ except Exception as e:
152
+ logger.error(f"Error analyzing {file_path}: {e}")
153
+ raise
154
+
155
+ async def _analyze_with_advanced_analyzer(
156
+ self, file_path: str, language: str, analysis_type: str, include_ast: bool
157
+ ) -> dict[str, Any]:
158
+ """
159
+ Analyze using the advanced analyzer (Java-specific)
160
+
161
+ Args:
162
+ file_path: Path to the file to analyze
163
+ language: Programming language
164
+ analysis_type: Type of analysis to perform
165
+ include_ast: Whether to include AST information
166
+
167
+ Returns:
168
+ Analysis results dictionary
169
+ """
170
+ # Use unified analysis engine instead of deprecated advanced_analyzer
171
+ request = AnalysisRequest(
172
+ file_path=file_path,
173
+ language=language,
174
+ include_complexity=True,
175
+ include_details=True,
176
+ )
177
+ analysis_result = await self.analysis_engine.analyze(request)
178
+
179
+ if analysis_result is None:
180
+ raise RuntimeError(f"Failed to analyze file: {file_path}")
181
+
182
+ # Build base result
183
+ result: dict[str, Any] = {
184
+ "file_path": file_path,
185
+ "language": language,
186
+ "analyzer_type": "advanced",
187
+ "analysis_type": analysis_type,
188
+ }
189
+
190
+ if analysis_type == "basic":
191
+ result.update(self._extract_basic_metrics(analysis_result))
192
+ elif analysis_type == "detailed":
193
+ result.update(self._extract_detailed_metrics(analysis_result))
194
+ elif analysis_type == "structure":
195
+ result.update(self._extract_structure_info(analysis_result))
196
+ elif analysis_type == "metrics":
197
+ result.update(self._extract_comprehensive_metrics(analysis_result))
198
+
199
+ if include_ast:
200
+ result["ast_info"] = {
201
+ "node_count": getattr(
202
+ analysis_result, "line_count", 0
203
+ ), # Approximation
204
+ "depth": 0, # Advanced analyzer doesn't provide this, use 0 instead of string
205
+ }
206
+
207
+ return result
208
+
209
+ async def _analyze_with_universal_analyzer(
210
+ self, file_path: str, language: str, analysis_type: str, include_ast: bool
211
+ ) -> dict[str, Any]:
212
+ """
213
+ Analyze using the universal analyzer
214
+
215
+ Args:
216
+ file_path: Path to the file to analyze
217
+ language: Programming language
218
+ analysis_type: Type of analysis to perform
219
+ include_ast: Whether to include AST information
220
+
221
+ Returns:
222
+ Analysis results dictionary
223
+ """
224
+ request = AnalysisRequest(
225
+ file_path=file_path,
226
+ language=language,
227
+ include_details=(analysis_type == "detailed"),
228
+ )
229
+ analysis_result = await self.analysis_engine.analyze(request)
230
+
231
+ if not analysis_result or not analysis_result.success:
232
+ error_message = (
233
+ analysis_result.error_message if analysis_result else "Unknown error"
234
+ )
235
+ raise RuntimeError(f"Failed to analyze file: {file_path} - {error_message}")
236
+
237
+ # Convert AnalysisResult to dictionary for consistent processing
238
+ analysis_dict = analysis_result.to_dict()
239
+
240
+ # Build base result
241
+ result: dict[str, Any] = {
242
+ "file_path": file_path,
243
+ "language": language,
244
+ "analyzer_type": "universal",
245
+ "analysis_type": analysis_type,
246
+ }
247
+
248
+ if analysis_type == "basic":
249
+ result.update(self._extract_universal_basic_metrics(analysis_dict))
250
+ elif analysis_type == "detailed":
251
+ result.update(self._extract_universal_detailed_metrics(analysis_dict))
252
+ elif analysis_type == "structure":
253
+ result.update(self._extract_universal_structure_info(analysis_dict))
254
+ elif analysis_type == "metrics":
255
+ result.update(self._extract_universal_comprehensive_metrics(analysis_dict))
256
+
257
+ if include_ast:
258
+ result["ast_info"] = analysis_dict.get("ast_info", {})
259
+
260
+ return result
261
+
262
+ def _extract_basic_metrics(self, analysis_result: Any) -> dict[str, Any]:
263
+ """Extract basic metrics from advanced analyzer result"""
264
+ stats = analysis_result.get_statistics()
265
+
266
+ return {
267
+ "metrics": {
268
+ "lines_total": analysis_result.line_count,
269
+ "lines_code": stats.get("lines_of_code", 0),
270
+ "lines_comment": stats.get("comment_lines", 0),
271
+ "lines_blank": stats.get("blank_lines", 0),
272
+ "elements": {
273
+ "classes": len(
274
+ [
275
+ e
276
+ for e in analysis_result.elements
277
+ if e.__class__.__name__ == "Class"
278
+ ]
279
+ ),
280
+ "methods": len(
281
+ [
282
+ e
283
+ for e in analysis_result.elements
284
+ if e.__class__.__name__ == "Function"
285
+ ]
286
+ ),
287
+ "fields": len(
288
+ [
289
+ e
290
+ for e in analysis_result.elements
291
+ if e.__class__.__name__ == "Variable"
292
+ ]
293
+ ),
294
+ "imports": len(
295
+ [
296
+ e
297
+ for e in analysis_result.elements
298
+ if e.__class__.__name__ == "Import"
299
+ ]
300
+ ),
301
+ "annotations": len(getattr(analysis_result, "annotations", [])),
302
+ },
303
+ }
304
+ }
305
+
306
+ def _extract_detailed_metrics(self, analysis_result: Any) -> dict[str, Any]:
307
+ """Extract detailed metrics from advanced analyzer result"""
308
+ basic = self._extract_basic_metrics(analysis_result)
309
+
310
+ # Add complexity metrics
311
+ methods = [
312
+ e for e in analysis_result.elements if e.__class__.__name__ == "Function"
313
+ ]
314
+ total_complexity = sum(
315
+ getattr(method, "complexity_score", 0) or 0 for method in methods
316
+ )
317
+
318
+ basic["metrics"]["complexity"] = {
319
+ "total": total_complexity,
320
+ "average": (total_complexity / len(methods) if methods else 0),
321
+ "max": max(
322
+ (getattr(method, "complexity_score", 0) or 0 for method in methods),
323
+ default=0,
324
+ ),
325
+ }
326
+
327
+ return basic
328
+
329
+ def _extract_structure_info(self, analysis_result: Any) -> dict[str, Any]:
330
+ """Extract structure information from advanced analyzer result"""
331
+ return {
332
+ "structure": {
333
+ "package": (
334
+ analysis_result.package.name if analysis_result.package else None
335
+ ),
336
+ "classes": [
337
+ (
338
+ cls.to_summary_item()
339
+ if hasattr(cls, "to_summary_item")
340
+ else {"name": getattr(cls, "name", "unknown")}
341
+ )
342
+ for cls in [
343
+ e
344
+ for e in analysis_result.elements
345
+ if e.__class__.__name__ == "Class"
346
+ ]
347
+ ],
348
+ "methods": [
349
+ (
350
+ method.to_summary_item()
351
+ if hasattr(method, "to_summary_item")
352
+ else {"name": getattr(method, "name", "unknown")}
353
+ )
354
+ for method in [
355
+ e
356
+ for e in analysis_result.elements
357
+ if e.__class__.__name__ == "Function"
358
+ ]
359
+ ],
360
+ "fields": [
361
+ (
362
+ field.to_summary_item()
363
+ if hasattr(field, "to_summary_item")
364
+ else {"name": getattr(field, "name", "unknown")}
365
+ )
366
+ for field in [
367
+ e
368
+ for e in analysis_result.elements
369
+ if e.__class__.__name__ == "Variable"
370
+ ]
371
+ ],
372
+ "imports": [
373
+ (
374
+ imp.to_summary_item()
375
+ if hasattr(imp, "to_summary_item")
376
+ else {"name": getattr(imp, "name", "unknown")}
377
+ )
378
+ for imp in [
379
+ e
380
+ for e in analysis_result.elements
381
+ if e.__class__.__name__ == "Import"
382
+ ]
383
+ ],
384
+ "annotations": [
385
+ (
386
+ ann.to_summary_item()
387
+ if hasattr(ann, "to_summary_item")
388
+ else {"name": getattr(ann, "name", "unknown")}
389
+ )
390
+ for ann in getattr(analysis_result, "annotations", [])
391
+ ],
392
+ }
393
+ }
394
+
395
+ def _extract_comprehensive_metrics(self, analysis_result: Any) -> dict[str, Any]:
396
+ """Extract comprehensive metrics from advanced analyzer result"""
397
+ detailed = self._extract_detailed_metrics(analysis_result)
398
+ structure = self._extract_structure_info(analysis_result)
399
+
400
+ # Combine both
401
+ result = detailed.copy()
402
+ result.update(structure)
403
+
404
+ return result
405
+
406
+ def _extract_universal_basic_metrics(
407
+ self, analysis_result: dict[str, Any]
408
+ ) -> dict[str, Any]:
409
+ """Extract basic metrics from universal analyzer result"""
410
+ elements = analysis_result.get("elements", [])
411
+ return {
412
+ "metrics": {
413
+ "lines_total": analysis_result.get("line_count", 0),
414
+ "lines_code": analysis_result.get("line_count", 0), # Approximation
415
+ "lines_comment": 0, # Not available in universal analyzer
416
+ "lines_blank": 0, # Not available in universal analyzer
417
+ "elements": {
418
+ "classes": len(
419
+ [e for e in elements if e.get("__class__", "") == "Class"]
420
+ ),
421
+ "methods": len(
422
+ [e for e in elements if e.get("__class__", "") == "Function"]
423
+ ),
424
+ "fields": len(
425
+ [e for e in elements if e.get("__class__", "") == "Variable"]
426
+ ),
427
+ "imports": len(
428
+ [e for e in elements if e.get("__class__", "") == "Import"]
429
+ ),
430
+ "annotations": 0, # Not available in universal analyzer
431
+ },
432
+ }
433
+ }
434
+
435
+ def _extract_universal_detailed_metrics(
436
+ self, analysis_result: dict[str, Any]
437
+ ) -> dict[str, Any]:
438
+ """Extract detailed metrics from universal analyzer result"""
439
+ basic = self._extract_universal_basic_metrics(analysis_result)
440
+
441
+ # Add query results if available
442
+ if "query_results" in analysis_result:
443
+ basic["query_results"] = analysis_result["query_results"]
444
+
445
+ return basic
446
+
447
+ def _extract_universal_structure_info(
448
+ self, analysis_result: dict[str, Any]
449
+ ) -> dict[str, Any]:
450
+ """Extract structure information from universal analyzer result"""
451
+ return {
452
+ "structure": analysis_result.get("structure", {}),
453
+ "queries_executed": analysis_result.get("queries_executed", []),
454
+ }
455
+
456
+ def _extract_universal_comprehensive_metrics(
457
+ self, analysis_result: dict[str, Any]
458
+ ) -> dict[str, Any]:
459
+ """Extract comprehensive metrics from universal analyzer result"""
460
+ detailed = self._extract_universal_detailed_metrics(analysis_result)
461
+ structure = self._extract_universal_structure_info(analysis_result)
462
+
463
+ # Combine both
464
+ result = detailed.copy()
465
+ result.update(structure)
466
+
467
+ return result
468
+
469
+ async def _get_available_queries(self, language: str) -> dict[str, Any]:
470
+ """
471
+ Get available queries for the specified language
472
+
473
+ Args:
474
+ language: Programming language
475
+
476
+ Returns:
477
+ Dictionary containing available queries information
478
+ """
479
+ try:
480
+ if language == "java":
481
+ # For Java, we don't have predefined queries in the advanced analyzer
482
+ return {
483
+ "language": language,
484
+ "queries": [],
485
+ "note": "Advanced analyzer uses built-in analysis logic",
486
+ }
487
+ else:
488
+ # For other languages, get from universal analyzer
489
+ queries = self.analysis_engine.get_supported_languages()
490
+ return {"language": language, "queries": queries, "count": len(queries)}
491
+ except Exception as e:
492
+ logger.warning(f"Failed to get queries for {language}: {e}")
493
+ return {"language": language, "queries": [], "error": str(e)}
494
+
495
+ def validate_arguments(self, arguments: dict[str, Any]) -> bool:
496
+ """
497
+ Validate tool arguments against the schema.
498
+
499
+ Args:
500
+ arguments: Arguments to validate
501
+
502
+ Returns:
503
+ True if arguments are valid
504
+
505
+ Raises:
506
+ ValueError: If arguments are invalid
507
+ """
508
+ # Check required fields
509
+ if "file_path" not in arguments:
510
+ raise ValueError("Required field 'file_path' is missing")
511
+
512
+ # Validate file_path
513
+ file_path = arguments["file_path"]
514
+ if not isinstance(file_path, str):
515
+ raise ValueError("file_path must be a string")
516
+ if not file_path.strip():
517
+ raise ValueError("file_path cannot be empty")
518
+
519
+ # Validate optional fields
520
+ if "language" in arguments:
521
+ language = arguments["language"]
522
+ if not isinstance(language, str):
523
+ raise ValueError("language must be a string")
524
+
525
+ if "analysis_type" in arguments:
526
+ analysis_type = arguments["analysis_type"]
527
+ if not isinstance(analysis_type, str):
528
+ raise ValueError("analysis_type must be a string")
529
+ valid_types = ["basic", "detailed", "structure", "metrics"]
530
+ if analysis_type not in valid_types:
531
+ raise ValueError(f"analysis_type must be one of {valid_types}")
532
+
533
+ if "include_ast" in arguments:
534
+ include_ast = arguments["include_ast"]
535
+ if not isinstance(include_ast, bool):
536
+ raise ValueError("include_ast must be a boolean")
537
+
538
+ if "include_queries" in arguments:
539
+ include_queries = arguments["include_queries"]
540
+ if not isinstance(include_queries, bool):
541
+ raise ValueError("include_queries must be a boolean")
542
+
543
+ return True