tree-sitter-analyzer 1.4.1__py3-none-any.whl → 1.5.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.

@@ -0,0 +1,467 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ JavaScript-specific table formatter.
4
+
5
+ Provides specialized formatting for JavaScript code analysis results,
6
+ handling modern JavaScript features like ES6+ syntax, async/await,
7
+ classes, modules, and framework-specific patterns.
8
+ """
9
+
10
+ from typing import Any
11
+
12
+ from .base_formatter import BaseTableFormatter
13
+
14
+
15
+ class JavaScriptTableFormatter(BaseTableFormatter):
16
+ """Table formatter specialized for JavaScript"""
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
+
22
+ def _format_full_table(self, data: dict[str, Any]) -> str:
23
+ """Full table format for JavaScript"""
24
+ lines = []
25
+
26
+ # Header - JavaScript (module/file based)
27
+ file_path = data.get("file_path", "Unknown")
28
+ file_name = file_path.split("/")[-1].split("\\")[-1]
29
+ module_name = (
30
+ file_name.replace(".js", "").replace(".jsx", "").replace(".mjs", "")
31
+ )
32
+
33
+ # Check if this is a module (has exports)
34
+ exports = data.get("exports", [])
35
+ is_module = len(exports) > 0
36
+
37
+ if is_module:
38
+ lines.append(f"# Module: {module_name}")
39
+ else:
40
+ lines.append(f"# Script: {module_name}")
41
+ lines.append("")
42
+
43
+ # Imports
44
+ imports = data.get("imports", [])
45
+ if imports:
46
+ lines.append("## Imports")
47
+ lines.append("```javascript")
48
+ for imp in imports:
49
+ import_statement = imp.get("statement", "")
50
+ if not import_statement:
51
+ # Construct import statement from parts
52
+ source = imp.get("source", "")
53
+ name = imp.get("name", "")
54
+ if name and source:
55
+ import_statement = f"import {name} from {source};"
56
+ lines.append(import_statement)
57
+ lines.append("```")
58
+ lines.append("")
59
+
60
+ # Module Info
61
+ stats = data.get("statistics", {})
62
+ classes = data.get("classes", [])
63
+
64
+ lines.append("## Module Info")
65
+ lines.append("| Property | Value |")
66
+ lines.append("|----------|-------|")
67
+ lines.append(f"| File | {file_name} |")
68
+ lines.append(f"| Type | {'ES6 Module' if is_module else 'Script'} |")
69
+ lines.append(f"| Functions | {stats.get('function_count', 0)} |")
70
+ lines.append(f"| Classes | {len(classes)} |")
71
+ lines.append(f"| Variables | {stats.get('variable_count', 0)} |")
72
+ lines.append(f"| Exports | {len(exports)} |")
73
+ lines.append("")
74
+
75
+ # Classes (if any)
76
+ if classes:
77
+ lines.append("## Classes")
78
+ lines.append("| Class | Type | Extends | Lines | Methods | Properties |")
79
+ lines.append("|-------|------|---------|-------|---------|------------|")
80
+
81
+ for class_info in classes:
82
+ name = str(class_info.get("name", "Unknown"))
83
+ class_type = "class" # JavaScript only has classes
84
+ extends = str(class_info.get("superclass", "")) or "-"
85
+ line_range = class_info.get("line_range", {})
86
+ lines_str = f"{line_range.get('start', 0)}-{line_range.get('end', 0)}"
87
+
88
+ # Count methods within the class
89
+ class_methods = [
90
+ m
91
+ for m in data.get("methods", [])
92
+ if line_range.get("start", 0)
93
+ <= m.get("line_range", {}).get("start", 0)
94
+ <= line_range.get("end", 0)
95
+ ]
96
+
97
+ # Count properties (class fields)
98
+ class_properties = [
99
+ v
100
+ for v in data.get("variables", [])
101
+ if line_range.get("start", 0)
102
+ <= v.get("line_range", {}).get("start", 0)
103
+ <= line_range.get("end", 0)
104
+ ]
105
+
106
+ lines.append(
107
+ f"| {name} | {class_type} | {extends} | {lines_str} | {len(class_methods)} | {len(class_properties)} |"
108
+ )
109
+ lines.append("")
110
+
111
+ # Variables/Constants
112
+ variables = data.get("variables", [])
113
+ if variables:
114
+ lines.append("## Variables")
115
+ lines.append("| Name | Type | Scope | Kind | Line | Value |")
116
+ lines.append("|------|------|-------|------|------|-------|")
117
+
118
+ for var in variables:
119
+ name = str(var.get("name", ""))
120
+ # Try to get value from initializer or value field
121
+ var_value = var.get("initializer") or var.get("value", "")
122
+ var_type = self._infer_js_type(var_value)
123
+ scope = self._determine_scope(var)
124
+ kind = self._get_variable_kind(var)
125
+ line = var.get("line_range", {}).get("start", 0)
126
+ value = str(var_value)[:30] + (
127
+ "..." if len(str(var_value)) > 30 else ""
128
+ )
129
+ value = value.replace("\n", " ").replace("|", "\\|")
130
+
131
+ lines.append(
132
+ f"| {name} | {var_type} | {scope} | {kind} | {line} | {value} |"
133
+ )
134
+ lines.append("")
135
+
136
+ # Functions
137
+ functions = data.get("functions", [])
138
+ if functions:
139
+ # Group functions by type
140
+ regular_functions = [
141
+ f
142
+ for f in functions
143
+ if not self._is_method(f) and not f.get("is_async", False)
144
+ ]
145
+ async_functions = [
146
+ f
147
+ for f in functions
148
+ if not self._is_method(f) and f.get("is_async", False)
149
+ ]
150
+ methods = [f for f in functions if self._is_method(f)]
151
+
152
+ # Regular Functions
153
+ if regular_functions:
154
+ lines.append("## Functions")
155
+ lines.append(
156
+ "| Function | Parameters | Type | Lines | Complexity | JSDoc |"
157
+ )
158
+ lines.append(
159
+ "|----------|------------|------|-------|------------|-------|"
160
+ )
161
+
162
+ for func in regular_functions:
163
+ lines.append(self._format_function_row(func))
164
+ lines.append("")
165
+
166
+ # Async Functions
167
+ if async_functions:
168
+ lines.append("## Async Functions")
169
+ lines.append(
170
+ "| Function | Parameters | Type | Lines | Complexity | JSDoc |"
171
+ )
172
+ lines.append(
173
+ "|----------|------------|------|-------|------------|-------|"
174
+ )
175
+
176
+ for func in async_functions:
177
+ lines.append(self._format_function_row(func))
178
+ lines.append("")
179
+
180
+ # Methods (class methods)
181
+ if methods:
182
+ lines.append("## Methods")
183
+ lines.append(
184
+ "| Method | Class | Parameters | Type | Lines | Complexity | JSDoc |"
185
+ )
186
+ lines.append(
187
+ "|--------|-------|------------|------|-------|------------|-------|"
188
+ )
189
+
190
+ for method in methods:
191
+ lines.append(self._format_method_row(method))
192
+ lines.append("")
193
+
194
+ # Exports
195
+ if exports:
196
+ lines.append("## Exports")
197
+ lines.append("| Export | Type | Name | Default |")
198
+ lines.append("|--------|------|------|---------|")
199
+
200
+ for export in exports:
201
+ export_type = self._get_export_type(export)
202
+ name = str(export.get("name", ""))
203
+ is_default = "✓" if export.get("is_default", False) else "-"
204
+
205
+ lines.append(f"| {export_type} | {name} | {is_default} |")
206
+ lines.append("")
207
+
208
+ # Trim trailing blank lines
209
+ while lines and lines[-1] == "":
210
+ lines.pop()
211
+
212
+ return "\n".join(lines)
213
+
214
+ def _format_compact_table(self, data: dict[str, Any]) -> str:
215
+ """Compact table format for JavaScript"""
216
+ lines = []
217
+
218
+ # Header
219
+ file_path = data.get("file_path", "Unknown")
220
+ file_name = file_path.split("/")[-1].split("\\")[-1]
221
+ module_name = (
222
+ file_name.replace(".js", "").replace(".jsx", "").replace(".mjs", "")
223
+ )
224
+ lines.append(f"# {module_name}")
225
+ lines.append("")
226
+
227
+ # Info
228
+ stats = data.get("statistics", {})
229
+ lines.append("## Info")
230
+ lines.append("| Property | Value |")
231
+ lines.append("|----------|-------|")
232
+ lines.append(f"| Functions | {stats.get('function_count', 0)} |")
233
+ lines.append(f"| Classes | {len(data.get('classes', []))} |")
234
+ lines.append(f"| Variables | {stats.get('variable_count', 0)} |")
235
+ lines.append(f"| Exports | {len(data.get('exports', []))} |")
236
+ lines.append("")
237
+
238
+ # Functions (compact)
239
+ functions = data.get("functions", [])
240
+ if functions:
241
+ lines.append("## Functions")
242
+ lines.append("| Function | Params | Type | L | Cx | Doc |")
243
+ lines.append("|----------|--------|------|---|----|----|")
244
+
245
+ for func in functions:
246
+ name = str(func.get("name", ""))
247
+ params = self._create_compact_params(func)
248
+ func_type = self._get_function_type_short(func)
249
+ line_range = func.get("line_range", {})
250
+ lines_str = f"{line_range.get('start', 0)}-{line_range.get('end', 0)}"
251
+ complexity = func.get("complexity_score", 0)
252
+ doc = self._clean_csv_text(
253
+ self._extract_doc_summary(str(func.get("jsdoc", "")))
254
+ )
255
+
256
+ lines.append(
257
+ f"| {name} | {params} | {func_type} | {lines_str} | {complexity} | {doc} |"
258
+ )
259
+ lines.append("")
260
+
261
+ # Trim trailing blank lines
262
+ while lines and lines[-1] == "":
263
+ lines.pop()
264
+
265
+ return "\n".join(lines)
266
+
267
+ def _format_function_row(self, func: dict[str, Any]) -> str:
268
+ """Format a function table row for JavaScript"""
269
+ name = str(func.get("name", ""))
270
+ params = self._create_full_params(func)
271
+ func_type = self._get_function_type(func)
272
+ line_range = func.get("line_range", {})
273
+ lines_str = f"{line_range.get('start', 0)}-{line_range.get('end', 0)}"
274
+ complexity = func.get("complexity_score", 0)
275
+ doc = self._clean_csv_text(
276
+ self._extract_doc_summary(str(func.get("jsdoc", "")))
277
+ )
278
+
279
+ return (
280
+ f"| {name} | {params} | {func_type} | {lines_str} | {complexity} | {doc} |"
281
+ )
282
+
283
+ def _format_method_row(self, method: dict[str, Any]) -> str:
284
+ """Format a method table row for JavaScript"""
285
+ name = str(method.get("name", ""))
286
+ class_name = self._get_method_class(method)
287
+ params = self._create_full_params(method)
288
+ method_type = self._get_method_type(method)
289
+ line_range = method.get("line_range", {})
290
+ lines_str = f"{line_range.get('start', 0)}-{line_range.get('end', 0)}"
291
+ complexity = method.get("complexity_score", 0)
292
+ doc = self._clean_csv_text(
293
+ self._extract_doc_summary(str(method.get("jsdoc", "")))
294
+ )
295
+
296
+ return f"| {name} | {class_name} | {params} | {method_type} | {lines_str} | {complexity} | {doc} |"
297
+
298
+ def _create_full_params(self, func: dict[str, Any]) -> str:
299
+ """Create full parameter list for JavaScript functions"""
300
+ params = func.get("parameters", [])
301
+ if not params:
302
+ return "()"
303
+
304
+ param_strs = []
305
+ for param in params:
306
+ if isinstance(param, dict):
307
+ param_name = param.get("name", "")
308
+ param_type = param.get("type", "")
309
+ if param_type:
310
+ param_strs.append(f"{param_name}: {param_type}")
311
+ else:
312
+ param_strs.append(param_name)
313
+ else:
314
+ param_strs.append(str(param))
315
+
316
+ params_str = ", ".join(param_strs)
317
+ if len(params_str) > 50:
318
+ params_str = params_str[:47] + "..."
319
+
320
+ return f"({params_str})"
321
+
322
+ def _create_compact_params(self, func: dict[str, Any]) -> str:
323
+ """Create compact parameter list for JavaScript functions"""
324
+ params = func.get("parameters", [])
325
+ if not params:
326
+ return "()"
327
+
328
+ param_count = len(params)
329
+ if param_count <= 3:
330
+ param_names = [
331
+ param.get("name", str(param)) if isinstance(param, dict) else str(param)
332
+ for param in params
333
+ ]
334
+ return f"({','.join(param_names)})"
335
+ else:
336
+ return f"({param_count} params)"
337
+
338
+ def _get_function_type(self, func: dict[str, Any]) -> str:
339
+ """Get full function type for JavaScript"""
340
+ if func.get("is_async", False):
341
+ return "async function"
342
+ elif func.get("is_generator", False):
343
+ return "generator"
344
+ elif func.get("is_arrow", False):
345
+ return "arrow"
346
+ elif self._is_method(func):
347
+ if func.get("is_constructor", False):
348
+ return "constructor"
349
+ elif func.get("is_getter", False):
350
+ return "getter"
351
+ elif func.get("is_setter", False):
352
+ return "setter"
353
+ elif func.get("is_static", False):
354
+ return "static method"
355
+ else:
356
+ return "method"
357
+ else:
358
+ return "function"
359
+
360
+ def _get_function_type_short(self, func: dict[str, Any]) -> str:
361
+ """Get short function type for JavaScript"""
362
+ if func.get("is_async", False):
363
+ return "async"
364
+ elif func.get("is_generator", False):
365
+ return "gen"
366
+ elif func.get("is_arrow", False):
367
+ return "arrow"
368
+ elif self._is_method(func):
369
+ return "method"
370
+ else:
371
+ return "func"
372
+
373
+ def _get_method_type(self, method: dict[str, Any]) -> str:
374
+ """Get method type for JavaScript"""
375
+ if method.get("is_constructor", False):
376
+ return "constructor"
377
+ elif method.get("is_getter", False):
378
+ return "getter"
379
+ elif method.get("is_setter", False):
380
+ return "setter"
381
+ elif method.get("is_static", False):
382
+ return "static"
383
+ elif method.get("is_async", False):
384
+ return "async"
385
+ else:
386
+ return "method"
387
+
388
+ def _is_method(self, func: dict[str, Any]) -> bool:
389
+ """Check if function is a class method"""
390
+ return func.get("is_method", False) or func.get("class_name") is not None
391
+
392
+ def _get_method_class(self, method: dict[str, Any]) -> str:
393
+ """Get the class name for a method"""
394
+ return str(method.get("class_name", "Unknown"))
395
+
396
+ def _infer_js_type(self, value: Any) -> str:
397
+ """Infer JavaScript type from value"""
398
+ if value is None:
399
+ return "undefined"
400
+
401
+ value_str = str(value).strip()
402
+
403
+ # Check for specific patterns
404
+ if (
405
+ value_str.startswith('"')
406
+ or value_str.startswith("'")
407
+ or value_str.startswith("`")
408
+ ):
409
+ return "string"
410
+ elif value_str in ["true", "false"]:
411
+ return "boolean"
412
+ elif value_str == "null":
413
+ return "null"
414
+ elif value_str.startswith("[") and value_str.endswith("]"):
415
+ return "array"
416
+ elif value_str.startswith("{") and value_str.endswith("}"):
417
+ return "object"
418
+ elif value_str.startswith("function") or "=>" in value_str:
419
+ return "function"
420
+ elif value_str.startswith("class"):
421
+ return "class"
422
+ elif value_str.replace(".", "").replace("-", "").isdigit():
423
+ return "number"
424
+ else:
425
+ return "unknown"
426
+
427
+ def _determine_scope(self, var: dict[str, Any]) -> str:
428
+ """Determine variable scope"""
429
+ # This would need more context from the parser
430
+ # For now, return basic scope info
431
+ kind = self._get_variable_kind(var)
432
+ if kind == "const":
433
+ return "block"
434
+ elif kind == "let":
435
+ return "block"
436
+ elif kind == "var":
437
+ return "function"
438
+ else:
439
+ return "unknown"
440
+
441
+ def _get_variable_kind(self, var: dict[str, Any]) -> str:
442
+ """Get variable declaration kind (const, let, var)"""
443
+ # Check if variable has is_constant flag
444
+ if var.get("is_constant", False):
445
+ return "const"
446
+
447
+ # Fall back to parsing raw text
448
+ raw_text = str(var.get("raw_text", "")).strip()
449
+ if raw_text.startswith("const"):
450
+ return "const"
451
+ elif raw_text.startswith("let"):
452
+ return "let"
453
+ elif raw_text.startswith("var"):
454
+ return "var"
455
+ else:
456
+ return "unknown"
457
+
458
+ def _get_export_type(self, export: dict[str, Any]) -> str:
459
+ """Get export type"""
460
+ if export.get("is_default", False):
461
+ return "default"
462
+ elif export.get("is_named", False):
463
+ return "named"
464
+ elif export.get("is_all", False):
465
+ return "all"
466
+ else:
467
+ return "unknown"
@@ -171,11 +171,11 @@ class LanguageLoader:
171
171
  parser.set_language(tree_sitter_language)
172
172
  elif hasattr(parser, "language"):
173
173
  try:
174
- setattr(parser, "language", tree_sitter_language)
174
+ parser.language = tree_sitter_language
175
175
  except Exception as inner_e: # noqa: F841
176
176
  raise
177
177
  else:
178
- raise RuntimeError("Unsupported Parser API: no way to set language")
178
+ raise RuntimeError("Unsupported Parser API: no way to set language") from None
179
179
 
180
180
  # Cache and return
181
181
  self._parser_cache[language] = parser