tree-sitter-analyzer 0.9.1__py3-none-any.whl → 0.9.2__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.
- tree_sitter_analyzer/__init__.py +132 -132
- tree_sitter_analyzer/__main__.py +11 -11
- tree_sitter_analyzer/api.py +533 -533
- tree_sitter_analyzer/cli/__init__.py +39 -39
- tree_sitter_analyzer/cli/__main__.py +12 -12
- tree_sitter_analyzer/cli/commands/__init__.py +26 -26
- tree_sitter_analyzer/cli/commands/advanced_command.py +88 -88
- tree_sitter_analyzer/cli/commands/base_command.py +182 -178
- tree_sitter_analyzer/cli/commands/structure_command.py +138 -138
- tree_sitter_analyzer/cli/commands/summary_command.py +101 -101
- tree_sitter_analyzer/core/__init__.py +15 -15
- tree_sitter_analyzer/core/analysis_engine.py +74 -78
- tree_sitter_analyzer/core/cache_service.py +320 -320
- tree_sitter_analyzer/core/engine.py +566 -566
- tree_sitter_analyzer/core/parser.py +293 -293
- tree_sitter_analyzer/encoding_utils.py +459 -459
- tree_sitter_analyzer/file_handler.py +210 -210
- tree_sitter_analyzer/formatters/__init__.py +1 -1
- tree_sitter_analyzer/formatters/base_formatter.py +167 -167
- tree_sitter_analyzer/formatters/formatter_factory.py +78 -78
- tree_sitter_analyzer/formatters/java_formatter.py +18 -18
- tree_sitter_analyzer/formatters/python_formatter.py +19 -19
- tree_sitter_analyzer/interfaces/__init__.py +9 -9
- tree_sitter_analyzer/interfaces/cli.py +528 -528
- tree_sitter_analyzer/interfaces/cli_adapter.py +344 -343
- tree_sitter_analyzer/interfaces/mcp_adapter.py +206 -206
- tree_sitter_analyzer/language_detector.py +53 -53
- tree_sitter_analyzer/languages/__init__.py +10 -10
- tree_sitter_analyzer/languages/java_plugin.py +1 -1
- tree_sitter_analyzer/languages/javascript_plugin.py +446 -446
- tree_sitter_analyzer/languages/python_plugin.py +755 -755
- tree_sitter_analyzer/mcp/__init__.py +34 -45
- tree_sitter_analyzer/mcp/resources/__init__.py +44 -44
- tree_sitter_analyzer/mcp/resources/code_file_resource.py +209 -209
- tree_sitter_analyzer/mcp/server.py +623 -568
- tree_sitter_analyzer/mcp/tools/__init__.py +30 -30
- tree_sitter_analyzer/mcp/tools/analyze_scale_tool.py +681 -673
- tree_sitter_analyzer/mcp/tools/analyze_scale_tool_cli_compatible.py +247 -247
- tree_sitter_analyzer/mcp/tools/base_tool.py +54 -54
- tree_sitter_analyzer/mcp/tools/read_partial_tool.py +310 -308
- tree_sitter_analyzer/mcp/tools/table_format_tool.py +386 -379
- tree_sitter_analyzer/mcp/tools/universal_analyze_tool.py +563 -559
- tree_sitter_analyzer/mcp/utils/__init__.py +107 -107
- tree_sitter_analyzer/models.py +10 -10
- tree_sitter_analyzer/output_manager.py +253 -253
- tree_sitter_analyzer/plugins/__init__.py +280 -280
- tree_sitter_analyzer/plugins/base.py +529 -529
- tree_sitter_analyzer/plugins/manager.py +379 -379
- tree_sitter_analyzer/queries/__init__.py +26 -26
- tree_sitter_analyzer/queries/java.py +391 -391
- tree_sitter_analyzer/queries/javascript.py +148 -148
- tree_sitter_analyzer/queries/python.py +285 -285
- tree_sitter_analyzer/queries/typescript.py +229 -229
- tree_sitter_analyzer/query_loader.py +257 -257
- tree_sitter_analyzer/security/validator.py +246 -241
- tree_sitter_analyzer/utils.py +294 -277
- {tree_sitter_analyzer-0.9.1.dist-info → tree_sitter_analyzer-0.9.2.dist-info}/METADATA +1 -1
- tree_sitter_analyzer-0.9.2.dist-info/RECORD +77 -0
- {tree_sitter_analyzer-0.9.1.dist-info → tree_sitter_analyzer-0.9.2.dist-info}/entry_points.txt +1 -0
- tree_sitter_analyzer-0.9.1.dist-info/RECORD +0 -77
- {tree_sitter_analyzer-0.9.1.dist-info → tree_sitter_analyzer-0.9.2.dist-info}/WHEEL +0 -0
|
@@ -1,379 +1,386 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""
|
|
3
|
-
Table Format MCP Tool
|
|
4
|
-
|
|
5
|
-
This tool provides table-formatted output for code analysis results through the MCP protocol,
|
|
6
|
-
equivalent to the CLI --table=full option functionality.
|
|
7
|
-
"""
|
|
8
|
-
|
|
9
|
-
from pathlib import Path
|
|
10
|
-
from typing import Any
|
|
11
|
-
|
|
12
|
-
from ...core.analysis_engine import AnalysisRequest, get_analysis_engine
|
|
13
|
-
from ...language_detector import detect_language_from_file
|
|
14
|
-
from ...security import SecurityValidator
|
|
15
|
-
from ...table_formatter import TableFormatter
|
|
16
|
-
from ...utils import setup_logger
|
|
17
|
-
from ..utils import get_performance_monitor
|
|
18
|
-
|
|
19
|
-
# Set up logging
|
|
20
|
-
logger = setup_logger(__name__)
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
class TableFormatTool:
|
|
24
|
-
"""
|
|
25
|
-
MCP Tool for formatting code analysis results as tables.
|
|
26
|
-
|
|
27
|
-
This tool integrates with existing table_formatter and analyzer components
|
|
28
|
-
to provide table-formatted output through the MCP protocol, equivalent to
|
|
29
|
-
the CLI --table=full option.
|
|
30
|
-
"""
|
|
31
|
-
|
|
32
|
-
def __init__(self, project_root: str = None) -> None:
|
|
33
|
-
"""Initialize the table format tool."""
|
|
34
|
-
self.logger = logger
|
|
35
|
-
self.project_root = project_root
|
|
36
|
-
self.analysis_engine = get_analysis_engine(project_root)
|
|
37
|
-
self.security_validator = SecurityValidator(project_root)
|
|
38
|
-
logger.info("TableFormatTool initialized with security validation")
|
|
39
|
-
|
|
40
|
-
def get_tool_schema(self) -> dict[str, Any]:
|
|
41
|
-
"""
|
|
42
|
-
Get the MCP tool schema for analyze_code_structure.
|
|
43
|
-
|
|
44
|
-
Returns:
|
|
45
|
-
Dictionary containing the tool schema
|
|
46
|
-
"""
|
|
47
|
-
return {
|
|
48
|
-
"type": "object",
|
|
49
|
-
"properties": {
|
|
50
|
-
"file_path": {
|
|
51
|
-
"type": "string",
|
|
52
|
-
"description": "Path to the code file to analyze and format",
|
|
53
|
-
},
|
|
54
|
-
"format_type": {
|
|
55
|
-
"type": "string",
|
|
56
|
-
"description": "Table format type",
|
|
57
|
-
"enum": ["full", "compact", "csv"],
|
|
58
|
-
"default": "full",
|
|
59
|
-
},
|
|
60
|
-
"language": {
|
|
61
|
-
"type": "string",
|
|
62
|
-
"description": "Programming language (optional, auto-detected if not specified)",
|
|
63
|
-
},
|
|
64
|
-
},
|
|
65
|
-
"required": ["file_path"],
|
|
66
|
-
"additionalProperties": False,
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
def validate_arguments(self, arguments: dict[str, Any]) -> bool:
|
|
70
|
-
"""
|
|
71
|
-
Validate tool arguments.
|
|
72
|
-
|
|
73
|
-
Args:
|
|
74
|
-
arguments: Dictionary of arguments to validate
|
|
75
|
-
|
|
76
|
-
Returns:
|
|
77
|
-
True if arguments are valid
|
|
78
|
-
|
|
79
|
-
Raises:
|
|
80
|
-
ValueError: If arguments are invalid
|
|
81
|
-
"""
|
|
82
|
-
# Check required fields
|
|
83
|
-
if "file_path" not in arguments:
|
|
84
|
-
raise ValueError("Required field 'file_path' is missing")
|
|
85
|
-
|
|
86
|
-
# Validate file_path
|
|
87
|
-
file_path = arguments["file_path"]
|
|
88
|
-
if not isinstance(file_path, str):
|
|
89
|
-
raise ValueError("file_path must be a string")
|
|
90
|
-
if not file_path.strip():
|
|
91
|
-
raise ValueError("file_path cannot be empty")
|
|
92
|
-
|
|
93
|
-
# Validate format_type if provided
|
|
94
|
-
if "format_type" in arguments:
|
|
95
|
-
format_type = arguments["format_type"]
|
|
96
|
-
if not isinstance(format_type, str):
|
|
97
|
-
raise ValueError("format_type must be a string")
|
|
98
|
-
if format_type not in ["full", "compact", "csv"]:
|
|
99
|
-
raise ValueError("format_type must be one of: full, compact, csv")
|
|
100
|
-
|
|
101
|
-
# Validate language if provided
|
|
102
|
-
if "language" in arguments:
|
|
103
|
-
language = arguments["language"]
|
|
104
|
-
if not isinstance(language, str):
|
|
105
|
-
raise ValueError("language must be a string")
|
|
106
|
-
|
|
107
|
-
return True
|
|
108
|
-
|
|
109
|
-
def _convert_parameters(self, parameters: Any) -> list[dict[str, str]]:
|
|
110
|
-
"""Convert parameters to expected format"""
|
|
111
|
-
result = []
|
|
112
|
-
for param in parameters:
|
|
113
|
-
if isinstance(param, dict):
|
|
114
|
-
result.append(
|
|
115
|
-
{
|
|
116
|
-
"name": param.get("name", "param"),
|
|
117
|
-
"type": param.get("type", "Object"),
|
|
118
|
-
}
|
|
119
|
-
)
|
|
120
|
-
else:
|
|
121
|
-
result.append(
|
|
122
|
-
{
|
|
123
|
-
"name": getattr(param, "name", "param"),
|
|
124
|
-
"type": getattr(param, "param_type", "Object"),
|
|
125
|
-
}
|
|
126
|
-
)
|
|
127
|
-
return result
|
|
128
|
-
|
|
129
|
-
def _get_method_modifiers(self, method: Any) -> list[str]:
|
|
130
|
-
"""Extract method modifiers as a list"""
|
|
131
|
-
modifiers = []
|
|
132
|
-
if getattr(method, "is_static", False):
|
|
133
|
-
modifiers.append("static")
|
|
134
|
-
if getattr(method, "is_final", False):
|
|
135
|
-
modifiers.append("final")
|
|
136
|
-
if getattr(method, "is_abstract", False):
|
|
137
|
-
modifiers.append("abstract")
|
|
138
|
-
return modifiers
|
|
139
|
-
|
|
140
|
-
def _get_method_parameters(self, method: Any) -> list[dict[str, str]]:
|
|
141
|
-
"""Get method parameters in the correct format for TableFormatter"""
|
|
142
|
-
parameters = getattr(method, "parameters", [])
|
|
143
|
-
|
|
144
|
-
# If parameters is already a list of strings (like "int value"), convert to dict format
|
|
145
|
-
if parameters and isinstance(parameters[0], str):
|
|
146
|
-
result = []
|
|
147
|
-
for param_str in parameters:
|
|
148
|
-
parts = param_str.strip().split()
|
|
149
|
-
if len(parts) >= 2:
|
|
150
|
-
param_type = " ".join(
|
|
151
|
-
parts[:-1]
|
|
152
|
-
) # Everything except last part is type
|
|
153
|
-
param_name = parts[-1] # Last part is name
|
|
154
|
-
result.append({"name": param_name, "type": param_type})
|
|
155
|
-
elif len(parts) == 1:
|
|
156
|
-
# Only type, no name
|
|
157
|
-
result.append({"name": "param", "type": parts[0]})
|
|
158
|
-
return result
|
|
159
|
-
|
|
160
|
-
# Fallback to original conversion method
|
|
161
|
-
return self._convert_parameters(parameters)
|
|
162
|
-
|
|
163
|
-
def _get_field_modifiers(self, field: Any) -> list[str]:
|
|
164
|
-
"""Extract field modifiers as a list"""
|
|
165
|
-
modifiers = []
|
|
166
|
-
|
|
167
|
-
# Add visibility to modifiers for CLI compatibility
|
|
168
|
-
visibility = getattr(field, "visibility", "private")
|
|
169
|
-
if visibility and visibility != "package":
|
|
170
|
-
modifiers.append(visibility)
|
|
171
|
-
|
|
172
|
-
if getattr(field, "is_static", False):
|
|
173
|
-
modifiers.append("static")
|
|
174
|
-
if getattr(field, "is_final", False):
|
|
175
|
-
modifiers.append("final")
|
|
176
|
-
return modifiers
|
|
177
|
-
|
|
178
|
-
def _convert_analysis_result_to_dict(self, result: Any) -> dict[str, Any]:
|
|
179
|
-
"""Convert AnalysisResult to dictionary format expected by TableFormatter"""
|
|
180
|
-
# Extract elements by type
|
|
181
|
-
classes = [e for e in result.elements if e.__class__.__name__ == "Class"]
|
|
182
|
-
methods = [e for e in result.elements if e.__class__.__name__ == "Function"]
|
|
183
|
-
fields = [e for e in result.elements if e.__class__.__name__ == "Variable"]
|
|
184
|
-
imports = [e for e in result.elements if e.__class__.__name__ == "Import"]
|
|
185
|
-
packages = [e for e in result.elements if e.__class__.__name__ == "Package"]
|
|
186
|
-
|
|
187
|
-
# Convert package to expected format
|
|
188
|
-
package_info = None
|
|
189
|
-
if packages:
|
|
190
|
-
package_info = {"name": packages[0].name}
|
|
191
|
-
|
|
192
|
-
return {
|
|
193
|
-
"file_path": result.file_path,
|
|
194
|
-
"language": result.language,
|
|
195
|
-
"package": package_info,
|
|
196
|
-
"classes": [
|
|
197
|
-
{
|
|
198
|
-
"name": getattr(cls, "name", "unknown"),
|
|
199
|
-
"line_range": {
|
|
200
|
-
"start": getattr(cls, "start_line", 0),
|
|
201
|
-
"end": getattr(cls, "end_line", 0),
|
|
202
|
-
},
|
|
203
|
-
"type": getattr(cls, "class_type", "class"),
|
|
204
|
-
"visibility": "public", # Force all classes to public for CLI compatibility
|
|
205
|
-
"extends": getattr(cls, "extends_class", None),
|
|
206
|
-
"implements": getattr(cls, "implements_interfaces", []),
|
|
207
|
-
"annotations": [],
|
|
208
|
-
}
|
|
209
|
-
for cls in classes
|
|
210
|
-
],
|
|
211
|
-
"methods": [
|
|
212
|
-
{
|
|
213
|
-
"name": getattr(method, "name", "unknown"),
|
|
214
|
-
"line_range": {
|
|
215
|
-
"start": getattr(method, "start_line", 0),
|
|
216
|
-
"end": getattr(method, "end_line", 0),
|
|
217
|
-
},
|
|
218
|
-
"return_type": getattr(method, "return_type", "void"),
|
|
219
|
-
"parameters": self._get_method_parameters(method),
|
|
220
|
-
"visibility": getattr(method, "visibility", "public"),
|
|
221
|
-
"is_static": getattr(method, "is_static", False),
|
|
222
|
-
"is_constructor": getattr(method, "is_constructor", False),
|
|
223
|
-
"complexity_score": getattr(method, "complexity_score", 0),
|
|
224
|
-
"modifiers": self._get_method_modifiers(method),
|
|
225
|
-
"annotations": [],
|
|
226
|
-
}
|
|
227
|
-
for method in methods
|
|
228
|
-
],
|
|
229
|
-
"fields": [
|
|
230
|
-
{
|
|
231
|
-
"name": getattr(field, "name", "unknown"),
|
|
232
|
-
"type": getattr(field, "field_type", "Object"),
|
|
233
|
-
"line_range": {
|
|
234
|
-
"start": getattr(field, "start_line", 0),
|
|
235
|
-
"end": getattr(field, "end_line", 0),
|
|
236
|
-
},
|
|
237
|
-
"visibility": getattr(field, "visibility", "private"),
|
|
238
|
-
"modifiers": self._get_field_modifiers(field),
|
|
239
|
-
"annotations": [],
|
|
240
|
-
}
|
|
241
|
-
for field in fields
|
|
242
|
-
],
|
|
243
|
-
"imports": [
|
|
244
|
-
{
|
|
245
|
-
"name": getattr(imp, "name", "unknown"),
|
|
246
|
-
"statement": getattr(
|
|
247
|
-
imp, "name", ""
|
|
248
|
-
), # Use name for CLI compatibility
|
|
249
|
-
"is_static": getattr(imp, "is_static", False),
|
|
250
|
-
"is_wildcard": getattr(imp, "is_wildcard", False),
|
|
251
|
-
}
|
|
252
|
-
for imp in imports
|
|
253
|
-
],
|
|
254
|
-
"statistics": {
|
|
255
|
-
"class_count": len(classes),
|
|
256
|
-
"method_count": len(methods),
|
|
257
|
-
"field_count": len(fields),
|
|
258
|
-
"import_count": len(imports),
|
|
259
|
-
"total_lines": result.line_count,
|
|
260
|
-
},
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
async def execute(self, args: dict[str, Any]) -> dict[str, Any]:
|
|
264
|
-
"""Execute code structure analysis tool."""
|
|
265
|
-
try:
|
|
266
|
-
# Validate arguments first
|
|
267
|
-
if "file_path" not in args:
|
|
268
|
-
raise ValueError("file_path is required")
|
|
269
|
-
|
|
270
|
-
file_path = args["file_path"]
|
|
271
|
-
format_type = args.get("format_type", "full")
|
|
272
|
-
language = args.get("language")
|
|
273
|
-
|
|
274
|
-
# Security validation
|
|
275
|
-
is_valid, error_msg = self.security_validator.validate_file_path(file_path)
|
|
276
|
-
if not is_valid:
|
|
277
|
-
self.logger.warning(
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
#
|
|
289
|
-
if
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
#
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
#
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
return
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Table Format MCP Tool
|
|
4
|
+
|
|
5
|
+
This tool provides table-formatted output for code analysis results through the MCP protocol,
|
|
6
|
+
equivalent to the CLI --table=full option functionality.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
from ...core.analysis_engine import AnalysisRequest, get_analysis_engine
|
|
13
|
+
from ...language_detector import detect_language_from_file
|
|
14
|
+
from ...security import SecurityValidator
|
|
15
|
+
from ...table_formatter import TableFormatter
|
|
16
|
+
from ...utils import setup_logger
|
|
17
|
+
from ..utils import get_performance_monitor
|
|
18
|
+
|
|
19
|
+
# Set up logging
|
|
20
|
+
logger = setup_logger(__name__)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class TableFormatTool:
|
|
24
|
+
"""
|
|
25
|
+
MCP Tool for formatting code analysis results as tables.
|
|
26
|
+
|
|
27
|
+
This tool integrates with existing table_formatter and analyzer components
|
|
28
|
+
to provide table-formatted output through the MCP protocol, equivalent to
|
|
29
|
+
the CLI --table=full option.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
def __init__(self, project_root: str = None) -> None:
|
|
33
|
+
"""Initialize the table format tool."""
|
|
34
|
+
self.logger = logger
|
|
35
|
+
self.project_root = project_root
|
|
36
|
+
self.analysis_engine = get_analysis_engine(project_root)
|
|
37
|
+
self.security_validator = SecurityValidator(project_root)
|
|
38
|
+
logger.info("TableFormatTool initialized with security validation")
|
|
39
|
+
|
|
40
|
+
def get_tool_schema(self) -> dict[str, Any]:
|
|
41
|
+
"""
|
|
42
|
+
Get the MCP tool schema for analyze_code_structure.
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
Dictionary containing the tool schema
|
|
46
|
+
"""
|
|
47
|
+
return {
|
|
48
|
+
"type": "object",
|
|
49
|
+
"properties": {
|
|
50
|
+
"file_path": {
|
|
51
|
+
"type": "string",
|
|
52
|
+
"description": "Path to the code file to analyze and format",
|
|
53
|
+
},
|
|
54
|
+
"format_type": {
|
|
55
|
+
"type": "string",
|
|
56
|
+
"description": "Table format type",
|
|
57
|
+
"enum": ["full", "compact", "csv"],
|
|
58
|
+
"default": "full",
|
|
59
|
+
},
|
|
60
|
+
"language": {
|
|
61
|
+
"type": "string",
|
|
62
|
+
"description": "Programming language (optional, auto-detected if not specified)",
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
"required": ["file_path"],
|
|
66
|
+
"additionalProperties": False,
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
def validate_arguments(self, arguments: dict[str, Any]) -> bool:
|
|
70
|
+
"""
|
|
71
|
+
Validate tool arguments.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
arguments: Dictionary of arguments to validate
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
True if arguments are valid
|
|
78
|
+
|
|
79
|
+
Raises:
|
|
80
|
+
ValueError: If arguments are invalid
|
|
81
|
+
"""
|
|
82
|
+
# Check required fields
|
|
83
|
+
if "file_path" not in arguments:
|
|
84
|
+
raise ValueError("Required field 'file_path' is missing")
|
|
85
|
+
|
|
86
|
+
# Validate file_path
|
|
87
|
+
file_path = arguments["file_path"]
|
|
88
|
+
if not isinstance(file_path, str):
|
|
89
|
+
raise ValueError("file_path must be a string")
|
|
90
|
+
if not file_path.strip():
|
|
91
|
+
raise ValueError("file_path cannot be empty")
|
|
92
|
+
|
|
93
|
+
# Validate format_type if provided
|
|
94
|
+
if "format_type" in arguments:
|
|
95
|
+
format_type = arguments["format_type"]
|
|
96
|
+
if not isinstance(format_type, str):
|
|
97
|
+
raise ValueError("format_type must be a string")
|
|
98
|
+
if format_type not in ["full", "compact", "csv"]:
|
|
99
|
+
raise ValueError("format_type must be one of: full, compact, csv")
|
|
100
|
+
|
|
101
|
+
# Validate language if provided
|
|
102
|
+
if "language" in arguments:
|
|
103
|
+
language = arguments["language"]
|
|
104
|
+
if not isinstance(language, str):
|
|
105
|
+
raise ValueError("language must be a string")
|
|
106
|
+
|
|
107
|
+
return True
|
|
108
|
+
|
|
109
|
+
def _convert_parameters(self, parameters: Any) -> list[dict[str, str]]:
|
|
110
|
+
"""Convert parameters to expected format"""
|
|
111
|
+
result = []
|
|
112
|
+
for param in parameters:
|
|
113
|
+
if isinstance(param, dict):
|
|
114
|
+
result.append(
|
|
115
|
+
{
|
|
116
|
+
"name": param.get("name", "param"),
|
|
117
|
+
"type": param.get("type", "Object"),
|
|
118
|
+
}
|
|
119
|
+
)
|
|
120
|
+
else:
|
|
121
|
+
result.append(
|
|
122
|
+
{
|
|
123
|
+
"name": getattr(param, "name", "param"),
|
|
124
|
+
"type": getattr(param, "param_type", "Object"),
|
|
125
|
+
}
|
|
126
|
+
)
|
|
127
|
+
return result
|
|
128
|
+
|
|
129
|
+
def _get_method_modifiers(self, method: Any) -> list[str]:
|
|
130
|
+
"""Extract method modifiers as a list"""
|
|
131
|
+
modifiers = []
|
|
132
|
+
if getattr(method, "is_static", False):
|
|
133
|
+
modifiers.append("static")
|
|
134
|
+
if getattr(method, "is_final", False):
|
|
135
|
+
modifiers.append("final")
|
|
136
|
+
if getattr(method, "is_abstract", False):
|
|
137
|
+
modifiers.append("abstract")
|
|
138
|
+
return modifiers
|
|
139
|
+
|
|
140
|
+
def _get_method_parameters(self, method: Any) -> list[dict[str, str]]:
|
|
141
|
+
"""Get method parameters in the correct format for TableFormatter"""
|
|
142
|
+
parameters = getattr(method, "parameters", [])
|
|
143
|
+
|
|
144
|
+
# If parameters is already a list of strings (like "int value"), convert to dict format
|
|
145
|
+
if parameters and isinstance(parameters[0], str):
|
|
146
|
+
result = []
|
|
147
|
+
for param_str in parameters:
|
|
148
|
+
parts = param_str.strip().split()
|
|
149
|
+
if len(parts) >= 2:
|
|
150
|
+
param_type = " ".join(
|
|
151
|
+
parts[:-1]
|
|
152
|
+
) # Everything except last part is type
|
|
153
|
+
param_name = parts[-1] # Last part is name
|
|
154
|
+
result.append({"name": param_name, "type": param_type})
|
|
155
|
+
elif len(parts) == 1:
|
|
156
|
+
# Only type, no name
|
|
157
|
+
result.append({"name": "param", "type": parts[0]})
|
|
158
|
+
return result
|
|
159
|
+
|
|
160
|
+
# Fallback to original conversion method
|
|
161
|
+
return self._convert_parameters(parameters)
|
|
162
|
+
|
|
163
|
+
def _get_field_modifiers(self, field: Any) -> list[str]:
|
|
164
|
+
"""Extract field modifiers as a list"""
|
|
165
|
+
modifiers = []
|
|
166
|
+
|
|
167
|
+
# Add visibility to modifiers for CLI compatibility
|
|
168
|
+
visibility = getattr(field, "visibility", "private")
|
|
169
|
+
if visibility and visibility != "package":
|
|
170
|
+
modifiers.append(visibility)
|
|
171
|
+
|
|
172
|
+
if getattr(field, "is_static", False):
|
|
173
|
+
modifiers.append("static")
|
|
174
|
+
if getattr(field, "is_final", False):
|
|
175
|
+
modifiers.append("final")
|
|
176
|
+
return modifiers
|
|
177
|
+
|
|
178
|
+
def _convert_analysis_result_to_dict(self, result: Any) -> dict[str, Any]:
|
|
179
|
+
"""Convert AnalysisResult to dictionary format expected by TableFormatter"""
|
|
180
|
+
# Extract elements by type
|
|
181
|
+
classes = [e for e in result.elements if e.__class__.__name__ == "Class"]
|
|
182
|
+
methods = [e for e in result.elements if e.__class__.__name__ == "Function"]
|
|
183
|
+
fields = [e for e in result.elements if e.__class__.__name__ == "Variable"]
|
|
184
|
+
imports = [e for e in result.elements if e.__class__.__name__ == "Import"]
|
|
185
|
+
packages = [e for e in result.elements if e.__class__.__name__ == "Package"]
|
|
186
|
+
|
|
187
|
+
# Convert package to expected format
|
|
188
|
+
package_info = None
|
|
189
|
+
if packages:
|
|
190
|
+
package_info = {"name": packages[0].name}
|
|
191
|
+
|
|
192
|
+
return {
|
|
193
|
+
"file_path": result.file_path,
|
|
194
|
+
"language": result.language,
|
|
195
|
+
"package": package_info,
|
|
196
|
+
"classes": [
|
|
197
|
+
{
|
|
198
|
+
"name": getattr(cls, "name", "unknown"),
|
|
199
|
+
"line_range": {
|
|
200
|
+
"start": getattr(cls, "start_line", 0),
|
|
201
|
+
"end": getattr(cls, "end_line", 0),
|
|
202
|
+
},
|
|
203
|
+
"type": getattr(cls, "class_type", "class"),
|
|
204
|
+
"visibility": "public", # Force all classes to public for CLI compatibility
|
|
205
|
+
"extends": getattr(cls, "extends_class", None),
|
|
206
|
+
"implements": getattr(cls, "implements_interfaces", []),
|
|
207
|
+
"annotations": [],
|
|
208
|
+
}
|
|
209
|
+
for cls in classes
|
|
210
|
+
],
|
|
211
|
+
"methods": [
|
|
212
|
+
{
|
|
213
|
+
"name": getattr(method, "name", "unknown"),
|
|
214
|
+
"line_range": {
|
|
215
|
+
"start": getattr(method, "start_line", 0),
|
|
216
|
+
"end": getattr(method, "end_line", 0),
|
|
217
|
+
},
|
|
218
|
+
"return_type": getattr(method, "return_type", "void"),
|
|
219
|
+
"parameters": self._get_method_parameters(method),
|
|
220
|
+
"visibility": getattr(method, "visibility", "public"),
|
|
221
|
+
"is_static": getattr(method, "is_static", False),
|
|
222
|
+
"is_constructor": getattr(method, "is_constructor", False),
|
|
223
|
+
"complexity_score": getattr(method, "complexity_score", 0),
|
|
224
|
+
"modifiers": self._get_method_modifiers(method),
|
|
225
|
+
"annotations": [],
|
|
226
|
+
}
|
|
227
|
+
for method in methods
|
|
228
|
+
],
|
|
229
|
+
"fields": [
|
|
230
|
+
{
|
|
231
|
+
"name": getattr(field, "name", "unknown"),
|
|
232
|
+
"type": getattr(field, "field_type", "Object"),
|
|
233
|
+
"line_range": {
|
|
234
|
+
"start": getattr(field, "start_line", 0),
|
|
235
|
+
"end": getattr(field, "end_line", 0),
|
|
236
|
+
},
|
|
237
|
+
"visibility": getattr(field, "visibility", "private"),
|
|
238
|
+
"modifiers": self._get_field_modifiers(field),
|
|
239
|
+
"annotations": [],
|
|
240
|
+
}
|
|
241
|
+
for field in fields
|
|
242
|
+
],
|
|
243
|
+
"imports": [
|
|
244
|
+
{
|
|
245
|
+
"name": getattr(imp, "name", "unknown"),
|
|
246
|
+
"statement": getattr(
|
|
247
|
+
imp, "name", ""
|
|
248
|
+
), # Use name for CLI compatibility
|
|
249
|
+
"is_static": getattr(imp, "is_static", False),
|
|
250
|
+
"is_wildcard": getattr(imp, "is_wildcard", False),
|
|
251
|
+
}
|
|
252
|
+
for imp in imports
|
|
253
|
+
],
|
|
254
|
+
"statistics": {
|
|
255
|
+
"class_count": len(classes),
|
|
256
|
+
"method_count": len(methods),
|
|
257
|
+
"field_count": len(fields),
|
|
258
|
+
"import_count": len(imports),
|
|
259
|
+
"total_lines": result.line_count,
|
|
260
|
+
},
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
async def execute(self, args: dict[str, Any]) -> dict[str, Any]:
|
|
264
|
+
"""Execute code structure analysis tool."""
|
|
265
|
+
try:
|
|
266
|
+
# Validate arguments first
|
|
267
|
+
if "file_path" not in args:
|
|
268
|
+
raise ValueError("file_path is required")
|
|
269
|
+
|
|
270
|
+
file_path = args["file_path"]
|
|
271
|
+
format_type = args.get("format_type", "full")
|
|
272
|
+
language = args.get("language")
|
|
273
|
+
|
|
274
|
+
# Security validation
|
|
275
|
+
is_valid, error_msg = self.security_validator.validate_file_path(file_path)
|
|
276
|
+
if not is_valid:
|
|
277
|
+
self.logger.warning(
|
|
278
|
+
f"Security validation failed for file path: {file_path} - {error_msg}"
|
|
279
|
+
)
|
|
280
|
+
raise ValueError(f"Invalid file path: {error_msg}")
|
|
281
|
+
|
|
282
|
+
# Sanitize format_type input
|
|
283
|
+
if format_type:
|
|
284
|
+
format_type = self.security_validator.sanitize_input(
|
|
285
|
+
format_type, max_length=50
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
# Sanitize language input
|
|
289
|
+
if language:
|
|
290
|
+
language = self.security_validator.sanitize_input(
|
|
291
|
+
language, max_length=50
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
# Validate file exists
|
|
295
|
+
if not Path(file_path).exists():
|
|
296
|
+
# Tests expect FileNotFoundError here
|
|
297
|
+
raise FileNotFoundError(f"File not found: {file_path}")
|
|
298
|
+
|
|
299
|
+
# Detect language if not provided
|
|
300
|
+
if not language:
|
|
301
|
+
language = detect_language_from_file(file_path)
|
|
302
|
+
|
|
303
|
+
# Use performance monitoring
|
|
304
|
+
monitor = get_performance_monitor()
|
|
305
|
+
with monitor.measure_operation("code_structure_analysis"):
|
|
306
|
+
# Analyze structure using the unified analysis engine
|
|
307
|
+
request = AnalysisRequest(
|
|
308
|
+
file_path=file_path,
|
|
309
|
+
language=language,
|
|
310
|
+
include_complexity=True,
|
|
311
|
+
include_details=True,
|
|
312
|
+
)
|
|
313
|
+
structure_result = await self.analysis_engine.analyze(request)
|
|
314
|
+
|
|
315
|
+
if structure_result is None:
|
|
316
|
+
raise RuntimeError(
|
|
317
|
+
f"Failed to analyze structure for file: {file_path}"
|
|
318
|
+
)
|
|
319
|
+
|
|
320
|
+
# Create table formatter
|
|
321
|
+
formatter = TableFormatter(format_type)
|
|
322
|
+
|
|
323
|
+
# Convert AnalysisResult to dict format for TableFormatter
|
|
324
|
+
structure_dict = self._convert_analysis_result_to_dict(structure_result)
|
|
325
|
+
|
|
326
|
+
# Format table
|
|
327
|
+
table_output = formatter.format_structure(structure_dict)
|
|
328
|
+
|
|
329
|
+
# Ensure output format matches CLI exactly
|
|
330
|
+
# Fix line ending differences: normalize to Unix-style LF (\n)
|
|
331
|
+
table_output = table_output.replace("\r\n", "\n").replace("\r", "\n")
|
|
332
|
+
|
|
333
|
+
# CLI uses sys.stdout.buffer.write() which doesn't add trailing newline
|
|
334
|
+
# Ensure MCP output matches this behavior exactly
|
|
335
|
+
# Remove any trailing whitespace and newlines to match CLI output
|
|
336
|
+
table_output = table_output.rstrip()
|
|
337
|
+
|
|
338
|
+
# Extract metadata from structure dict
|
|
339
|
+
metadata = {}
|
|
340
|
+
if "statistics" in structure_dict:
|
|
341
|
+
stats = structure_dict["statistics"]
|
|
342
|
+
metadata = {
|
|
343
|
+
"classes_count": stats.get("class_count", 0),
|
|
344
|
+
"methods_count": stats.get("method_count", 0),
|
|
345
|
+
"fields_count": stats.get("field_count", 0),
|
|
346
|
+
"total_lines": stats.get("total_lines", 0),
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
return {
|
|
350
|
+
"table_output": table_output,
|
|
351
|
+
"format_type": format_type,
|
|
352
|
+
"file_path": file_path,
|
|
353
|
+
"language": language,
|
|
354
|
+
"metadata": metadata,
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
except Exception as e:
|
|
358
|
+
self.logger.error(f"Error in code structure analysis tool: {e}")
|
|
359
|
+
raise
|
|
360
|
+
|
|
361
|
+
def get_tool_definition(self) -> Any:
|
|
362
|
+
"""
|
|
363
|
+
Get the MCP tool definition for analyze_code_structure.
|
|
364
|
+
|
|
365
|
+
Returns:
|
|
366
|
+
Tool definition object compatible with MCP server
|
|
367
|
+
"""
|
|
368
|
+
try:
|
|
369
|
+
from mcp.types import Tool
|
|
370
|
+
|
|
371
|
+
return Tool(
|
|
372
|
+
name="analyze_code_structure",
|
|
373
|
+
description="Analyze code structure and generate detailed overview tables (classes, methods, fields) for large files",
|
|
374
|
+
inputSchema=self.get_tool_schema(),
|
|
375
|
+
)
|
|
376
|
+
except ImportError:
|
|
377
|
+
# Fallback for when MCP is not available
|
|
378
|
+
return {
|
|
379
|
+
"name": "analyze_code_structure",
|
|
380
|
+
"description": "Analyze code structure and generate detailed overview tables (classes, methods, fields) for large files",
|
|
381
|
+
"inputSchema": self.get_tool_schema(),
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
# Tool instance for easy access
|
|
386
|
+
table_format_tool = TableFormatTool()
|