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