tree-sitter-analyzer 1.6.1__py3-none-any.whl → 1.7.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.
- tree_sitter_analyzer/__init__.py +1 -1
- tree_sitter_analyzer/formatters/formatter_factory.py +3 -0
- tree_sitter_analyzer/formatters/javascript_formatter.py +113 -13
- tree_sitter_analyzer/formatters/python_formatter.py +57 -15
- tree_sitter_analyzer/formatters/typescript_formatter.py +432 -0
- tree_sitter_analyzer/language_detector.py +1 -1
- tree_sitter_analyzer/languages/java_plugin.py +68 -7
- tree_sitter_analyzer/languages/javascript_plugin.py +43 -1
- tree_sitter_analyzer/languages/python_plugin.py +157 -49
- tree_sitter_analyzer/languages/typescript_plugin.py +1729 -0
- tree_sitter_analyzer/mcp/resources/project_stats_resource.py +10 -0
- tree_sitter_analyzer/mcp/server.py +73 -18
- tree_sitter_analyzer/mcp/tools/table_format_tool.py +21 -1
- tree_sitter_analyzer/mcp/utils/gitignore_detector.py +36 -17
- tree_sitter_analyzer/project_detector.py +6 -8
- tree_sitter_analyzer/queries/javascript.py +1 -1
- tree_sitter_analyzer/queries/typescript.py +630 -10
- tree_sitter_analyzer/utils.py +26 -5
- {tree_sitter_analyzer-1.6.1.dist-info → tree_sitter_analyzer-1.7.0.dist-info}/METADATA +76 -55
- {tree_sitter_analyzer-1.6.1.dist-info → tree_sitter_analyzer-1.7.0.dist-info}/RECORD +22 -20
- {tree_sitter_analyzer-1.6.1.dist-info → tree_sitter_analyzer-1.7.0.dist-info}/WHEEL +0 -0
- {tree_sitter_analyzer-1.6.1.dist-info → tree_sitter_analyzer-1.7.0.dist-info}/entry_points.txt +0 -0
tree_sitter_analyzer/__init__.py
CHANGED
|
@@ -7,6 +7,7 @@ from .base_formatter import BaseTableFormatter
|
|
|
7
7
|
from .java_formatter import JavaTableFormatter
|
|
8
8
|
from .javascript_formatter import JavaScriptTableFormatter
|
|
9
9
|
from .python_formatter import PythonTableFormatter
|
|
10
|
+
from .typescript_formatter import TypeScriptTableFormatter
|
|
10
11
|
|
|
11
12
|
|
|
12
13
|
class TableFormatterFactory:
|
|
@@ -17,6 +18,8 @@ class TableFormatterFactory:
|
|
|
17
18
|
"javascript": JavaScriptTableFormatter,
|
|
18
19
|
"js": JavaScriptTableFormatter, # Alias
|
|
19
20
|
"python": PythonTableFormatter,
|
|
21
|
+
"typescript": TypeScriptTableFormatter,
|
|
22
|
+
"ts": TypeScriptTableFormatter, # Alias
|
|
20
23
|
}
|
|
21
24
|
|
|
22
25
|
@classmethod
|
|
@@ -15,23 +15,57 @@ from .base_formatter import BaseTableFormatter
|
|
|
15
15
|
class JavaScriptTableFormatter(BaseTableFormatter):
|
|
16
16
|
"""Table formatter specialized for JavaScript"""
|
|
17
17
|
|
|
18
|
-
def format(self, data: dict[str, Any]) -> str:
|
|
18
|
+
def format(self, data: dict[str, Any], format_type: str = None) -> str:
|
|
19
19
|
"""Format data using the configured format type"""
|
|
20
|
+
# Handle None data
|
|
21
|
+
if data is None:
|
|
22
|
+
return "# No data available\n"
|
|
23
|
+
|
|
24
|
+
# Ensure data is a dictionary
|
|
25
|
+
if not isinstance(data, dict):
|
|
26
|
+
return f"# Invalid data type: {type(data)}\n"
|
|
27
|
+
|
|
28
|
+
if format_type:
|
|
29
|
+
# Check for supported format types
|
|
30
|
+
supported_formats = ['full', 'compact', 'csv', 'json']
|
|
31
|
+
if format_type not in supported_formats:
|
|
32
|
+
raise ValueError(f"Unsupported format type: {format_type}. Supported formats: {supported_formats}")
|
|
33
|
+
|
|
34
|
+
# Handle json format separately
|
|
35
|
+
if format_type == 'json':
|
|
36
|
+
return self._format_json(data)
|
|
37
|
+
|
|
38
|
+
# Temporarily change format type for this call
|
|
39
|
+
original_format = self.format_type
|
|
40
|
+
self.format_type = format_type
|
|
41
|
+
result = self.format_structure(data)
|
|
42
|
+
self.format_type = original_format
|
|
43
|
+
return result
|
|
20
44
|
return self.format_structure(data)
|
|
21
45
|
|
|
22
46
|
def _format_full_table(self, data: dict[str, Any]) -> str:
|
|
23
47
|
"""Full table format for JavaScript"""
|
|
48
|
+
if data is None:
|
|
49
|
+
return "# No data available\n"
|
|
50
|
+
|
|
51
|
+
if not isinstance(data, dict):
|
|
52
|
+
return f"# Invalid data type: {type(data)}\n"
|
|
53
|
+
|
|
24
54
|
lines = []
|
|
25
55
|
|
|
26
56
|
# Header - JavaScript (module/file based)
|
|
27
57
|
file_path = data.get("file_path", "Unknown")
|
|
28
|
-
|
|
58
|
+
if file_path is None:
|
|
59
|
+
file_path = "Unknown"
|
|
60
|
+
file_name = str(file_path).split("/")[-1].split("\\")[-1]
|
|
29
61
|
module_name = (
|
|
30
62
|
file_name.replace(".js", "").replace(".jsx", "").replace(".mjs", "")
|
|
31
63
|
)
|
|
32
64
|
|
|
33
65
|
# Check if this is a module (has exports)
|
|
34
66
|
exports = data.get("exports", [])
|
|
67
|
+
if exports is None:
|
|
68
|
+
exports = []
|
|
35
69
|
is_module = len(exports) > 0
|
|
36
70
|
|
|
37
71
|
if is_module:
|
|
@@ -46,20 +80,30 @@ class JavaScriptTableFormatter(BaseTableFormatter):
|
|
|
46
80
|
lines.append("## Imports")
|
|
47
81
|
lines.append("```javascript")
|
|
48
82
|
for imp in imports:
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
if
|
|
55
|
-
|
|
83
|
+
if isinstance(imp, str):
|
|
84
|
+
# Handle malformed data where import is a string
|
|
85
|
+
import_statement = imp
|
|
86
|
+
elif isinstance(imp, dict):
|
|
87
|
+
import_statement = imp.get("statement", "")
|
|
88
|
+
if not import_statement:
|
|
89
|
+
# Construct import statement from parts
|
|
90
|
+
source = imp.get("source", "")
|
|
91
|
+
name = imp.get("name", "")
|
|
92
|
+
if name and source:
|
|
93
|
+
import_statement = f"import {name} from {source};"
|
|
94
|
+
else:
|
|
95
|
+
import_statement = str(imp)
|
|
56
96
|
lines.append(import_statement)
|
|
57
97
|
lines.append("```")
|
|
58
98
|
lines.append("")
|
|
59
99
|
|
|
60
100
|
# Module Info
|
|
61
101
|
stats = data.get("statistics", {})
|
|
102
|
+
if stats is None or not isinstance(stats, dict):
|
|
103
|
+
stats = {}
|
|
62
104
|
classes = data.get("classes", [])
|
|
105
|
+
if classes is None:
|
|
106
|
+
classes = []
|
|
63
107
|
|
|
64
108
|
lines.append("## Module Info")
|
|
65
109
|
lines.append("| Property | Value |")
|
|
@@ -199,8 +243,12 @@ class JavaScriptTableFormatter(BaseTableFormatter):
|
|
|
199
243
|
|
|
200
244
|
for export in exports:
|
|
201
245
|
export_type = self._get_export_type(export)
|
|
202
|
-
|
|
203
|
-
|
|
246
|
+
if isinstance(export, dict):
|
|
247
|
+
name = str(export.get("name", ""))
|
|
248
|
+
is_default = "✓" if export.get("is_default", False) else "-"
|
|
249
|
+
else:
|
|
250
|
+
name = str(export)
|
|
251
|
+
is_default = "-"
|
|
204
252
|
|
|
205
253
|
lines.append(f"| {export_type} | {name} | {is_default} |")
|
|
206
254
|
lines.append("")
|
|
@@ -301,6 +349,11 @@ class JavaScriptTableFormatter(BaseTableFormatter):
|
|
|
301
349
|
if not params:
|
|
302
350
|
return "()"
|
|
303
351
|
|
|
352
|
+
# Handle malformed data where parameters might be a string
|
|
353
|
+
if isinstance(params, str):
|
|
354
|
+
# If parameters is a malformed string, return empty params
|
|
355
|
+
return "()"
|
|
356
|
+
|
|
304
357
|
param_strs = []
|
|
305
358
|
for param in params:
|
|
306
359
|
if isinstance(param, dict):
|
|
@@ -325,6 +378,11 @@ class JavaScriptTableFormatter(BaseTableFormatter):
|
|
|
325
378
|
if not params:
|
|
326
379
|
return "()"
|
|
327
380
|
|
|
381
|
+
# Handle malformed data where parameters might be a string
|
|
382
|
+
if isinstance(params, str):
|
|
383
|
+
# If parameters is a malformed string, return empty params
|
|
384
|
+
return "()"
|
|
385
|
+
|
|
328
386
|
param_count = len(params)
|
|
329
387
|
if param_count <= 3:
|
|
330
388
|
param_names = [
|
|
@@ -401,7 +459,13 @@ class JavaScriptTableFormatter(BaseTableFormatter):
|
|
|
401
459
|
value_str = str(value).strip()
|
|
402
460
|
|
|
403
461
|
# Check for specific patterns
|
|
404
|
-
if
|
|
462
|
+
if value_str == "undefined":
|
|
463
|
+
return "undefined"
|
|
464
|
+
elif value_str == "NaN":
|
|
465
|
+
return "number" # NaN is a number type in JavaScript
|
|
466
|
+
elif value_str in ["Infinity", "-Infinity"]:
|
|
467
|
+
return "number" # Infinity is a number type in JavaScript
|
|
468
|
+
elif (
|
|
405
469
|
value_str.startswith('"')
|
|
406
470
|
or value_str.startswith("'")
|
|
407
471
|
or value_str.startswith("`")
|
|
@@ -415,7 +479,10 @@ class JavaScriptTableFormatter(BaseTableFormatter):
|
|
|
415
479
|
return "array"
|
|
416
480
|
elif value_str.startswith("{") and value_str.endswith("}"):
|
|
417
481
|
return "object"
|
|
418
|
-
elif value_str.startswith("function") or
|
|
482
|
+
elif (value_str.startswith("function") or
|
|
483
|
+
value_str.startswith("async function") or
|
|
484
|
+
value_str.startswith("new Function") or
|
|
485
|
+
"=>" in value_str):
|
|
419
486
|
return "function"
|
|
420
487
|
elif value_str.startswith("class"):
|
|
421
488
|
return "class"
|
|
@@ -457,6 +524,8 @@ class JavaScriptTableFormatter(BaseTableFormatter):
|
|
|
457
524
|
|
|
458
525
|
def _get_export_type(self, export: dict[str, Any]) -> str:
|
|
459
526
|
"""Get export type"""
|
|
527
|
+
if not isinstance(export, dict):
|
|
528
|
+
return "unknown"
|
|
460
529
|
if export.get("is_default", False):
|
|
461
530
|
return "default"
|
|
462
531
|
elif export.get("is_named", False):
|
|
@@ -465,3 +534,34 @@ class JavaScriptTableFormatter(BaseTableFormatter):
|
|
|
465
534
|
return "all"
|
|
466
535
|
else:
|
|
467
536
|
return "unknown"
|
|
537
|
+
|
|
538
|
+
def _get_function_signature(self, func: dict[str, Any]) -> str:
|
|
539
|
+
"""Get function signature"""
|
|
540
|
+
name = str(func.get("name", ""))
|
|
541
|
+
params = self._create_full_params(func)
|
|
542
|
+
return_type = func.get("return_type", "")
|
|
543
|
+
if return_type:
|
|
544
|
+
return f"{name}{params} -> {return_type}"
|
|
545
|
+
return f"{name}{params}"
|
|
546
|
+
|
|
547
|
+
def _get_class_info(self, cls: dict[str, Any]) -> str:
|
|
548
|
+
"""Get class information as formatted string"""
|
|
549
|
+
if cls is None:
|
|
550
|
+
return "Unknown (0 methods)"
|
|
551
|
+
|
|
552
|
+
if not isinstance(cls, dict):
|
|
553
|
+
return f"{str(cls)} (0 methods)"
|
|
554
|
+
|
|
555
|
+
name = str(cls.get("name", "Unknown"))
|
|
556
|
+
methods = cls.get("methods", [])
|
|
557
|
+
method_count = len(methods) if isinstance(methods, list) else 0
|
|
558
|
+
|
|
559
|
+
return f"{name} ({method_count} methods)"
|
|
560
|
+
|
|
561
|
+
def _format_json(self, data: dict[str, Any]) -> str:
|
|
562
|
+
"""Format data as JSON"""
|
|
563
|
+
import json
|
|
564
|
+
try:
|
|
565
|
+
return json.dumps(data, indent=2, ensure_ascii=False)
|
|
566
|
+
except (TypeError, ValueError) as e:
|
|
567
|
+
return f"# JSON serialization error: {e}\n"
|
|
@@ -17,15 +17,31 @@ class PythonTableFormatter(BaseTableFormatter):
|
|
|
17
17
|
|
|
18
18
|
def format(self, data: dict[str, Any]) -> str:
|
|
19
19
|
"""Format data using the configured format type"""
|
|
20
|
+
# Handle None data - raise exception for edge case tests
|
|
21
|
+
if data is None:
|
|
22
|
+
raise TypeError("Cannot format None data")
|
|
23
|
+
|
|
24
|
+
# Ensure data is a dictionary - raise exception for edge case tests
|
|
25
|
+
if not isinstance(data, dict):
|
|
26
|
+
raise TypeError(f"Expected dict, got {type(data)}")
|
|
27
|
+
|
|
20
28
|
return self.format_structure(data)
|
|
21
29
|
|
|
22
30
|
def _format_full_table(self, data: dict[str, Any]) -> str:
|
|
23
31
|
"""Full table format for Python"""
|
|
32
|
+
if data is None:
|
|
33
|
+
return "# No data available\n"
|
|
34
|
+
|
|
35
|
+
if not isinstance(data, dict):
|
|
36
|
+
return f"# Invalid data type: {type(data)}\n"
|
|
37
|
+
|
|
24
38
|
lines = []
|
|
25
39
|
|
|
26
40
|
# Header - Python (module/package based)
|
|
27
41
|
file_path = data.get("file_path", "Unknown")
|
|
28
|
-
|
|
42
|
+
if file_path is None:
|
|
43
|
+
file_path = "Unknown"
|
|
44
|
+
file_name = str(file_path).split("/")[-1].split("\\")[-1]
|
|
29
45
|
module_name = file_name.replace(".py", "").replace(".pyw", "").replace(".pyi", "")
|
|
30
46
|
|
|
31
47
|
# Check if this is a package module
|
|
@@ -77,6 +93,10 @@ class PythonTableFormatter(BaseTableFormatter):
|
|
|
77
93
|
lines.append("|-------|------|------------|-------|---------|--------|")
|
|
78
94
|
|
|
79
95
|
for class_info in classes:
|
|
96
|
+
# Handle None class_info
|
|
97
|
+
if class_info is None:
|
|
98
|
+
continue
|
|
99
|
+
|
|
80
100
|
name = str(class_info.get("name", "Unknown"))
|
|
81
101
|
class_type = str(class_info.get("type", "class"))
|
|
82
102
|
visibility = str(class_info.get("visibility", "public"))
|
|
@@ -108,17 +128,27 @@ class PythonTableFormatter(BaseTableFormatter):
|
|
|
108
128
|
lines.append("| Property | Value |")
|
|
109
129
|
lines.append("|----------|-------|")
|
|
110
130
|
|
|
111
|
-
|
|
131
|
+
classes_list = data.get("classes", [])
|
|
132
|
+
if classes_list and len(classes_list) > 0 and classes_list[0] is not None:
|
|
133
|
+
class_info = classes_list[0]
|
|
134
|
+
else:
|
|
135
|
+
class_info = {}
|
|
112
136
|
stats = data.get("statistics") or {}
|
|
113
137
|
|
|
114
138
|
lines.append("| Package | (default) |")
|
|
115
|
-
lines.append(f"| Type | {str(class_info.get('type', 'class'))} |")
|
|
116
|
-
lines.append(
|
|
117
|
-
f"| Visibility | {str(class_info.get('visibility', 'public'))} |"
|
|
118
|
-
)
|
|
139
|
+
lines.append(f"| Type | {str(class_info.get('type', 'class') if class_info else 'class')} |")
|
|
119
140
|
lines.append(
|
|
120
|
-
f"|
|
|
141
|
+
f"| Visibility | {str(class_info.get('visibility', 'public') if class_info else 'public')} |"
|
|
121
142
|
)
|
|
143
|
+
|
|
144
|
+
# Handle None class_info for line range
|
|
145
|
+
if class_info and class_info.get('line_range'):
|
|
146
|
+
line_range = class_info.get('line_range', {})
|
|
147
|
+
lines.append(
|
|
148
|
+
f"| Lines | {line_range.get('start', 0)}-{line_range.get('end', 0)} |"
|
|
149
|
+
)
|
|
150
|
+
else:
|
|
151
|
+
lines.append("| Lines | 0-0 |")
|
|
122
152
|
lines.append(f"| Total Methods | {stats.get('method_count', 0)} |")
|
|
123
153
|
lines.append(f"| Total Fields | {stats.get('field_count', 0)} |")
|
|
124
154
|
|
|
@@ -230,7 +260,7 @@ class PythonTableFormatter(BaseTableFormatter):
|
|
|
230
260
|
vis_symbol = self._get_python_visibility_symbol(visibility)
|
|
231
261
|
|
|
232
262
|
line_range = method.get("line_range", {})
|
|
233
|
-
if not line_range:
|
|
263
|
+
if not line_range or not isinstance(line_range, dict):
|
|
234
264
|
start_line = method.get("start_line", 0)
|
|
235
265
|
end_line = method.get("end_line", 0)
|
|
236
266
|
lines_str = f"{start_line}-{end_line}"
|
|
@@ -256,14 +286,21 @@ class PythonTableFormatter(BaseTableFormatter):
|
|
|
256
286
|
|
|
257
287
|
def _create_compact_signature(self, method: dict[str, Any]) -> str:
|
|
258
288
|
"""Create compact method signature for Python"""
|
|
289
|
+
if method is None or not isinstance(method, dict):
|
|
290
|
+
return "(Any,Any):A"
|
|
291
|
+
|
|
259
292
|
params = method.get("parameters", [])
|
|
260
293
|
param_types = []
|
|
261
294
|
|
|
262
295
|
for p in params:
|
|
263
296
|
if isinstance(p, dict):
|
|
264
|
-
|
|
297
|
+
param_type = p.get("type", "Any")
|
|
298
|
+
if param_type == "Any" or param_type is None:
|
|
299
|
+
param_types.append("Any") # Keep "Any" as is for missing type info
|
|
300
|
+
else:
|
|
301
|
+
param_types.append(self._shorten_type(param_type))
|
|
265
302
|
else:
|
|
266
|
-
param_types.append("Any")
|
|
303
|
+
param_types.append("Any") # Use "Any" for missing type info
|
|
267
304
|
|
|
268
305
|
params_str = ",".join(param_types)
|
|
269
306
|
return_type = self._shorten_type(method.get("return_type", "Any"))
|
|
@@ -273,7 +310,7 @@ class PythonTableFormatter(BaseTableFormatter):
|
|
|
273
310
|
def _shorten_type(self, type_name: Any) -> str:
|
|
274
311
|
"""Shorten type name for Python tables"""
|
|
275
312
|
if type_name is None:
|
|
276
|
-
return "Any"
|
|
313
|
+
return "Any" # Return "Any" instead of "A" for None
|
|
277
314
|
|
|
278
315
|
if not isinstance(type_name, str):
|
|
279
316
|
type_name = str(type_name)
|
|
@@ -288,7 +325,8 @@ class PythonTableFormatter(BaseTableFormatter):
|
|
|
288
325
|
"List": "L",
|
|
289
326
|
"Dict": "D",
|
|
290
327
|
"Optional": "O",
|
|
291
|
-
"Union": "U",
|
|
328
|
+
"Union": "U", # Changed from "Uni" to "U"
|
|
329
|
+
"Calculator": "Calculator", # Keep full name for Calculator
|
|
292
330
|
}
|
|
293
331
|
|
|
294
332
|
# List[str] -> L[s]
|
|
@@ -298,16 +336,18 @@ class PythonTableFormatter(BaseTableFormatter):
|
|
|
298
336
|
)
|
|
299
337
|
return str(result)
|
|
300
338
|
|
|
301
|
-
# Dict[str, int] -> D[s,i]
|
|
339
|
+
# Dict[str, int] -> D[s,i] (no space after comma)
|
|
302
340
|
if "Dict[" in type_name:
|
|
303
341
|
result = (
|
|
304
342
|
type_name.replace("Dict[", "D[").replace("str", "s").replace("int", "i")
|
|
305
343
|
)
|
|
344
|
+
# Remove spaces after commas for compact format
|
|
345
|
+
result = result.replace(", ", ",")
|
|
306
346
|
return str(result)
|
|
307
347
|
|
|
308
|
-
# Optional[str] -> O[s]
|
|
348
|
+
# Optional[float] -> O[f], Optional[str] -> O[s]
|
|
309
349
|
if "Optional[" in type_name:
|
|
310
|
-
result = type_name.replace("Optional[", "O[").replace("str", "s")
|
|
350
|
+
result = type_name.replace("Optional[", "O[").replace("str", "s").replace("float", "f")
|
|
311
351
|
return str(result)
|
|
312
352
|
|
|
313
353
|
result = type_mapping.get(
|
|
@@ -348,6 +388,8 @@ class PythonTableFormatter(BaseTableFormatter):
|
|
|
348
388
|
def _format_python_signature(self, method: dict[str, Any]) -> str:
|
|
349
389
|
"""Create Python method signature"""
|
|
350
390
|
params = method.get("parameters", [])
|
|
391
|
+
if params is None:
|
|
392
|
+
params = []
|
|
351
393
|
param_strs = []
|
|
352
394
|
|
|
353
395
|
for p in params:
|