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.

Files changed (61) 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 +182 -178
  9. tree_sitter_analyzer/cli/commands/structure_command.py +138 -138
  10. tree_sitter_analyzer/cli/commands/summary_command.py +101 -101
  11. tree_sitter_analyzer/core/__init__.py +15 -15
  12. tree_sitter_analyzer/core/analysis_engine.py +74 -78
  13. tree_sitter_analyzer/core/cache_service.py +320 -320
  14. tree_sitter_analyzer/core/engine.py +566 -566
  15. tree_sitter_analyzer/core/parser.py +293 -293
  16. tree_sitter_analyzer/encoding_utils.py +459 -459
  17. tree_sitter_analyzer/file_handler.py +210 -210
  18. tree_sitter_analyzer/formatters/__init__.py +1 -1
  19. tree_sitter_analyzer/formatters/base_formatter.py +167 -167
  20. tree_sitter_analyzer/formatters/formatter_factory.py +78 -78
  21. tree_sitter_analyzer/formatters/java_formatter.py +18 -18
  22. tree_sitter_analyzer/formatters/python_formatter.py +19 -19
  23. tree_sitter_analyzer/interfaces/__init__.py +9 -9
  24. tree_sitter_analyzer/interfaces/cli.py +528 -528
  25. tree_sitter_analyzer/interfaces/cli_adapter.py +344 -343
  26. tree_sitter_analyzer/interfaces/mcp_adapter.py +206 -206
  27. tree_sitter_analyzer/language_detector.py +53 -53
  28. tree_sitter_analyzer/languages/__init__.py +10 -10
  29. tree_sitter_analyzer/languages/java_plugin.py +1 -1
  30. tree_sitter_analyzer/languages/javascript_plugin.py +446 -446
  31. tree_sitter_analyzer/languages/python_plugin.py +755 -755
  32. tree_sitter_analyzer/mcp/__init__.py +34 -45
  33. tree_sitter_analyzer/mcp/resources/__init__.py +44 -44
  34. tree_sitter_analyzer/mcp/resources/code_file_resource.py +209 -209
  35. tree_sitter_analyzer/mcp/server.py +623 -568
  36. tree_sitter_analyzer/mcp/tools/__init__.py +30 -30
  37. tree_sitter_analyzer/mcp/tools/analyze_scale_tool.py +681 -673
  38. tree_sitter_analyzer/mcp/tools/analyze_scale_tool_cli_compatible.py +247 -247
  39. tree_sitter_analyzer/mcp/tools/base_tool.py +54 -54
  40. tree_sitter_analyzer/mcp/tools/read_partial_tool.py +310 -308
  41. tree_sitter_analyzer/mcp/tools/table_format_tool.py +386 -379
  42. tree_sitter_analyzer/mcp/tools/universal_analyze_tool.py +563 -559
  43. tree_sitter_analyzer/mcp/utils/__init__.py +107 -107
  44. tree_sitter_analyzer/models.py +10 -10
  45. tree_sitter_analyzer/output_manager.py +253 -253
  46. tree_sitter_analyzer/plugins/__init__.py +280 -280
  47. tree_sitter_analyzer/plugins/base.py +529 -529
  48. tree_sitter_analyzer/plugins/manager.py +379 -379
  49. tree_sitter_analyzer/queries/__init__.py +26 -26
  50. tree_sitter_analyzer/queries/java.py +391 -391
  51. tree_sitter_analyzer/queries/javascript.py +148 -148
  52. tree_sitter_analyzer/queries/python.py +285 -285
  53. tree_sitter_analyzer/queries/typescript.py +229 -229
  54. tree_sitter_analyzer/query_loader.py +257 -257
  55. tree_sitter_analyzer/security/validator.py +246 -241
  56. tree_sitter_analyzer/utils.py +294 -277
  57. {tree_sitter_analyzer-0.9.1.dist-info → tree_sitter_analyzer-0.9.2.dist-info}/METADATA +1 -1
  58. tree_sitter_analyzer-0.9.2.dist-info/RECORD +77 -0
  59. {tree_sitter_analyzer-0.9.1.dist-info → tree_sitter_analyzer-0.9.2.dist-info}/entry_points.txt +1 -0
  60. tree_sitter_analyzer-0.9.1.dist-info/RECORD +0 -77
  61. {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(f"Security validation failed for file path: {file_path} - {error_msg}")
278
- raise ValueError(f"Invalid file path: {error_msg}")
279
-
280
- # Sanitize format_type input
281
- if format_type:
282
- format_type = self.security_validator.sanitize_input(format_type, max_length=50)
283
-
284
- # Sanitize language input
285
- if language:
286
- language = self.security_validator.sanitize_input(language, max_length=50)
287
-
288
- # Validate file exists
289
- if not Path(file_path).exists():
290
- raise FileNotFoundError(f"File not found: {file_path}")
291
-
292
- # Detect language if not provided
293
- if not language:
294
- language = detect_language_from_file(file_path)
295
-
296
- # Use performance monitoring
297
- monitor = get_performance_monitor()
298
- with monitor.measure_operation("code_structure_analysis"):
299
- # Analyze structure using the unified analysis engine
300
- request = AnalysisRequest(
301
- file_path=file_path,
302
- language=language,
303
- include_complexity=True,
304
- include_details=True,
305
- )
306
- structure_result = await self.analysis_engine.analyze(request)
307
-
308
- if structure_result is None:
309
- raise RuntimeError(
310
- f"Failed to analyze structure for file: {file_path}"
311
- )
312
-
313
- # Create table formatter
314
- formatter = TableFormatter(format_type)
315
-
316
- # Convert AnalysisResult to dict format for TableFormatter
317
- structure_dict = self._convert_analysis_result_to_dict(structure_result)
318
-
319
- # Format table
320
- table_output = formatter.format_structure(structure_dict)
321
-
322
- # Ensure output format matches CLI exactly
323
- # Fix line ending differences: normalize to Unix-style LF (\n)
324
- table_output = table_output.replace("\r\n", "\n").replace("\r", "\n")
325
-
326
- # CLI uses sys.stdout.buffer.write() which doesn't add trailing newline
327
- # Ensure MCP output matches this behavior exactly
328
- # Remove any trailing whitespace and newlines to match CLI output
329
- table_output = table_output.rstrip()
330
-
331
- # Extract metadata from structure dict
332
- metadata = {}
333
- if "statistics" in structure_dict:
334
- stats = structure_dict["statistics"]
335
- metadata = {
336
- "classes_count": stats.get("class_count", 0),
337
- "methods_count": stats.get("method_count", 0),
338
- "fields_count": stats.get("field_count", 0),
339
- "total_lines": stats.get("total_lines", 0),
340
- }
341
-
342
- return {
343
- "table_output": table_output,
344
- "format_type": format_type,
345
- "file_path": file_path,
346
- "language": language,
347
- "metadata": metadata,
348
- }
349
-
350
- except Exception as e:
351
- self.logger.error(f"Error in code structure analysis tool: {e}")
352
- raise
353
-
354
- def get_tool_definition(self) -> Any:
355
- """
356
- Get the MCP tool definition for analyze_code_structure.
357
-
358
- Returns:
359
- Tool definition object compatible with MCP server
360
- """
361
- try:
362
- from mcp.types import Tool
363
-
364
- return Tool(
365
- name="analyze_code_structure",
366
- description="Analyze code structure and generate detailed overview tables (classes, methods, fields) for large files",
367
- inputSchema=self.get_tool_schema(),
368
- )
369
- except ImportError:
370
- # Fallback for when MCP is not available
371
- return {
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
-
377
-
378
- # Tool instance for easy access
379
- 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 ...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()