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