tree-sitter-analyzer 0.6.2__py3-none-any.whl → 0.8.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 (69) hide show
  1. tree_sitter_analyzer/__init__.py +132 -132
  2. tree_sitter_analyzer/__main__.py +11 -11
  3. tree_sitter_analyzer/api.py +533 -533
  4. tree_sitter_analyzer/cli/__init__.py +39 -39
  5. tree_sitter_analyzer/cli/__main__.py +12 -12
  6. tree_sitter_analyzer/cli/commands/__init__.py +26 -26
  7. tree_sitter_analyzer/cli/commands/advanced_command.py +88 -88
  8. tree_sitter_analyzer/cli/commands/base_command.py +160 -160
  9. tree_sitter_analyzer/cli/commands/default_command.py +18 -18
  10. tree_sitter_analyzer/cli/commands/partial_read_command.py +141 -141
  11. tree_sitter_analyzer/cli/commands/query_command.py +81 -81
  12. tree_sitter_analyzer/cli/commands/structure_command.py +138 -138
  13. tree_sitter_analyzer/cli/commands/summary_command.py +101 -101
  14. tree_sitter_analyzer/cli/commands/table_command.py +235 -235
  15. tree_sitter_analyzer/cli/info_commands.py +121 -121
  16. tree_sitter_analyzer/cli_main.py +297 -297
  17. tree_sitter_analyzer/core/__init__.py +15 -15
  18. tree_sitter_analyzer/core/analysis_engine.py +555 -555
  19. tree_sitter_analyzer/core/cache_service.py +320 -320
  20. tree_sitter_analyzer/core/engine.py +566 -566
  21. tree_sitter_analyzer/core/parser.py +293 -293
  22. tree_sitter_analyzer/encoding_utils.py +459 -459
  23. tree_sitter_analyzer/exceptions.py +406 -337
  24. tree_sitter_analyzer/file_handler.py +210 -210
  25. tree_sitter_analyzer/formatters/__init__.py +1 -1
  26. tree_sitter_analyzer/formatters/base_formatter.py +167 -167
  27. tree_sitter_analyzer/formatters/formatter_factory.py +78 -78
  28. tree_sitter_analyzer/interfaces/__init__.py +9 -9
  29. tree_sitter_analyzer/interfaces/cli.py +528 -528
  30. tree_sitter_analyzer/interfaces/cli_adapter.py +343 -343
  31. tree_sitter_analyzer/interfaces/mcp_adapter.py +206 -206
  32. tree_sitter_analyzer/interfaces/mcp_server.py +425 -405
  33. tree_sitter_analyzer/languages/__init__.py +10 -10
  34. tree_sitter_analyzer/languages/javascript_plugin.py +446 -446
  35. tree_sitter_analyzer/languages/python_plugin.py +755 -755
  36. tree_sitter_analyzer/mcp/__init__.py +31 -31
  37. tree_sitter_analyzer/mcp/resources/__init__.py +44 -44
  38. tree_sitter_analyzer/mcp/resources/code_file_resource.py +209 -209
  39. tree_sitter_analyzer/mcp/server.py +346 -333
  40. tree_sitter_analyzer/mcp/tools/__init__.py +30 -30
  41. tree_sitter_analyzer/mcp/tools/analyze_scale_tool.py +654 -654
  42. tree_sitter_analyzer/mcp/tools/analyze_scale_tool_cli_compatible.py +247 -247
  43. tree_sitter_analyzer/mcp/tools/base_tool.py +54 -54
  44. tree_sitter_analyzer/mcp/tools/read_partial_tool.py +300 -300
  45. tree_sitter_analyzer/mcp/tools/table_format_tool.py +362 -362
  46. tree_sitter_analyzer/mcp/tools/universal_analyze_tool.py +543 -543
  47. tree_sitter_analyzer/mcp/utils/__init__.py +107 -107
  48. tree_sitter_analyzer/mcp/utils/error_handler.py +549 -549
  49. tree_sitter_analyzer/output_manager.py +253 -253
  50. tree_sitter_analyzer/plugins/__init__.py +280 -280
  51. tree_sitter_analyzer/plugins/base.py +529 -529
  52. tree_sitter_analyzer/plugins/manager.py +379 -379
  53. tree_sitter_analyzer/queries/__init__.py +26 -26
  54. tree_sitter_analyzer/queries/java.py +391 -391
  55. tree_sitter_analyzer/queries/javascript.py +148 -148
  56. tree_sitter_analyzer/queries/python.py +285 -285
  57. tree_sitter_analyzer/queries/typescript.py +229 -229
  58. tree_sitter_analyzer/query_loader.py +257 -257
  59. tree_sitter_analyzer/security/__init__.py +22 -0
  60. tree_sitter_analyzer/security/boundary_manager.py +237 -0
  61. tree_sitter_analyzer/security/regex_checker.py +292 -0
  62. tree_sitter_analyzer/security/validator.py +224 -0
  63. tree_sitter_analyzer/table_formatter.py +652 -473
  64. tree_sitter_analyzer/utils.py +277 -277
  65. {tree_sitter_analyzer-0.6.2.dist-info → tree_sitter_analyzer-0.8.0.dist-info}/METADATA +4 -1
  66. tree_sitter_analyzer-0.8.0.dist-info/RECORD +76 -0
  67. tree_sitter_analyzer-0.6.2.dist-info/RECORD +0 -72
  68. {tree_sitter_analyzer-0.6.2.dist-info → tree_sitter_analyzer-0.8.0.dist-info}/WHEEL +0 -0
  69. {tree_sitter_analyzer-0.6.2.dist-info → tree_sitter_analyzer-0.8.0.dist-info}/entry_points.txt +0 -0
@@ -1,543 +1,543 @@
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
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