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.

@@ -0,0 +1,432 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ TypeScript-specific table formatter.
4
+
5
+ Provides specialized formatting for TypeScript code analysis results,
6
+ handling TypeScript-specific features like interfaces, type aliases, enums,
7
+ generics, decorators, and modern JavaScript features with type annotations.
8
+ """
9
+
10
+ from typing import Any
11
+
12
+ from .base_formatter import BaseTableFormatter
13
+
14
+
15
+ class TypeScriptTableFormatter(BaseTableFormatter):
16
+ """Table formatter specialized for TypeScript"""
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 TypeScript"""
24
+ lines = []
25
+
26
+ # Header - TypeScript (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(".ts", "").replace(".tsx", "").replace(".d.ts", "")
31
+ )
32
+
33
+ # Check if this is a module (has exports, classes, interfaces, or functions)
34
+ exports = data.get("exports", [])
35
+ classes = data.get("classes", [])
36
+ interfaces = data.get("interfaces", [])
37
+ functions = data.get("functions", [])
38
+ is_module = len(exports) > 0 or len(classes) > 0 or len(interfaces) > 0 or len(functions) > 0
39
+ is_declaration_file = file_name.endswith(".d.ts")
40
+ is_tsx = file_name.endswith(".tsx")
41
+
42
+ if is_declaration_file:
43
+ lines.append(f"# Declaration File: {module_name}")
44
+ elif is_tsx:
45
+ lines.append(f"# TSX Module: {module_name}")
46
+ elif is_module:
47
+ lines.append(f"# TypeScript Module: {module_name}")
48
+ else:
49
+ lines.append(f"# TypeScript Script: {module_name}")
50
+ lines.append("")
51
+
52
+ # Imports
53
+ imports = data.get("imports", [])
54
+ if imports:
55
+ lines.append("## Imports")
56
+ lines.append("```typescript")
57
+ for imp in imports:
58
+ import_statement = imp.get("statement", "")
59
+ if not import_statement:
60
+ # Construct import statement from parts
61
+ source = imp.get("source", "")
62
+ name = imp.get("name", "")
63
+ is_type_import = imp.get("is_type_import", False)
64
+ if name and source:
65
+ type_prefix = "type " if is_type_import else ""
66
+ import_statement = f"import {type_prefix}{name} from {source};"
67
+ lines.append(import_statement)
68
+ lines.append("```")
69
+ lines.append("")
70
+
71
+ # Module Info
72
+ stats = data.get("statistics", {})
73
+ classes = data.get("classes", [])
74
+ interfaces = [c for c in classes if c.get("class_type") == "interface"]
75
+ type_aliases = [c for c in classes if c.get("class_type") == "type"]
76
+ enums = [c for c in classes if c.get("class_type") == "enum"]
77
+ actual_classes = [c for c in classes if c.get("class_type") in ["class", "abstract_class"]]
78
+
79
+ lines.append("## Module Info")
80
+ lines.append("| Property | Value |")
81
+ lines.append("|----------|-------|")
82
+ lines.append(f"| File | {file_name} |")
83
+ lines.append(f"| Type | {'Declaration File' if is_declaration_file else 'TSX Module' if is_tsx else 'TypeScript Module' if is_module else 'TypeScript Script'} |")
84
+ lines.append(f"| Functions | {stats.get('function_count', 0)} |")
85
+ lines.append(f"| Classes | {len(actual_classes)} |")
86
+ lines.append(f"| Interfaces | {len(interfaces)} |")
87
+ lines.append(f"| Type Aliases | {len(type_aliases)} |")
88
+ lines.append(f"| Enums | {len(enums)} |")
89
+ lines.append(f"| Variables | {stats.get('variable_count', 0)} |")
90
+ lines.append(f"| Exports | {len(exports)} |")
91
+ lines.append("")
92
+
93
+ # Interfaces (TypeScript specific)
94
+ if interfaces:
95
+ lines.append("## Interfaces")
96
+ lines.append("| Interface | Extends | Lines | Properties | Methods | Generics |")
97
+ lines.append("|-----------|---------|-------|------------|---------|----------|")
98
+
99
+ for interface in interfaces:
100
+ name = str(interface.get("name", "Unknown"))
101
+ extends = ", ".join(interface.get("interfaces", [])) or "-"
102
+ line_range = interface.get("line_range", {})
103
+ lines_str = f"{line_range.get('start', 0)}-{line_range.get('end', 0)}"
104
+
105
+ # Count properties and methods within the interface
106
+ interface_properties = [
107
+ v for v in data.get("variables", [])
108
+ if line_range.get("start", 0) <= v.get("line_range", {}).get("start", 0) <= line_range.get("end", 0)
109
+ and v.get("declaration_kind") == "property_signature"
110
+ ]
111
+
112
+ interface_methods = [
113
+ m for m in data.get("methods", [])
114
+ if line_range.get("start", 0) <= m.get("line_range", {}).get("start", 0) <= line_range.get("end", 0)
115
+ and m.get("is_signature", False)
116
+ ]
117
+
118
+ generics = ", ".join(interface.get("generics", [])) or "-"
119
+
120
+ lines.append(f"| {name} | {extends} | {lines_str} | {len(interface_properties)} | {len(interface_methods)} | {generics} |")
121
+ lines.append("")
122
+
123
+ # Type Aliases (TypeScript specific)
124
+ if type_aliases:
125
+ lines.append("## Type Aliases")
126
+ lines.append("| Type | Lines | Generics | Definition |")
127
+ lines.append("|------|-------|----------|------------|")
128
+
129
+ for type_alias in type_aliases:
130
+ name = str(type_alias.get("name", "Unknown"))
131
+ line_range = type_alias.get("line_range", {})
132
+ lines_str = f"{line_range.get('start', 0)}-{line_range.get('end', 0)}"
133
+ generics = ", ".join(type_alias.get("generics", [])) or "-"
134
+
135
+ # Extract type definition from raw text
136
+ raw_text = type_alias.get("raw_text", "")
137
+ if "=" in raw_text:
138
+ definition = raw_text.split("=", 1)[1].strip().rstrip(";")[:50]
139
+ if len(definition) > 47:
140
+ definition = definition[:47] + "..."
141
+ else:
142
+ definition = "-"
143
+
144
+ lines.append(f"| {name} | {lines_str} | {generics} | {definition} |")
145
+ lines.append("")
146
+
147
+ # Enums (TypeScript specific)
148
+ if enums:
149
+ lines.append("## Enums")
150
+ lines.append("| Enum | Lines | Values |")
151
+ lines.append("|------|-------|--------|")
152
+
153
+ for enum in enums:
154
+ name = str(enum.get("name", "Unknown"))
155
+ line_range = enum.get("line_range", {})
156
+ lines_str = f"{line_range.get('start', 0)}-{line_range.get('end', 0)}"
157
+
158
+ # Count enum values (simplified)
159
+ raw_text = enum.get("raw_text", "")
160
+ value_count = raw_text.count(",") + 1 if raw_text.count("{") > 0 else 0
161
+
162
+ lines.append(f"| {name} | {lines_str} | {value_count} |")
163
+ lines.append("")
164
+
165
+ # Classes
166
+ if actual_classes:
167
+ lines.append("## Classes")
168
+ lines.append("| Class | Type | Extends | Implements | Lines | Methods | Properties | Generics |")
169
+ lines.append("|-------|------|---------|------------|-------|---------|------------|----------|")
170
+
171
+ for class_info in actual_classes:
172
+ name = str(class_info.get("name", "Unknown"))
173
+ class_type = class_info.get("class_type", "class")
174
+ extends = str(class_info.get("superclass", "")) or "-"
175
+ implements = ", ".join(class_info.get("interfaces", [])) or "-"
176
+ line_range = class_info.get("line_range", {})
177
+ lines_str = f"{line_range.get('start', 0)}-{line_range.get('end', 0)}"
178
+ generics = ", ".join(class_info.get("generics", [])) or "-"
179
+
180
+ # Count methods within the class
181
+ class_methods = [
182
+ m for m in data.get("functions", [])
183
+ if (line_range.get("start", 0) <= m.get("line_range", {}).get("start", 0) <= line_range.get("end", 0)
184
+ and m.get("is_method", False)
185
+ and not m.get("is_signature", False))
186
+ ]
187
+
188
+ # Count properties (class fields)
189
+ class_properties = [
190
+ v for v in data.get("variables", [])
191
+ if line_range.get("start", 0) <= v.get("line_range", {}).get("start", 0) <= line_range.get("end", 0)
192
+ and v.get("declaration_kind") == "property"
193
+ ]
194
+
195
+ lines.append(f"| {name} | {class_type} | {extends} | {implements} | {lines_str} | {len(class_methods)} | {len(class_properties)} | {generics} |")
196
+ lines.append("")
197
+
198
+ # Functions
199
+ functions = data.get("functions", [])
200
+ if functions:
201
+ lines.append("## Functions")
202
+ lines.append("| Function | Type | Return Type | Parameters | Async | Generic | Lines | Complexity |")
203
+ lines.append("|----------|------|-------------|------------|-------|---------|-------|------------|")
204
+
205
+ for func in functions:
206
+ name = str(func.get("name", "Unknown"))
207
+ func_type = "arrow" if func.get("is_arrow") else "method" if func.get("is_method") else "function"
208
+ return_type = str(func.get("return_type", "any"))
209
+ params = func.get("parameters", [])
210
+ param_count = len(params)
211
+ is_async = "✓" if func.get("is_async") else ""
212
+ has_generics = "✓" if func.get("generics") else ""
213
+ line_range = func.get("line_range", {})
214
+ lines_str = f"{line_range.get('start', 0)}-{line_range.get('end', 0)}"
215
+ complexity = func.get("complexity_score", 1)
216
+
217
+ lines.append(f"| {name} | {func_type} | {return_type} | {param_count} | {is_async} | {has_generics} | {lines_str} | {complexity} |")
218
+ lines.append("")
219
+
220
+ # Variables/Properties
221
+ variables = data.get("variables", [])
222
+ if variables:
223
+ lines.append("## Variables & Properties")
224
+ lines.append("| Name | Type | Kind | Visibility | Static | Optional | Lines |")
225
+ lines.append("|------|------|------|------------|--------|----------|-------|")
226
+
227
+ for var in variables:
228
+ name = str(var.get("name", "Unknown"))
229
+ var_type = str(var.get("variable_type", "any"))
230
+ kind = var.get("declaration_kind", "variable")
231
+ visibility = var.get("visibility", "public")
232
+ is_static = "✓" if var.get("is_static") else ""
233
+ is_optional = "✓" if var.get("is_optional") else ""
234
+ line_range = var.get("line_range", {})
235
+ lines_str = f"{line_range.get('start', 0)}-{line_range.get('end', 0)}"
236
+
237
+ lines.append(f"| {name} | {var_type} | {kind} | {visibility} | {is_static} | {is_optional} | {lines_str} |")
238
+ lines.append("")
239
+
240
+ # Exports
241
+ if exports:
242
+ lines.append("## Exports")
243
+ lines.append("| Export | Type | Default |")
244
+ lines.append("|--------|------|---------|")
245
+
246
+ for export in exports:
247
+ names = export.get("names", [])
248
+ export_type = export.get("type", "unknown")
249
+ is_default = "✓" if export.get("is_default") else ""
250
+
251
+ for name in names:
252
+ lines.append(f"| {name} | {export_type} | {is_default} |")
253
+ lines.append("")
254
+
255
+ return "\n".join(lines)
256
+
257
+ def _format_compact_table(self, data: dict[str, Any]) -> str:
258
+ """Compact table format for TypeScript"""
259
+ lines = []
260
+
261
+ # Header
262
+ file_path = data.get("file_path", "Unknown")
263
+ file_name = file_path.split("/")[-1].split("\\")[-1]
264
+ lines.append(f"# {file_name}")
265
+ lines.append("")
266
+
267
+ # Summary
268
+ stats = data.get("statistics", {})
269
+ classes = data.get("classes", [])
270
+ functions = data.get("functions", [])
271
+ variables = data.get("variables", [])
272
+
273
+ interfaces = len([c for c in classes if c.get("class_type") == "interface"])
274
+ type_aliases = len([c for c in classes if c.get("class_type") == "type"])
275
+ enums = len([c for c in classes if c.get("class_type") == "enum"])
276
+ actual_classes = len([c for c in classes if c.get("class_type") in ["class", "abstract_class"]])
277
+
278
+ lines.append("## Summary")
279
+ lines.append(f"- **Classes**: {actual_classes}")
280
+ lines.append(f"- **Interfaces**: {interfaces}")
281
+ lines.append(f"- **Type Aliases**: {type_aliases}")
282
+ lines.append(f"- **Enums**: {enums}")
283
+ lines.append(f"- **Functions**: {len(functions)}")
284
+ lines.append(f"- **Variables**: {len(variables)}")
285
+ lines.append("")
286
+
287
+ # Main elements
288
+ if classes:
289
+ lines.append("## Types")
290
+ for class_info in classes:
291
+ name = class_info.get("name", "Unknown")
292
+ class_type = class_info.get("class_type", "class")
293
+ line_range = class_info.get("line_range", {})
294
+ lines_str = f"L{line_range.get('start', 0)}-{line_range.get('end', 0)}"
295
+ lines.append(f"- **{name}** ({class_type}) - {lines_str}")
296
+ lines.append("")
297
+
298
+ if functions:
299
+ lines.append("## Functions")
300
+ for func in functions:
301
+ name = func.get("name", "Unknown")
302
+ return_type = func.get("return_type", "any")
303
+ line_range = func.get("line_range", {})
304
+ lines_str = f"L{line_range.get('start', 0)}-{line_range.get('end', 0)}"
305
+ async_marker = " (async)" if func.get("is_async") else ""
306
+ lines.append(f"- **{name}**(): {return_type}{async_marker} - {lines_str}")
307
+ lines.append("")
308
+
309
+ return "\n".join(lines)
310
+
311
+ def _format_csv(self, data: dict[str, Any]) -> str:
312
+ """CSV format for TypeScript"""
313
+ lines = []
314
+
315
+ # Header
316
+ lines.append("Type,Name,Kind,Return/Type,Lines,Visibility,Static,Async,Generic")
317
+
318
+ # Classes, interfaces, types, enums
319
+ classes = data.get("classes", [])
320
+ for class_info in classes:
321
+ name = class_info.get("name", "")
322
+ class_type = class_info.get("class_type", "class")
323
+ line_range = class_info.get("line_range", {})
324
+ lines_str = f"{line_range.get('start', 0)}-{line_range.get('end', 0)}"
325
+ visibility = class_info.get("visibility", "public")
326
+ is_static = "true" if class_info.get("is_static") else "false"
327
+ has_generics = "true" if class_info.get("generics") else "false"
328
+
329
+ lines.append(f"Class,{name},{class_type},,{lines_str},{visibility},{is_static},,{has_generics}")
330
+
331
+ # Functions
332
+ functions = data.get("functions", [])
333
+ for func in functions:
334
+ name = func.get("name", "")
335
+ func_type = "arrow" if func.get("is_arrow") else "method" if func.get("is_method") else "function"
336
+ return_type = func.get("return_type", "any")
337
+ line_range = func.get("line_range", {})
338
+ lines_str = f"{line_range.get('start', 0)}-{line_range.get('end', 0)}"
339
+ visibility = func.get("visibility", "public")
340
+ is_static = "true" if func.get("is_static") else "false"
341
+ is_async = "true" if func.get("is_async") else "false"
342
+ has_generics = "true" if func.get("generics") else "false"
343
+
344
+ lines.append(f"Function,{name},{func_type},{return_type},{lines_str},{visibility},{is_static},{is_async},{has_generics}")
345
+
346
+ # Variables
347
+ variables = data.get("variables", [])
348
+ for var in variables:
349
+ name = var.get("name", "")
350
+ kind = var.get("declaration_kind", "variable")
351
+ var_type = var.get("variable_type", "any")
352
+ line_range = var.get("line_range", {})
353
+ lines_str = f"{line_range.get('start', 0)}-{line_range.get('end', 0)}"
354
+ visibility = var.get("visibility", "public")
355
+ is_static = "true" if var.get("is_static") else "false"
356
+
357
+ lines.append(f"Variable,{name},{kind},{var_type},{lines_str},{visibility},{is_static},,")
358
+
359
+ return "\n".join(lines)
360
+
361
+ def _get_element_type_name(self, element: dict[str, Any]) -> str:
362
+ """Get human-readable type name for TypeScript elements"""
363
+ element_type = element.get("element_type", "unknown")
364
+
365
+ if element_type == "class":
366
+ class_type = element.get("class_type", "class")
367
+ if class_type == "interface":
368
+ return "Interface"
369
+ elif class_type == "type":
370
+ return "Type Alias"
371
+ elif class_type == "enum":
372
+ return "Enum"
373
+ elif class_type == "abstract_class":
374
+ return "Abstract Class"
375
+ else:
376
+ return "Class"
377
+ elif element_type == "function":
378
+ if element.get("is_arrow"):
379
+ return "Arrow Function"
380
+ elif element.get("is_method"):
381
+ return "Method"
382
+ elif element.get("is_constructor"):
383
+ return "Constructor"
384
+ else:
385
+ return "Function"
386
+ elif element_type == "variable":
387
+ kind = element.get("declaration_kind", "variable")
388
+ if kind == "property":
389
+ return "Property"
390
+ elif kind == "property_signature":
391
+ return "Property Signature"
392
+ else:
393
+ return "Variable"
394
+ elif element_type == "import":
395
+ return "Import"
396
+ else:
397
+ return element_type.title()
398
+
399
+ def _format_element_details(self, element: dict[str, Any]) -> str:
400
+ """Format TypeScript-specific element details"""
401
+ details = []
402
+
403
+ # Type annotations
404
+ if element.get("has_type_annotations"):
405
+ details.append("typed")
406
+
407
+ # Generics
408
+ if element.get("generics"):
409
+ generics = ", ".join(element.get("generics", []))
410
+ details.append(f"<{generics}>")
411
+
412
+ # Visibility
413
+ visibility = element.get("visibility")
414
+ if visibility and visibility != "public":
415
+ details.append(visibility)
416
+
417
+ # Modifiers
418
+ if element.get("is_static"):
419
+ details.append("static")
420
+ if element.get("is_async"):
421
+ details.append("async")
422
+ if element.get("is_abstract"):
423
+ details.append("abstract")
424
+ if element.get("is_optional"):
425
+ details.append("optional")
426
+
427
+ # Framework specific
428
+ framework = element.get("framework_type")
429
+ if framework:
430
+ details.append(f"{framework}")
431
+
432
+ return " ".join(details) if details else ""
@@ -23,7 +23,7 @@ class LanguageDetector:
23
23
  ".js": "javascript",
