tree-sitter-analyzer 1.5.0__py3-none-any.whl → 1.6.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.
- tree_sitter_analyzer/__init__.py +1 -1
- tree_sitter_analyzer/cli_main.py +3 -1
- tree_sitter_analyzer/formatters/python_formatter.py +161 -20
- tree_sitter_analyzer/languages/python_plugin.py +581 -148
- tree_sitter_analyzer/mcp/server.py +17 -2
- tree_sitter_analyzer/mcp/tools/table_format_tool.py +106 -4
- tree_sitter_analyzer/mcp/utils/file_output_manager.py +257 -0
- tree_sitter_analyzer/models.py +8 -0
- tree_sitter_analyzer/queries/python.py +617 -58
- tree_sitter_analyzer/table_formatter.py +26 -2
- tree_sitter_analyzer-1.6.1.dist-info/METADATA +845 -0
- {tree_sitter_analyzer-1.5.0.dist-info → tree_sitter_analyzer-1.6.1.dist-info}/RECORD +14 -13
- tree_sitter_analyzer-1.5.0.dist-info/METADATA +0 -1229
- {tree_sitter_analyzer-1.5.0.dist-info → tree_sitter_analyzer-1.6.1.dist-info}/WHEEL +0 -0
- {tree_sitter_analyzer-1.5.0.dist-info → tree_sitter_analyzer-1.6.1.dist-info}/entry_points.txt +0 -0
tree_sitter_analyzer/__init__.py
CHANGED
tree_sitter_analyzer/cli_main.py
CHANGED
|
@@ -152,7 +152,9 @@ def create_argument_parser() -> argparse.ArgumentParser:
|
|
|
152
152
|
help="Specify output format",
|
|
153
153
|
)
|
|
154
154
|
parser.add_argument(
|
|
155
|
-
"--table",
|
|
155
|
+
"--table",
|
|
156
|
+
choices=["full", "compact", "csv", "json"],
|
|
157
|
+
help="Output in table format",
|
|
156
158
|
)
|
|
157
159
|
parser.add_argument(
|
|
158
160
|
"--include-javadoc",
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
"""
|
|
3
3
|
Python-specific table formatter.
|
|
4
|
+
|
|
5
|
+
Provides specialized formatting for Python code analysis results,
|
|
6
|
+
handling modern Python features like async/await, type hints, decorators,
|
|
7
|
+
context managers, and framework-specific patterns.
|
|
4
8
|
"""
|
|
5
9
|
|
|
6
10
|
from typing import Any
|
|
@@ -11,29 +15,58 @@ from .base_formatter import BaseTableFormatter
|
|
|
11
15
|
class PythonTableFormatter(BaseTableFormatter):
|
|
12
16
|
"""Table formatter specialized for Python"""
|
|
13
17
|
|
|
18
|
+
def format(self, data: dict[str, Any]) -> str:
|
|
19
|
+
"""Format data using the configured format type"""
|
|
20
|
+
return self.format_structure(data)
|
|
21
|
+
|
|
14
22
|
def _format_full_table(self, data: dict[str, Any]) -> str:
|
|
15
23
|
"""Full table format for Python"""
|
|
16
24
|
lines = []
|
|
17
25
|
|
|
18
|
-
# Header - Python (
|
|
26
|
+
# Header - Python (module/package based)
|
|
27
|
+
file_path = data.get("file_path", "Unknown")
|
|
28
|
+
file_name = file_path.split("/")[-1].split("\\")[-1]
|
|
29
|
+
module_name = file_name.replace(".py", "").replace(".pyw", "").replace(".pyi", "")
|
|
30
|
+
|
|
31
|
+
# Check if this is a package module
|
|
19
32
|
classes = data.get("classes", [])
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
33
|
+
functions = data.get("functions", [])
|
|
34
|
+
imports = data.get("imports", [])
|
|
35
|
+
|
|
36
|
+
# Determine module type
|
|
37
|
+
is_package = "__init__.py" in file_name
|
|
38
|
+
is_script = any("if __name__ == '__main__'" in func.get("raw_text", "") for func in functions)
|
|
39
|
+
|
|
40
|
+
if is_package:
|
|
41
|
+
lines.append(f"# Package: {module_name}")
|
|
42
|
+
elif is_script:
|
|
43
|
+
lines.append(f"# Script: {module_name}")
|
|
24
44
|
else:
|
|
25
|
-
#
|
|
26
|
-
class_name = classes[0].get("name", "Unknown") if classes else "Unknown"
|
|
27
|
-
lines.append(f"# {class_name}")
|
|
45
|
+
lines.append(f"# Module: {module_name}")
|
|
28
46
|
lines.append("")
|
|
29
47
|
|
|
48
|
+
# Module docstring
|
|
49
|
+
module_docstring = self._extract_module_docstring(data)
|
|
50
|
+
if module_docstring:
|
|
51
|
+
lines.append("## Description")
|
|
52
|
+
lines.append(f'"{module_docstring}"')
|
|
53
|
+
lines.append("")
|
|
54
|
+
|
|
30
55
|
# Imports
|
|
31
|
-
imports = data.get("imports", [])
|
|
32
56
|
if imports:
|
|
33
57
|
lines.append("## Imports")
|
|
34
58
|
lines.append("```python")
|
|
35
59
|
for imp in imports:
|
|
36
|
-
|
|
60
|
+
import_statement = imp.get("raw_text", "")
|
|
61
|
+
if not import_statement:
|
|
62
|
+
# Fallback construction
|
|
63
|
+
module_name = imp.get("module_name", "")
|
|
64
|
+
name = imp.get("name", "")
|
|
65
|
+
if module_name:
|
|
66
|
+
import_statement = f"from {module_name} import {name}"
|
|
67
|
+
else:
|
|
68
|
+
import_statement = f"import {name}"
|
|
69
|
+
lines.append(import_statement)
|
|
37
70
|
lines.append("```")
|
|
38
71
|
lines.append("")
|
|
39
72
|
|
|
@@ -112,12 +145,12 @@ class PythonTableFormatter(BaseTableFormatter):
|
|
|
112
145
|
)
|
|
113
146
|
lines.append("")
|
|
114
147
|
|
|
115
|
-
# Methods - Python (
|
|
116
|
-
methods = data.get("methods", [])
|
|
148
|
+
# Methods - Python (with decorators and async support)
|
|
149
|
+
methods = data.get("methods", []) or functions # Use functions if methods not available
|
|
117
150
|
if methods:
|
|
118
151
|
lines.append("## Methods")
|
|
119
|
-
lines.append("| Method | Signature | Vis | Lines | Cols | Cx | Doc |")
|
|
120
|
-
lines.append("
|
|
152
|
+
lines.append("| Method | Signature | Vis | Lines | Cols | Cx | Decorators | Doc |")
|
|
153
|
+
lines.append("|--------|-----------|-----|-------|------|----|-----------|----|")
|
|
121
154
|
|
|
122
155
|
for method in methods:
|
|
123
156
|
lines.append(self._format_method_row(method))
|
|
@@ -185,17 +218,41 @@ class PythonTableFormatter(BaseTableFormatter):
|
|
|
185
218
|
def _format_method_row(self, method: dict[str, Any]) -> str:
|
|
186
219
|
"""Format a method table row for Python"""
|
|
187
220
|
name = str(method.get("name", ""))
|
|
188
|
-
signature = self.
|
|
189
|
-
|
|
221
|
+
signature = self._format_python_signature(method)
|
|
222
|
+
|
|
223
|
+
# Python-specific visibility handling
|
|
224
|
+
visibility = method.get("visibility", "public")
|
|
225
|
+
if name.startswith("__") and name.endswith("__"):
|
|
226
|
+
visibility = "magic"
|
|
227
|
+
elif name.startswith("_"):
|
|
228
|
+
visibility = "private"
|
|
229
|
+
|
|
230
|
+
vis_symbol = self._get_python_visibility_symbol(visibility)
|
|
231
|
+
|
|
190
232
|
line_range = method.get("line_range", {})
|
|
191
|
-
|
|
233
|
+
if not line_range:
|
|
234
|
+
start_line = method.get("start_line", 0)
|
|
235
|
+
end_line = method.get("end_line", 0)
|
|
236
|
+
lines_str = f"{start_line}-{end_line}"
|
|
237
|
+
else:
|
|
238
|
+
lines_str = f"{line_range.get('start', 0)}-{line_range.get('end', 0)}"
|
|
239
|
+
|
|
192
240
|
cols_str = "5-6" # default placeholder
|
|
193
241
|
complexity = method.get("complexity_score", 0)
|
|
242
|
+
|
|
243
|
+
# Use docstring instead of javadoc
|
|
194
244
|
doc = self._clean_csv_text(
|
|
195
|
-
self._extract_doc_summary(str(method.get("
|
|
245
|
+
self._extract_doc_summary(str(method.get("docstring", "")))
|
|
196
246
|
)
|
|
197
|
-
|
|
198
|
-
|
|
247
|
+
|
|
248
|
+
# Add decorators info
|
|
249
|
+
decorators = method.get("modifiers", []) or method.get("decorators", [])
|
|
250
|
+
decorator_str = self._format_decorators(decorators)
|
|
251
|
+
|
|
252
|
+
# Add async indicator
|
|
253
|
+
async_indicator = "🔄" if method.get("is_async", False) else ""
|
|
254
|
+
|
|
255
|
+
return f"| {name}{async_indicator} | {signature} | {vis_symbol} | {lines_str} | {cols_str} | {complexity} | {decorator_str} | {doc} |"
|
|
199
256
|
|
|
200
257
|
def _create_compact_signature(self, method: dict[str, Any]) -> str:
|
|
201
258
|
"""Create compact method signature for Python"""
|
|
@@ -257,3 +314,87 @@ class PythonTableFormatter(BaseTableFormatter):
|
|
|
257
314
|
type_name, type_name[:3] if len(type_name) > 3 else type_name
|
|
258
315
|
)
|
|
259
316
|
return str(result)
|
|
317
|
+
|
|
318
|
+
def _extract_module_docstring(self, data: dict[str, Any]) -> str | None:
|
|
319
|
+
"""Extract module-level docstring"""
|
|
320
|
+
# Look for module docstring in the first few lines
|
|
321
|
+
source_code = data.get("source_code", "")
|
|
322
|
+
if not source_code:
|
|
323
|
+
return None
|
|
324
|
+
|
|
325
|
+
lines = source_code.split("\n")
|
|
326
|
+
for i, line in enumerate(lines[:10]): # Check first 10 lines
|
|
327
|
+
stripped = line.strip()
|
|
328
|
+
if stripped.startswith('"""') or stripped.startswith("'''"):
|
|
329
|
+
quote_type = '"""' if stripped.startswith('"""') else "'''"
|
|
330
|
+
|
|
331
|
+
# Single line docstring
|
|
332
|
+
if stripped.count(quote_type) >= 2:
|
|
333
|
+
return stripped.replace(quote_type, "").strip()
|
|
334
|
+
|
|
335
|
+
# Multi-line docstring
|
|
336
|
+
docstring_lines = [stripped.replace(quote_type, "")]
|
|
337
|
+
for j in range(i + 1, len(lines)):
|
|
338
|
+
next_line = lines[j]
|
|
339
|
+
if quote_type in next_line:
|
|
340
|
+
docstring_lines.append(next_line.replace(quote_type, ""))
|
|
341
|
+
break
|
|
342
|
+
docstring_lines.append(next_line)
|
|
343
|
+
|
|
344
|
+
return "\n".join(docstring_lines).strip()
|
|
345
|
+
|
|
346
|
+
return None
|
|
347
|
+
|
|
348
|
+
def _format_python_signature(self, method: dict[str, Any]) -> str:
|
|
349
|
+
"""Create Python method signature"""
|
|
350
|
+
params = method.get("parameters", [])
|
|
351
|
+
param_strs = []
|
|
352
|
+
|
|
353
|
+
for p in params:
|
|
354
|
+
if isinstance(p, dict):
|
|
355
|
+
param_name = p.get("name", "")
|
|
356
|
+
param_type = p.get("type", "")
|
|
357
|
+
if param_type:
|
|
358
|
+
param_strs.append(f"{param_name}: {param_type}")
|
|
359
|
+
else:
|
|
360
|
+
param_strs.append(param_name)
|
|
361
|
+
else:
|
|
362
|
+
param_strs.append(str(p))
|
|
363
|
+
|
|
364
|
+
params_str = ", ".join(param_strs)
|
|
365
|
+
return_type = method.get("return_type", "")
|
|
366
|
+
|
|
367
|
+
if return_type and return_type != "Any":
|
|
368
|
+
return f"({params_str}) -> {return_type}"
|
|
369
|
+
else:
|
|
370
|
+
return f"({params_str})"
|
|
371
|
+
|
|
372
|
+
def _get_python_visibility_symbol(self, visibility: str) -> str:
|
|
373
|
+
"""Get Python visibility symbol"""
|
|
374
|
+
visibility_map = {
|
|
375
|
+
"public": "🔓",
|
|
376
|
+
"private": "🔒",
|
|
377
|
+
"protected": "🔐",
|
|
378
|
+
"magic": "✨",
|
|
379
|
+
}
|
|
380
|
+
return visibility_map.get(visibility, "🔓")
|
|
381
|
+
|
|
382
|
+
def _format_decorators(self, decorators: list[str]) -> str:
|
|
383
|
+
"""Format Python decorators"""
|
|
384
|
+
if not decorators:
|
|
385
|
+
return "-"
|
|
386
|
+
|
|
387
|
+
# Show important decorators
|
|
388
|
+
important = ["property", "staticmethod", "classmethod", "dataclass", "abstractmethod"]
|
|
389
|
+
shown_decorators = []
|
|
390
|
+
|
|
391
|
+
for dec in decorators:
|
|
392
|
+
if any(imp in dec for imp in important):
|
|
393
|
+
shown_decorators.append(f"@{dec}")
|
|
394
|
+
|
|
395
|
+
if shown_decorators:
|
|
396
|
+
return ", ".join(shown_decorators)
|
|
397
|
+
elif len(decorators) == 1:
|
|
398
|
+
return f"@{decorators[0]}"
|
|
399
|
+
else:
|
|
400
|
+
return f"@{decorators[0]} (+{len(decorators)-1})"
|