24
24
  ".jsx": "jsx",
25
25
  ".ts": "typescript",
26
- ".tsx": "tsx",
26
+ ".tsx": "typescript", # TSX files are TypeScript with JSX
27
27
  ".mjs": "javascript",
28
28
  ".cjs": "javascript",
29
29
  # Python系
@@ -201,9 +201,12 @@ class JavaElementExtractor(ElementExtractor):
201
201
  import_name = static_match.group(1)
202
202
  if import_content.endswith(".*"):
203
203
  import_name = import_name.replace(".*", "")
204
- parts = import_name.split(".")
205
- if len(parts) > 1:
206
- import_name = ".".join(parts[:-1])
204
+
205
+ # For static imports, extract the class name (remove method/field name)
206
+ parts = import_name.split(".")
207
+ if len(parts) > 1:
208
+ # Remove the last part (method/field name) to get class name
209
+ import_name = ".".join(parts[:-1])
207
210
 
208
211
  imports.append(
209
212
  Import(
@@ -255,6 +258,10 @@ class JavaElementExtractor(ElementExtractor):
255
258
  packages: list[Package] = []
256
259
 
257
260
  # Extract package declaration
261
+ if tree is None or tree.root_node is None:
262
+ log_debug("Tree or root_node is None, returning empty packages list")
263
+ return packages
264
+
258
265
  for child in tree.root_node.children:
259
266
  if child.type == "package_declaration":
260
267
  package_info = self._extract_package_element(child)
@@ -1054,10 +1061,12 @@ class JavaElementExtractor(ElementExtractor):
1054
1061
  # Handle wildcard case
1055
1062
  if import_content.endswith(".*"):
1056
1063
  import_name = import_name.replace(".*", "")
1057
- # For static wildcard, remove last element
1058
- parts = import_name.split(".")
1059
- if len(parts) > 1:
1060
- import_name = ".".join(parts[:-1])
1064
+
1065
+ # For static imports, extract the class name (remove method/field name)
1066
+ parts = import_name.split(".")
1067
+ if len(parts) > 1:
1068
+ # Remove the last part (method/field name) to get class name
1069
+ import_name = ".".join(parts[:-1])
1061
1070
 
1062
1071
  return Import(
1063
1072
  name=import_name,
@@ -1099,6 +1108,20 @@ class JavaElementExtractor(ElementExtractor):
1099
1108
  log_error(f"Unexpected error in import extraction: {e}")
1100
1109
  return None
1101
1110
 
1111
+ def extract_elements(self, tree: "tree_sitter.Tree", source_code: str) -> list:
1112
+ """Extract elements from source code using tree-sitter AST"""
1113
+ elements = []
1114
+
1115
+ try:
1116
+ elements.extend(self.extract_functions(tree, source_code))
1117
+ elements.extend(self.extract_classes(tree, source_code))
1118
+ elements.extend(self.extract_variables(tree, source_code))
1119
+ elements.extend(self.extract_imports(tree, source_code))
1120
+ except Exception as e:
1121
+ log_error(f"Failed to extract elements: {e}")
1122
+
1123
+ return elements
1124
+
1102
1125
 
1103
1126
  class JavaPlugin(LanguagePlugin):
1104
1127
  """Java language plugin for the new architecture"""
@@ -1107,6 +1130,12 @@ class JavaPlugin(LanguagePlugin):
1107
1130
  """Initialize the Java plugin"""
1108
1131
  super().__init__()
1109
1132
  self._language_cache: tree_sitter.Language | None = None
1133
+ self._extractor: Optional[JavaElementExtractor] = None
1134
+
1135
+ # Legacy attributes for backward compatibility with tests
1136
+ self.language = "java"
1137
+ self.extractor = self.create_extractor()
1138
+ self.supported_extensions = self.get_file_extensions()
1110
1139
 
1111
1140
  def get_language_name(self) -> str:
1112
1141
  """Return the name of the programming language this plugin supports"""
@@ -1120,6 +1149,12 @@ class JavaPlugin(LanguagePlugin):
1120
1149
  """Create and return an element extractor for this language"""
1121
1150
  return JavaElementExtractor()
1122
1151
 
1152
+ def get_extractor(self) -> ElementExtractor:
1153
+ """Get the cached extractor instance, creating it if necessary"""
1154
+ if self._extractor is None:
1155
+ self._extractor = JavaElementExtractor()
1156
+ return self._extractor
1157
+
1123
1158
  def get_tree_sitter_language(self) -> Optional["tree_sitter.Language"]:
1124
1159
  """Get the Tree-sitter language object for Java"""
1125
1160
  if self._language_cache is None:
@@ -1270,3 +1305,29 @@ class JavaPlugin(LanguagePlugin):
1270
1305
  success=False,
1271
1306
  error_message=str(e),
1272
1307
  )
1308
+
1309
+ def extract_elements(self, tree: "tree_sitter.Tree", source_code: str) -> dict[str, list[CodeElement]]:
1310
+ """Legacy method for backward compatibility with tests"""
1311
+ if not tree or not tree.root_node:
1312
+ return {
1313
+ "packages": [],
1314
+ "functions": [],
1315
+ "classes": [],
1316
+ "variables": [],
1317
+ "imports": [],
1318
+ "annotations": []
1319
+ }
1320
+
1321
+ extractor = self.create_extractor()
1322
+
1323
+ # Extract all types of elements and return as dictionary
1324
+ result = {
1325
+ "packages": extractor.extract_packages(tree, source_code),
1326
+ "functions": extractor.extract_functions(tree, source_code),
1327
+ "classes": extractor.extract_classes(tree, source_code),
1328
+ "variables": extractor.extract_variables(tree, source_code),
1329
+ "imports": extractor.extract_imports(tree, source_code),
1330
+ "annotations": extractor.extract_annotations(tree, source_code)
1331
+ }
1332
+
1333
+ return result
@@ -558,7 +558,8 @@ class JavaScriptElementExtractor(ElementExtractor):
558
558
  elif child.type == "class_heritage":
559
559
  # Extract extends clause
560
560
  heritage_text = self._get_node_text_optimized(child)
561
- match = re.search(r"extends\s+(\w+)", heritage_text)
561
+ # Support both simple names (Component) and dotted names (React.Component)
562
+ match = re.search(r"extends\s+([\w.]+)", heritage_text)
562
563
  if match:
563
564
  superclass = match.group(1)
564
565
 
@@ -1217,6 +1218,20 @@ class JavaScriptElementExtractor(ElementExtractor):
1217
1218
  else:
1218
1219
  return "unknown"
1219
1220
 
1221
+ def extract_elements(self, tree: "tree_sitter.Tree", source_code: str) -> list:
1222
+ """Extract elements from source code using tree-sitter AST"""
1223
+ elements = []
1224
+
1225
+ try:
1226
+ elements.extend(self.extract_functions(tree, source_code))
1227
+ elements.extend(self.extract_classes(tree, source_code))
1228
+ elements.extend(self.extract_variables(tree, source_code))
1229
+ elements.extend(self.extract_imports(tree, source_code))
1230
+ except Exception as e:
1231
+ log_error(f"Failed to extract elements: {e}")
1232
+
1233
+ return elements
1234
+
1220
1235
  def _get_variable_kind(self, var_data: dict | str) -> str:
1221
1236
  """Get variable declaration kind from variable data or raw text"""
1222
1237
  if isinstance(var_data, dict):
@@ -1344,6 +1359,11 @@ class JavaScriptPlugin(LanguagePlugin):
1344
1359
  def __init__(self) -> None:
1345
1360
  self._extractor = JavaScriptElementExtractor()
1346
1361
  self._language: tree_sitter.Language | None = None
1362
+
1363
+ # Legacy compatibility attributes for tests
1364
+ self.language = "javascript"
1365
+ self.extractor = self._extractor
1366
+ self.supported_extensions = [".js", ".mjs", ".jsx", ".es6", ".es", ".cjs"]
1347
1367
 
1348
1368
  @property
1349
1369
  def language_name(self) -> str:
@@ -1495,3 +1515,25 @@ class JavaScriptPlugin(LanguagePlugin):
1495
1515
  success=False,
1496
1516
  error_message=str(e),
1497
1517
  )
1518
+
1519
+ def extract_elements(self, tree: "tree_sitter.Tree", source_code: str) -> dict:
1520
+ """Extract elements from source code using tree-sitter AST"""
1521
+ try:
1522
+ if tree is None or not hasattr(tree, 'root_node') or tree.root_node is None:
1523
+ return {"functions": [], "classes": [], "variables": [], "imports": [], "exports": []}
1524
+
1525
+ functions = self._extractor.extract_functions(tree, source_code)
1526
+ classes = self._extractor.extract_classes(tree, source_code)
1527
+ variables = self._extractor.extract_variables(tree, source_code)
1528
+ imports = self._extractor.extract_imports(tree, source_code)
1529
+
1530
+ return {
1531
+ "functions": functions,
1532
+ "classes": classes,
1533
+ "variables": variables,
1534
+ "imports": imports,
1535
+ "exports": [] # TODO: Implement exports extraction
1536
+ }
1537
+ except Exception as e:
1538
+ log_error(f"Failed to extract elements: {e}")
1539
+ return {"functions": [], "classes": [], "variables": [], "imports": [], "exports": []}