tree-sitter-analyzer 1.4.0__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.

@@ -2,10 +2,14 @@
2
2
  """
3
3
  JavaScript Language Plugin
4
4
 
5
- Provides JavaScript-specific parsing and element extraction functionality.
5
+ Enhanced JavaScript-specific parsing and element extraction functionality.
6
+ Provides comprehensive support for modern JavaScript features including ES6+,
7
+ async/await, classes, modules, JSX, and framework-specific patterns.
8
+ Equivalent to Java plugin capabilities for consistent language support.
6
9
  """
7
10
 
8
- from typing import TYPE_CHECKING, Optional
11
+ import re
12
+ from typing import TYPE_CHECKING, Any, Optional
9
13
 
10
14
  if TYPE_CHECKING:
11
15
  import tree_sitter
@@ -18,340 +22,1324 @@ except ImportError:
18
22
  TREE_SITTER_AVAILABLE = False
19
23
 
20
24
  from ..core.analysis_engine import AnalysisRequest
25
+ from ..encoding_utils import extract_text_slice, safe_encode
21
26
  from ..language_loader import loader
22
27
  from ..models import AnalysisResult, Class, CodeElement, Function, Import, Variable
23
28
  from ..plugins.base import ElementExtractor, LanguagePlugin
24
- from ..utils import log_error, log_warning
29
+ from ..utils import log_debug, log_error, log_warning
25
30
 
26
31
 
27
32
  class JavaScriptElementExtractor(ElementExtractor):
28
- """JavaScript-specific element extractor"""
33
+ """Enhanced JavaScript-specific element extractor with comprehensive feature support"""
34
+
35
+ def __init__(self) -> None:
36
+ """Initialize the JavaScript element extractor."""
37
+ self.current_file: str = ""
38
+ self.source_code: str = ""
39
+ self.content_lines: list[str] = []
40
+ self.imports: list[str] = []
41
+ self.exports: list[dict[str, Any]] = []
42
+
43
+ # Performance optimization caches
44
+ self._node_text_cache: dict[int, str] = {}
45
+ self._processed_nodes: set[int] = set()
46
+ self._element_cache: dict[tuple[int, str], Any] = {}
47
+ self._file_encoding: str | None = None
48
+ self._jsdoc_cache: dict[int, str] = {}
49
+ self._complexity_cache: dict[int, int] = {}
50
+
51
+ # JavaScript-specific tracking
52
+ self.is_module: bool = False
53
+ self.is_jsx: bool = False
54
+ self.framework_type: str = "" # react, vue, angular, etc.
29
55
 
30
56
  def extract_functions(
31
57
  self, tree: "tree_sitter.Tree", source_code: str
32
58
  ) -> list[Function]:
33
- """Extract JavaScript function definitions"""
34
- functions = []
35
-
36
- # Multiple function patterns in JavaScript
37
- queries = [
38
- # Regular function declarations
39
- """
40
- (function_declaration
41
- name: (identifier) @func.name
42
- parameters: (formal_parameters) @func.params
43
- body: (statement_block) @func.body) @func.declaration
44
- """,
45
- # Method definitions (class methods)
46
- """
47
- (method_definition
48
- name: (property_identifier) @func.name
49
- parameters: (formal_parameters) @func.params
50
- body: (statement_block) @func.body) @func.method
51
- """,
52
- # Arrow functions
53
- """
54
- (variable_declarator
55
- name: (identifier) @func.name
56
- value: (arrow_function
57
- parameters: (formal_parameters) @func.params
58
- body: (_) @func.body)) @func.arrow
59
- """,
60
- # Function expressions
61
- """
62
- (variable_declarator
63
- name: (identifier) @func.name
64
- value: (function_expression
65
- parameters: (formal_parameters) @func.params
66
- body: (statement_block) @func.body)) @func.expression
67
- """,
68
- ]
69
-
70
- try:
71
- language = tree.language if hasattr(tree, "language") else None
72
- if language:
73
- for query_string in queries:
74
- query = language.query(query_string)
75
- captures = query.captures(tree.root_node)
76
-
77
- if isinstance(captures, dict):
78
- # Handle different function types
79
- for capture_key in [
80
- "func.declaration",
81
- "func.method",
82
- "func.arrow",
83
- "func.expression",
84
- ]:
85
- func_nodes = captures.get(capture_key, [])
86
- for node in func_nodes:
87
- function = self._extract_function_info(
88
- node, source_code
89
- )
90
- if function:
91
- functions.append(function)
92
-
93
- except Exception as e:
94
- log_warning(f"Could not extract JavaScript functions: {e}")
95
-
59
+ """Extract JavaScript function definitions with comprehensive details"""
60
+ self.source_code = source_code
61
+ self.content_lines = source_code.split("\n")
62
+ self._reset_caches()
63
+ self._detect_file_characteristics()
64
+
65
+ functions: list[Function] = []
66
+
67
+ # Use optimized traversal for multiple function types
68
+ extractors = {
69
+ "function_declaration": self._extract_function_optimized,
70
+ "function_expression": self._extract_function_optimized,
71
+ "arrow_function": self._extract_arrow_function_optimized,
72
+ "method_definition": self._extract_method_optimized,
73
+ "generator_function_declaration": self._extract_generator_function_optimized,
74
+ }
75
+
76
+ self._traverse_and_extract_iterative(
77
+ tree.root_node, extractors, functions, "function"
78
+ )
79
+
80
+ log_debug(f"Extracted {len(functions)} JavaScript functions")
96
81
  return functions
97
82
 
98
83
  def extract_classes(
99
84
  self, tree: "tree_sitter.Tree", source_code: str
100
85
  ) -> list[Class]:
101
- """Extract JavaScript class definitions"""
102
- classes = []
86
+ """Extract JavaScript class definitions with detailed information"""
87
+ self.source_code = source_code
88
+ self.content_lines = source_code.split("\n")
89
+ self._reset_caches()
103
90
 
104
- query_string = """
105
- (class_declaration
106
- name: (identifier) @class.name
107
- body: (class_body) @class.body) @class.declaration
108
- """
91
+ classes: list[Class] = []
109
92
 
110
- try:
111
- language = tree.language if hasattr(tree, "language") else None
112
- if language:
113
- query = language.query(query_string)
114
- captures = query.captures(tree.root_node)
115
-
116
- if isinstance(captures, dict):
117
- class_nodes = captures.get("class.declaration", [])
118
- for node in class_nodes:
119
- cls = self._extract_class_info(node, source_code)
120
- if cls:
121
- classes.append(cls)
93
+ # Extract both class declarations and expressions
94
+ extractors = {
95
+ "class_declaration": self._extract_class_optimized,
96
+ "class_expression": self._extract_class_optimized,
97
+ }
122
98
 
123
- except Exception as e:
124
- log_warning(f"Could not extract JavaScript classes: {e}")
99
+ self._traverse_and_extract_iterative(
100
+ tree.root_node, extractors, classes, "class"
101
+ )
125
102
 
103
+ log_debug(f"Extracted {len(classes)} JavaScript classes")
126
104
  return classes
127
105
 
128
106
  def extract_variables(
129
107
  self, tree: "tree_sitter.Tree", source_code: str
130
108
  ) -> list[Variable]:
131
- """Extract JavaScript variable definitions"""
132
- variables = []
133
-
134
- # Variable declarations (let, const, var)
135
- queries = [
136
- # var declarations
137
- """
138
- (variable_declaration
139
- (variable_declarator
140
- name: (identifier) @var.name
141
- value: (_)? @var.value)) @var.declaration
142
- """,
143
- # let/const declarations
144
- """
145
- (lexical_declaration
146
- (variable_declarator
147
- name: (identifier) @var.name
148
- value: (_)? @var.value)) @var.lexical
149
- """,
150
- ]
109
+ """Extract JavaScript variable definitions with modern syntax support"""
110
+ self.source_code = source_code
111
+ self.content_lines = source_code.split("\n")
112
+ self._reset_caches()
151
113
 
152
- try:
153
- language = tree.language if hasattr(tree, "language") else None
154
- if language:
155
- for query_string in queries:
156
- query = language.query(query_string)
157
- captures = query.captures(tree.root_node)
158
-
159
- if isinstance(captures, dict):
160
- # Handle both var and lexical declarations
161
- for capture_key in ["var.declaration", "var.lexical"]:
162
- var_nodes = captures.get(capture_key, [])
163
- for node in var_nodes:
164
- variable = self._extract_variable_info(
165
- node, source_code
166
- )
167
- if variable:
168
- variables.append(variable)
114
+ variables: list[Variable] = []
169
115
 
170
- except Exception as e:
171
- log_warning(f"Could not extract JavaScript variables: {e}")
116
+ # Handle all JavaScript variable declaration types
117
+ extractors = {
118
+ "variable_declaration": self._extract_variable_optimized,
119
+ "lexical_declaration": self._extract_lexical_variable_optimized,
120
+ "property_definition": self._extract_property_optimized,
121
+ }
122
+
123
+ self._traverse_and_extract_iterative(
124
+ tree.root_node, extractors, variables, "variable"
125
+ )
172
126
 
127
+ log_debug(f"Extracted {len(variables)} JavaScript variables")
173
128
  return variables
174
129
 
175
130
  def extract_imports(
176
131
  self, tree: "tree_sitter.Tree", source_code: str
177
132
  ) -> list[Import]:
178
- """Extract JavaScript import statements"""
179
- imports = []
133
+ """Extract JavaScript import statements with ES6+ support"""
134
+ self.source_code = source_code
135
+ self.content_lines = source_code.split("\n")
136
+
137
+ imports: list[Import] = []
138
+
139
+ # Extract imports efficiently
140
+ for child in tree.root_node.children:
141
+ if child.type == "import_statement":
142
+ import_info = self._extract_import_info_simple(child)
143
+ if import_info:
144
+ imports.append(import_info)
145
+ elif child.type == "expression_statement":
146
+ # Check for dynamic imports
147
+ dynamic_import = self._extract_dynamic_import(child)
148
+ if dynamic_import:
149
+ imports.append(dynamic_import)
150
+
151
+ # Also check for CommonJS requires
152
+ commonjs_imports = self._extract_commonjs_requires(tree, source_code)
153
+ imports.extend(commonjs_imports)
154
+
155
+ log_debug(f"Extracted {len(imports)} JavaScript imports")
156
+ return imports
180
157
 
181
- # ES6 import statements
182
- query_string = """
183
- (import_statement
184
- source: (string) @import.source) @import.declaration
185
- """
158
+ def extract_exports(
159
+ self, tree: "tree_sitter.Tree", source_code: str
160
+ ) -> list[dict[str, Any]]:
161
+ """Extract JavaScript export statements"""
162
+ self.source_code = source_code
163
+ self.content_lines = source_code.split("\n")
164
+
165
+ exports: list[dict[str, Any]] = []
166
+
167
+ # Extract ES6 exports
168
+ for child in tree.root_node.children:
169
+ if child.type == "export_statement":
170
+ export_info = self._extract_export_info(child)
171
+ if export_info:
172
+ exports.append(export_info)
173
+
174
+ # Also check for CommonJS exports
175
+ commonjs_exports = self._extract_commonjs_exports(tree, source_code)
176
+ exports.extend(commonjs_exports)
177
+
178
+ self.exports = exports
179
+ log_debug(f"Extracted {len(exports)} JavaScript exports")
180
+ return exports
181
+
182
+ def _reset_caches(self) -> None:
183
+ """Reset performance caches"""
184
+ self._node_text_cache.clear()
185
+ self._processed_nodes.clear()
186
+ self._element_cache.clear()
187
+ self._jsdoc_cache.clear()
188
+ self._complexity_cache.clear()
189
+
190
+ def _detect_file_characteristics(self) -> None:
191
+ """Detect JavaScript file characteristics"""
192
+ # Check if it's a module
193
+ self.is_module = "import " in self.source_code or "export " in self.source_code
194
+
195
+ # Check if it contains JSX
196
+ self.is_jsx = "</" in self.source_code and "jsx" in self.current_file.lower()
197
+
198
+ # Detect framework
199
+ if "react" in self.source_code.lower() or "jsx" in self.source_code:
200
+ self.framework_type = "react"
201
+ elif "vue" in self.source_code.lower():
202
+ self.framework_type = "vue"
203
+ elif "angular" in self.source_code.lower():
204
+ self.framework_type = "angular"
205
+
206
+ def _traverse_and_extract_iterative(
207
+ self,
208
+ root_node: "tree_sitter.Node",
209
+ extractors: dict[str, Any],
210
+ results: list[Any],
211
+ element_type: str,
212
+ ) -> None:
213
+ """Iterative node traversal and extraction with caching"""
214
+ if not root_node:
215
+ return
216
+
217
+ target_node_types = set(extractors.keys())
218
+ container_node_types = {
219
+ "program",
220
+ "class_body",
221
+ "statement_block",
222
+ "object",
223
+ "class_declaration",
224
+ "function_declaration",
225
+ "method_definition",
226
+ "export_statement",
227
+ "variable_declaration",
228
+ "lexical_declaration",
229
+ "variable_declarator",
230
+ "assignment_expression",
231
+ }
232
+
233
+ node_stack = [(root_node, 0)]
234
+ processed_nodes = 0
235
+ max_depth = 50
236
+
237
+ while node_stack:
238
+ current_node, depth = node_stack.pop()
239
+
240
+ if depth > max_depth:
241
+ log_warning(f"Maximum traversal depth ({max_depth}) exceeded")
242
+ continue
243
+
244
+ processed_nodes += 1
245
+ node_type = current_node.type
246
+
247
+ # Early termination for irrelevant nodes
248
+ if (
249
+ depth > 0
250
+ and node_type not in target_node_types
251
+ and node_type not in container_node_types
252
+ ):
253
+ continue
254
+
255
+ # Process target nodes
256
+ if node_type in target_node_types:
257
+ node_id = id(current_node)
258
+
259
+ if node_id in self._processed_nodes:
260
+ continue
261
+
262
+ cache_key = (node_id, element_type)
263
+ if cache_key in self._element_cache:
264
+ element = self._element_cache[cache_key]
265
+ if element:
266
+ if isinstance(element, list):
267
+ results.extend(element)
268
+ else:
269
+ results.append(element)
270
+ self._processed_nodes.add(node_id)
271
+ continue
272
+
273
+ # Extract and cache
274
+ extractor = extractors.get(node_type)
275
+ if extractor:
276
+ element = extractor(current_node)
277
+ self._element_cache[cache_key] = element
278
+ if element:
279
+ if isinstance(element, list):
280
+ results.extend(element)
281
+ else:
282
+ results.append(element)
283
+ self._processed_nodes.add(node_id)
284
+
285
+ # Add children to stack
286
+ if current_node.children:
287
+ for child in reversed(current_node.children):
288
+ node_stack.append((child, depth + 1))
289
+
290
+ log_debug(f"Iterative traversal processed {processed_nodes} nodes")
291
+
292
+ def _get_node_text_optimized(self, node: "tree_sitter.Node") -> str:
293
+ """Get node text with optimized caching"""
294
+ node_id = id(node)
295
+
296
+ if node_id in self._node_text_cache:
297
+ return self._node_text_cache[node_id]
186
298
 
187
299
  try:
188
- language = tree.language if hasattr(tree, "language") else None
189
- if language:
190
- query = language.query(query_string)
191
- captures = query.captures(tree.root_node)
192
-
193
- if isinstance(captures, dict):
194
- import_nodes = captures.get("import.declaration", [])
195
- for node in import_nodes:
196
- imp = self._extract_import_info(node, source_code)
197
- if imp:
198
- imports.append(imp)
300
+ start_byte = node.start_byte
301
+ end_byte = node.end_byte
199
302
 
303
+ encoding = self._file_encoding or "utf-8"
304
+ content_bytes = safe_encode("\n".join(self.content_lines), encoding)
305
+ text = extract_text_slice(content_bytes, start_byte, end_byte, encoding)
306
+
307
+ self._node_text_cache[node_id] = text
308
+ return text
200
309
  except Exception as e:
201
- log_warning(f"Could not extract JavaScript imports: {e}")
310
+ log_error(f"Error in _get_node_text_optimized: {e}")
311
+ # Fallback to simple text extraction
312
+ try:
313
+ start_point = node.start_point
314
+ end_point = node.end_point
315
+
316
+ if start_point[0] == end_point[0]:
317
+ line = self.content_lines[start_point[0]]
318
+ return line[start_point[1] : end_point[1]]
319
+ else:
320
+ lines = []
321
+ for i in range(start_point[0], end_point[0] + 1):
322
+ if i < len(self.content_lines):
323
+ line = self.content_lines[i]
324
+ if i == start_point[0]:
325
+ lines.append(line[start_point[1] :])
326
+ elif i == end_point[0]:
327
+ lines.append(line[: end_point[1]])
328
+ else:
329
+ lines.append(line)
330
+ return "\n".join(lines)
331
+ except Exception as fallback_error:
332
+ log_error(f"Fallback text extraction also failed: {fallback_error}")
333
+ return ""
334
+
335
+ def _extract_function_optimized(self, node: "tree_sitter.Node") -> Function | None:
336
+ """Extract regular function information with detailed metadata"""
337
+ try:
338
+ start_line = node.start_point[0] + 1
339
+ end_line = node.end_point[0] + 1
202
340
 
203
- return imports
341
+ # Extract function details
342
+ function_info = self._parse_function_signature_optimized(node)
343
+ if not function_info:
344
+ return None
204
345
 
205
- def _extract_function_info(
206
- self, node: "tree_sitter.Node", source_code: str
346
+ name, parameters, is_async, is_generator = function_info
347
+
348
+ # Extract JSDoc
349
+ jsdoc = self._extract_jsdoc_for_line(start_line)
350
+
351
+ # Calculate complexity
352
+ complexity_score = self._calculate_complexity_optimized(node)
353
+
354
+ # Extract raw text
355
+ start_line_idx = max(0, start_line - 1)
356
+ end_line_idx = min(len(self.content_lines), end_line)
357
+ raw_text = "\n".join(self.content_lines[start_line_idx:end_line_idx])
358
+
359
+ return Function(
360
+ name=name,
361
+ start_line=start_line,
362
+ end_line=end_line,
363
+ raw_text=raw_text,
364
+ language="javascript",
365
+ parameters=parameters,
366
+ return_type="unknown", # JavaScript is dynamically typed
367
+ is_async=is_async,
368
+ is_generator=is_generator,
369
+ docstring=jsdoc,
370
+ complexity_score=complexity_score,
371
+ # JavaScript-specific properties
372
+ is_arrow=False,
373
+ is_method=False,
374
+ framework_type=self.framework_type,
375
+ )
376
+ except Exception as e:
377
+ log_error(f"Failed to extract function info: {e}")
378
+ import traceback
379
+
380
+ traceback.print_exc()
381
+ return None
382
+
383
+ def _extract_arrow_function_optimized(
384
+ self, node: "tree_sitter.Node"
207
385
  ) -> Function | None:
208
- """Extract function information from AST node"""
386
+ """Extract arrow function information"""
209
387
  try:
210
- name_node = None
211
- params_node = None
388
+ start_line = node.start_point[0] + 1
389
+ end_line = node.end_point[0] + 1
212
390
 
213
- # Navigate the node structure to find name and parameters
214
- for child in node.children:
215
- if child.type == "identifier":
216
- name_node = child
217
- elif child.type == "property_identifier": # For method definitions
218
- name_node = child
219
- elif child.type == "formal_parameters":
220
- params_node = child
221
- elif child.type == "variable_declarator":
222
- # For arrow functions and function expressions
223
- for subchild in child.children:
224
- if subchild.type == "identifier":
225
- name_node = subchild
226
- elif subchild.type in ["arrow_function", "function_expression"]:
227
- for funcchild in subchild.children:
228
- if funcchild.type == "formal_parameters":
229
- params_node = funcchild
230
-
231
- if not name_node:
232
- return None
391
+ # For arrow functions, we need to find the variable declaration
392
+ parent = node.parent
393
+ name = "anonymous"
233
394
 
234
- name = source_code[name_node.start_byte : name_node.end_byte]
395
+ if parent and parent.type == "variable_declarator":
396
+ for child in parent.children:
397
+ if child.type == "identifier":
398
+ name = self._get_node_text_optimized(child)
399
+ break
235
400
 
236
401
  # Extract parameters
237
402
  parameters = []
238
- if params_node:
239
- for child in params_node.children:
240
- if child.type == "identifier":
241
- param_name = source_code[child.start_byte : child.end_byte]
242
- parameters.append(param_name)
403
+ for child in node.children:
404
+ if child.type == "formal_parameters":
405
+ parameters = self._extract_parameters(child)
406
+ elif child.type == "identifier":
407
+ # Single parameter without parentheses
408
+ param_name = self._get_node_text_optimized(child)
409
+ parameters = [param_name]
410
+
411
+ # Check if async
412
+ is_async = "async" in self._get_node_text_optimized(node)
413
+
414
+ # Extract JSDoc (look at parent variable declaration)
415
+ jsdoc = self._extract_jsdoc_for_line(start_line)
416
+
417
+ # Calculate complexity
418
+ complexity_score = self._calculate_complexity_optimized(node)
419
+
420
+ # Extract raw text
421
+ raw_text = self._get_node_text_optimized(node)
243
422
 
244
423
  return Function(
245
424
  name=name,
425
+ start_line=start_line,
426
+ end_line=end_line,
427
+ raw_text=raw_text,
428
+ language="javascript",
246
429
  parameters=parameters,
247
- start_line=node.start_point[0] + 1,
248
- end_line=node.end_point[0] + 1,
249
- raw_text=source_code[node.start_byte : node.end_byte],
430
+ return_type="unknown",
431
+ is_async=is_async,
432
+ is_generator=False,
433
+ docstring=jsdoc,
434
+ complexity_score=complexity_score,
435
+ # JavaScript-specific properties
436
+ is_arrow=True,
437
+ is_method=False,
438
+ framework_type=self.framework_type,
439
+ )
440
+ except Exception as e:
441
+ log_debug(f"Failed to extract arrow function info: {e}")
442
+ return None
443
+
444
+ def _extract_method_optimized(self, node: "tree_sitter.Node") -> Function | None:
445
+ """Extract method information from class"""
446
+ try:
447
+ start_line = node.start_point[0] + 1
448
+ end_line = node.end_point[0] + 1
449
+
450
+ # Extract method details
451
+ method_info = self._parse_method_signature_optimized(node)
452
+ if not method_info:
453
+ return None
454
+
455
+ (
456
+ name,
457
+ parameters,
458
+ is_async,
459
+ is_static,
460
+ is_getter,
461
+ is_setter,
462
+ is_constructor,
463
+ ) = method_info
464
+
465
+ # Find parent class (currently not used but may be needed for future enhancements)
466
+ # class_name = self._find_parent_class_name(node)
467
+
468
+ # Extract JSDoc
469
+ jsdoc = self._extract_jsdoc_for_line(start_line)
470
+
471
+ # Calculate complexity
472
+ complexity_score = self._calculate_complexity_optimized(node)
473
+
474
+ # Extract raw text
475
+ raw_text = self._get_node_text_optimized(node)
476
+
477
+ return Function(
478
+ name=name,
479
+ start_line=start_line,
480
+ end_line=end_line,
481
+ raw_text=raw_text,
250
482
  language="javascript",
483
+ parameters=parameters,
484
+ return_type="unknown",
485
+ is_async=is_async,
486
+ is_static=is_static,
487
+ is_constructor=is_constructor,
488
+ docstring=jsdoc,
489
+ complexity_score=complexity_score,
490
+ # JavaScript-specific properties
491
+ is_arrow=False,
492
+ is_method=True,
493
+ framework_type=self.framework_type,
251
494
  )
495
+ except Exception as e:
496
+ log_debug(f"Failed to extract method info: {e}")
497
+ # Re-raise for debugging
498
+ raise
499
+
500
+ def _extract_generator_function_optimized(
501
+ self, node: "tree_sitter.Node"
502
+ ) -> Function | None:
503
+ """Extract generator function information"""
504
+ try:
505
+ start_line = node.start_point[0] + 1
506
+ end_line = node.end_point[0] + 1
507
+
508
+ # Extract function details
509
+ function_info = self._parse_function_signature_optimized(node)
510
+ if not function_info:
511
+ return None
512
+
513
+ name, parameters, is_async, _ = function_info
514
+
515
+ # Extract JSDoc
516
+ jsdoc = self._extract_jsdoc_for_line(start_line)
517
+
518
+ # Calculate complexity
519
+ complexity_score = self._calculate_complexity_optimized(node)
520
+
521
+ # Extract raw text
522
+ raw_text = self._get_node_text_optimized(node)
252
523
 
524
+ return Function(
525
+ name=name,
526
+ start_line=start_line,
527
+ end_line=end_line,
528
+ raw_text=raw_text,
529
+ language="javascript",
530
+ parameters=parameters,
531
+ return_type="Generator",
532
+ is_async=is_async,
533
+ is_generator=True,
534
+ docstring=jsdoc,
535
+ complexity_score=complexity_score,
536
+ # JavaScript-specific properties
537
+ is_arrow=False,
538
+ is_method=False,
539
+ framework_type=self.framework_type,
540
+ )
253
541
  except Exception as e:
254
- log_warning(f"Could not extract function info: {e}")
542
+ log_debug(f"Failed to extract generator function info: {e}")
255
543
  return None
256
544
 
257
- def _extract_class_info(
258
- self, node: "tree_sitter.Node", source_code: str
259
- ) -> Class | None:
260
- """Extract class information from AST node"""
545
+ def _extract_class_optimized(self, node: "tree_sitter.Node") -> Class | None:
546
+ """Extract class information with detailed metadata"""
261
547
  try:
262
- name_node = None
548
+ start_line = node.start_point[0] + 1
549
+ end_line = node.end_point[0] + 1
550
+
551
+ # Extract class name
552
+ class_name = None
553
+ superclass = None
263
554
 
264
555
  for child in node.children:
265
556
  if child.type == "identifier":
266
- name_node = child
267
- break
268
-
269
- if not name_node:
557
+ class_name = child.text.decode("utf8") if child.text else None
558
+ elif child.type == "class_heritage":
559
+ # Extract extends clause
560
+ heritage_text = self._get_node_text_optimized(child)
561
+ match = re.search(r"extends\s+(\w+)", heritage_text)
562
+ if match:
563
+ superclass = match.group(1)
564
+
565
+ if not class_name:
270
566
  return None
271
567
 
272
- name = source_code[name_node.start_byte : name_node.end_byte]
568
+ # Extract JSDoc
569
+ jsdoc = self._extract_jsdoc_for_line(start_line)
570
+
571
+ # Check if it's a React component
572
+ is_react_component = self._is_react_component(node, class_name)
573
+
574
+ # Extract raw text
575
+ raw_text = self._get_node_text_optimized(node)
273
576
 
274
577
  return Class(
275
- name=name,
276
- class_type="class",
277
- start_line=node.start_point[0] + 1,
278
- end_line=node.end_point[0] + 1,
279
- raw_text=source_code[node.start_byte : node.end_byte],
578
+ name=class_name,
579
+ start_line=start_line,
580
+ end_line=end_line,
581
+ raw_text=raw_text,
280
582
  language="javascript",
583
+ class_type="class",
584
+ superclass=superclass,
585
+ docstring=jsdoc,
586
+ # JavaScript-specific properties
587
+ is_react_component=is_react_component,
588
+ framework_type=self.framework_type,
589
+ is_exported=self._is_exported_class(class_name),
281
590
  )
591
+ except Exception as e:
592
+ log_debug(f"Failed to extract class info: {e}")
593
+ return None
594
+
595
+ def _extract_variable_optimized(self, node: "tree_sitter.Node") -> list[Variable]:
596
+ """Extract var declaration variables"""
597
+ return self._extract_variables_from_declaration(node, "var")
598
+
599
+ def _extract_lexical_variable_optimized(
600
+ self, node: "tree_sitter.Node"
601
+ ) -> list[Variable]:
602
+ """Extract let/const declaration variables"""
603
+ # Determine if it's let or const
604
+ node_text = self._get_node_text_optimized(node)
605
+ kind = "let" if node_text.strip().startswith("let") else "const"
606
+ return self._extract_variables_from_declaration(node, kind)
607
+
608
+ def _extract_property_optimized(self, node: "tree_sitter.Node") -> Variable | None:
609
+ """Extract class property definition"""
610
+ try:
611
+ start_line = node.start_point[0] + 1
612
+ end_line = node.end_point[0] + 1
613
+
614
+ # Extract property name
615
+ prop_name = None
616
+ prop_value = None
617
+ is_static = False
282
618
 
619
+ for child in node.children:
620
+ if child.type == "property_identifier":
621
+ prop_name = self._get_node_text_optimized(child)
622
+ elif child.type in ["string", "number", "true", "false", "null"]:
623
+ prop_value = self._get_node_text_optimized(child)
624
+
625
+ # Check if static (would be in parent modifiers)
626
+ parent = node.parent
627
+ if parent:
628
+ parent_text = self._get_node_text_optimized(parent)
629
+ is_static = "static" in parent_text
630
+
631
+ if not prop_name:
632
+ return None
633
+
634
+ # Find parent class (currently not used but may be needed for future enhancements)
635
+ # class_name = self._find_parent_class_name(node)
636
+
637
+ # Extract raw text
638
+ raw_text = self._get_node_text_optimized(node)
639
+
640
+ return Variable(
641
+ name=prop_name,
642
+ start_line=start_line,
643
+ end_line=end_line,
644
+ raw_text=raw_text,
645
+ language="javascript",
646
+ variable_type=self._infer_type_from_value(prop_value),
647
+ value=prop_value,
648
+ is_static=is_static,
649
+ is_constant=False, # Class properties are not const
650
+ # JavaScript-specific properties
651
+ # class_name=class_name, # Not available since class_name is commented out
652
+ declaration_kind="property",
653
+ framework_type=self.framework_type,
654
+ )
283
655
  except Exception as e:
284
- log_warning(f"Could not extract class info: {e}")
656
+ log_debug(f"Failed to extract property info: {e}")
285
657
  return None
286
658
 
287
- def _extract_variable_info(
288
- self, node: "tree_sitter.Node", source_code: str
289
- ) -> Variable | None:
290
- """Extract variable information from AST node"""
659
+ def _extract_variables_from_declaration(
660
+ self, node: "tree_sitter.Node", kind: str
661
+ ) -> list[Variable]:
662
+ """Extract variables from declaration node"""
663
+ variables: list[Variable] = []
664
+
291
665
  try:
292
- name_node = None
666
+ start_line = node.start_point[0] + 1
667
+ end_line = node.end_point[0] + 1
293
668
 
294
- # Find the identifier in variable declarator
669
+ # Find variable declarators
295
670
  for child in node.children:
296
671
  if child.type == "variable_declarator":
297
- for subchild in child.children:
298
- if subchild.type == "identifier":
299
- name_node = subchild
300
- break
301
- break
672
+ var_info = self._parse_variable_declarator(
673
+ child, kind, start_line, end_line
674
+ )
675
+ if var_info:
676
+ variables.append(var_info)
677
+
678
+ except Exception as e:
679
+ log_debug(f"Failed to extract variables from declaration: {e}")
680
+
681
+ return variables
302
682
 
303
- if not name_node:
683
+ def _parse_variable_declarator(
684
+ self, node: "tree_sitter.Node", kind: str, start_line: int, end_line: int
685
+ ) -> Variable | None:
686
+ """Parse individual variable declarator"""
687
+ try:
688
+ var_name = None
689
+ var_value = None
690
+
691
+ # Find identifier and value in children
692
+ for child in node.children:
693
+ if child.type == "identifier":
694
+ var_name = self._get_node_text_optimized(child)
695
+ elif child.type == "=" and child.next_sibling:
696
+ # Get the value after the assignment operator
697
+ value_node = child.next_sibling
698
+ var_value = self._get_node_text_optimized(value_node)
699
+ elif child.type in [
700
+ "string",
701
+ "number",
702
+ "true",
703
+ "false",
704
+ "null",
705
+ "object",
706
+ "array",
707
+ "function_expression",
708
+ "arrow_function",
709
+ "call_expression",
710
+ "member_expression",
711
+ "template_literal",
712
+ ]:
713
+ var_value = self._get_node_text_optimized(child)
714
+
715
+ # If no value found through assignment, try to find any value node
716
+ if not var_value and len(node.children) >= 3:
717
+ # Pattern: identifier = value
718
+ for i, child in enumerate(node.children):
719
+ if child.type == "=" and i + 1 < len(node.children):
720
+ value_node = node.children[i + 1]
721
+ # Skip arrow functions - they should be handled by function extractor
722
+ if value_node.type == "arrow_function":
723
+ return None
724
+ var_value = self._get_node_text_optimized(value_node)
725
+ break
726
+
727
+ if not var_name:
304
728
  return None
305
729
 
306
- name = source_code[name_node.start_byte : name_node.end_byte]
730
+ # Skip variables that are arrow functions
731
+ for child in node.children:
732
+ if child.type == "arrow_function":
733
+ return None
734
+
735
+ # Extract JSDoc
736
+ jsdoc = self._extract_jsdoc_for_line(start_line)
737
+
738
+ # Extract raw text with declaration keyword
739
+ raw_text = self._get_node_text_optimized(node)
740
+
741
+ # Try to get parent declaration for complete text
742
+ parent = node.parent
743
+ if parent and parent.type in [
744
+ "lexical_declaration",
745
+ "variable_declaration",
746
+ ]:
747
+ parent_text = self._get_node_text_optimized(parent)
748
+ if parent_text and len(parent_text) > len(raw_text):
749
+ # Only use parent text if it contains our node text
750
+ if raw_text in parent_text:
751
+ raw_text = parent_text
307
752
 
308
753
  return Variable(
309
- name=name,
310
- start_line=node.start_point[0] + 1,
311
- end_line=node.end_point[0] + 1,
312
- raw_text=source_code[node.start_byte : node.end_byte],
754
+ name=var_name,
755
+ start_line=start_line,
756
+ end_line=end_line,
757
+ raw_text=raw_text,
313
758
  language="javascript",
759
+ variable_type=self._infer_type_from_value(var_value),
760
+ is_static=False,
761
+ is_constant=(kind == "const"),
762
+ docstring=jsdoc,
763
+ initializer=var_value, # Use initializer instead of value
764
+ )
765
+ except Exception as e:
766
+ log_debug(f"Failed to parse variable declarator: {e}")
767
+ return None
768
+
769
+ def _parse_function_signature_optimized(
770
+ self, node: "tree_sitter.Node"
771
+ ) -> tuple[str, list[str], bool, bool] | None:
772
+ """Parse function signature for regular functions"""
773
+ try:
774
+ name = None
775
+ parameters = []
776
+ is_async = False
777
+ is_generator = False
778
+
779
+ # Check for async/generator keywords
780
+ node_text = self._get_node_text_optimized(node)
781
+ is_async = "async" in node_text
782
+ is_generator = node.type == "generator_function_declaration"
783
+
784
+ for child in node.children:
785
+ if child.type == "identifier":
786
+ name = child.text.decode("utf8") if child.text else None
787
+ elif child.type == "formal_parameters":
788
+ parameters = self._extract_parameters(child)
789
+
790
+ return name or "", parameters, is_async, is_generator
791
+ except Exception:
792
+ return None
793
+
794
+ def _parse_method_signature_optimized(
795
+ self, node: "tree_sitter.Node"
796
+ ) -> tuple[str, list[str], bool, bool, bool, bool, bool] | None:
797
+ """Parse method signature for class methods"""
798
+ try:
799
+ name = None
800
+ parameters = []
801
+ is_async = False
802
+ is_static = False
803
+ is_getter = False
804
+ is_setter = False
805
+ is_constructor = False
806
+
807
+ # Check for method type
808
+ node_text = self._get_node_text_optimized(node)
809
+ is_async = "async" in node_text
810
+ is_static = "static" in node_text
811
+
812
+ for child in node.children:
813
+ if child.type == "property_identifier":
814
+ name = self._get_node_text_optimized(child)
815
+ is_constructor = name == "constructor"
816
+ elif child.type == "formal_parameters":
817
+ parameters = self._extract_parameters(child)
818
+
819
+ # Check for getter/setter
820
+ if "get " in node_text:
821
+ is_getter = True
822
+ elif "set " in node_text:
823
+ is_setter = True
824
+
825
+ return (
826
+ name,
827
+ parameters,
828
+ is_async,
829
+ is_static,
830
+ is_getter,
831
+ is_setter,
832
+ is_constructor,
833
+ )
834
+ except Exception:
835
+ return None
836
+
837
+ def _extract_parameters(self, params_node: "tree_sitter.Node") -> list[str]:
838
+ """Extract function parameters"""
839
+ parameters = []
840
+
841
+ for child in params_node.children:
842
+ if child.type == "identifier":
843
+ param_name = self._get_node_text_optimized(child)
844
+ parameters.append(param_name)
845
+ elif child.type == "rest_parameter":
846
+ # Handle rest parameters (...args)
847
+ rest_text = self._get_node_text_optimized(child)
848
+ parameters.append(rest_text)
849
+ elif child.type in ["object_pattern", "array_pattern"]:
850
+ # Handle destructuring parameters
851
+ destructure_text = self._get_node_text_optimized(child)
852
+ parameters.append(destructure_text)
853
+
854
+ return parameters
855
+
856
+ def _extract_import_info_simple(self, node: "tree_sitter.Node") -> Import | None:
857
+ """Extract import information from import_statement node"""
858
+ try:
859
+ start_line = node.start_point[0] + 1
860
+ end_line = node.end_point[0] + 1
861
+
862
+ # Get raw text using byte positions
863
+ start_byte = node.start_byte
864
+ end_byte = node.end_byte
865
+ source_bytes = self.source_code.encode("utf-8")
866
+ raw_text = source_bytes[start_byte:end_byte].decode("utf-8")
867
+
868
+ # Extract import details from AST structure
869
+ import_names = []
870
+ module_path = ""
871
+
872
+ for child in node.children:
873
+ if child.type == "import_clause":
874
+ import_names.extend(self._extract_import_names(child))
875
+ elif child.type == "string":
876
+ # Module path
877
+ module_text = source_bytes[
878
+ child.start_byte : child.end_byte
879
+ ].decode("utf-8")
880
+ module_path = module_text.strip("\"'")
881
+
882
+ # Use first import name or "unknown"
883
+ primary_name = import_names[0] if import_names else "unknown"
884
+
885
+ return Import(
886
+ name=primary_name,
887
+ start_line=start_line,
888
+ end_line=end_line,
889
+ raw_text=raw_text,
890
+ language="javascript",
891
+ module_path=module_path,
892
+ module_name=module_path,
893
+ imported_names=import_names,
314
894
  )
315
895
 
316
896
  except Exception as e:
317
- log_warning(f"Could not extract variable info: {e}")
897
+ log_debug(f"Failed to extract import info: {e}")
318
898
  return None
319
899
 
320
- def _extract_import_info(
900
+ def _extract_import_names(
901
+ self, import_clause_node: "tree_sitter.Node"
902
+ ) -> list[str]:
903
+ """Extract import names from import clause"""
904
+ names = []
905
+ source_bytes = self.source_code.encode("utf-8")
906
+
907
+ for child in import_clause_node.children:
908
+ if child.type == "import_default_specifier":
909
+ # Default import
910
+ for grandchild in child.children:
911
+ if grandchild.type == "identifier":
912
+ name_text = source_bytes[
913
+ grandchild.start_byte : grandchild.end_byte
914
+ ].decode("utf-8")
915
+ names.append(name_text)
916
+ elif child.type == "named_imports":
917
+ # Named imports
918
+ for grandchild in child.children:
919
+ if grandchild.type == "import_specifier":
920
+ for ggchild in grandchild.children:
921
+ if ggchild.type == "identifier":
922
+ name_text = source_bytes[
923
+ ggchild.start_byte : ggchild.end_byte
924
+ ].decode("utf-8")
925
+ names.append(name_text)
926
+
927
+ return names
928
+
929
+ def _extract_import_info_enhanced(
321
930
  self, node: "tree_sitter.Node", source_code: str
322
931
  ) -> Import | None:
323
- """Extract import information from AST node"""
932
+ """Extract enhanced import information"""
324
933
  try:
325
- source_node = None
934
+ import_text = self._get_node_text_optimized(node)
326
935
 
327
- for child in node.children:
328
- if child.type == "string":
329
- source_node = child
330
- break
936
+ # Parse different import types
937
+ import_info = self._parse_import_statement(import_text)
938
+ if not import_info:
939
+ return None
331
940
 
332
- if not source_node:
941
+ import_type, names, source, is_default, is_namespace = import_info
942
+
943
+ return Import(
944
+ name=names[0] if names else "unknown",
945
+ start_line=node.start_point[0] + 1,
946
+ end_line=node.end_point[0] + 1,
947
+ raw_text=import_text,
948
+ language="javascript",
949
+ module_path=source,
950
+ # JavaScript-specific properties
951
+ import_type=import_type,
952
+ imported_names=names,
953
+ is_default=is_default,
954
+ is_namespace=is_namespace,
955
+ is_dynamic=False,
956
+ )
957
+ except Exception as e:
958
+ log_debug(f"Failed to extract import info: {e}")
959
+ return None
960
+
961
+ def _extract_dynamic_import(self, node: "tree_sitter.Node") -> Import | None:
962
+ """Extract dynamic import() calls"""
963
+ try:
964
+ node_text = self._get_node_text_optimized(node)
965
+
966
+ # Look for import() calls
967
+ import_match = re.search(
968
+ r"import\s*\(\s*[\"']([^\"']+)[\"']\s*\)", node_text
969
+ )
970
+ if not import_match:
333
971
  return None
334
972
 
335
- module_path = source_code[source_node.start_byte : source_node.end_byte]
336
- # Remove quotes from string
337
- module_path = module_path.strip("\"'")
973
+ source = import_match.group(1)
338
974
 
339
975
  return Import(
340
- name="import",
341
- module_path=module_path,
976
+ name="dynamic_import",
342
977
  start_line=node.start_point[0] + 1,
343
978
  end_line=node.end_point[0] + 1,
344
- raw_text=source_code[node.start_byte : node.end_byte],
979
+ raw_text=node_text,
345
980
  language="javascript",
981
+ module_path=source,
982
+ # JavaScript-specific properties
983
+ import_type="dynamic",
984
+ is_dynamic=True,
346
985
  )
986
+ except Exception as e:
987
+ log_debug(f"Failed to extract dynamic import: {e}")
988
+ return None
989
+
990
+ def _extract_commonjs_requires(
991
+ self, tree: "tree_sitter.Tree", source_code: str
992
+ ) -> list[Import]:
993
+ """Extract CommonJS require() statements"""
994
+ imports = []
995
+
996
+ try:
997
+ # Use regex to find require statements
998
+ require_pattern = r"(?:const|let|var)\s+(\w+)\s*=\s*require\s*\(\s*[\"']([^\"']+)[\"']\s*\)"
999
+
1000
+ for match in re.finditer(require_pattern, source_code):
1001
+ var_name = match.group(1)
1002
+ module_path = match.group(2)
1003
+
1004
+ # Find line number
1005
+ line_num = source_code[: match.start()].count("\n") + 1
1006
+
1007
+ import_obj = Import(
1008
+ name=var_name,
1009
+ start_line=line_num,
1010
+ end_line=line_num,
1011
+ raw_text=match.group(0),
1012
+ language="javascript",
1013
+ module_path=module_path,
1014
+ module_name=module_path,
1015
+ imported_names=[var_name],
1016
+ )
1017
+ imports.append(import_obj)
1018
+
1019
+ except Exception as e:
1020
+ log_debug(f"Failed to extract CommonJS requires: {e}")
1021
+ # Re-raise for debugging
1022
+ raise
1023
+
1024
+ return imports
1025
+
1026
+ def _extract_export_info(self, node: "tree_sitter.Node") -> dict[str, Any] | None:
1027
+ """Extract export information"""
1028
+ try:
1029
+ export_text = self._get_node_text_optimized(node)
1030
+
1031
+ # Parse export type
1032
+ export_info = self._parse_export_statement(export_text)
1033
+ if not export_info:
1034
+ return None
1035
+
1036
+ export_type, names, is_default = export_info
1037
+
1038
+ return {
1039
+ "type": export_type,
1040
+ "names": names,
1041
+ "is_default": is_default,
1042
+ "start_line": node.start_point[0] + 1,
1043
+ "end_line": node.end_point[0] + 1,
1044
+ "raw_text": export_text,
1045
+ }
1046
+ except Exception as e:
1047
+ log_debug(f"Failed to extract export info: {e}")
1048
+ return None
1049
+
1050
+ def _extract_commonjs_exports(
1051
+ self, tree: "tree_sitter.Tree", source_code: str
1052
+ ) -> list[dict[str, Any]]:
1053
+ """Extract CommonJS module.exports statements"""
1054
+ exports = []
1055
+
1056
+ try:
1057
+ # Look for module.exports patterns
1058
+ patterns = [
1059
+ r"module\.exports\s*=\s*(\w+)",
1060
+ r"module\.exports\.(\w+)\s*=",
1061
+ r"exports\.(\w+)\s*=",
1062
+ ]
1063
+
1064
+ for pattern in patterns:
1065
+ for match in re.finditer(pattern, source_code):
1066
+ name = match.group(1)
1067
+ line_num = source_code[: match.start()].count("\n") + 1
1068
+
1069
+ export_obj = {
1070
+ "type": "commonjs",
1071
+ "names": [name],
1072
+ "is_default": "module.exports =" in match.group(0),
1073
+ "start_line": line_num,
1074
+ "end_line": line_num,
1075
+ "raw_text": match.group(0),
1076
+ }
1077
+ exports.append(export_obj)
347
1078
 
348
1079
  except Exception as e:
349
- log_warning(f"Could not extract import info: {e}")
1080
+ log_debug(f"Failed to extract CommonJS exports: {e}")
1081
+
1082
+ return exports
1083
+
1084
+ def _parse_import_statement(
1085
+ self, import_text: str
1086
+ ) -> tuple[str, list[str], str, bool, bool] | None:
1087
+ """Parse import statement to extract details"""
1088
+ try:
1089
+ # Remove semicolon and clean up
1090
+ clean_text = import_text.strip().rstrip(";")
1091
+
1092
+ # Extract source
1093
+ source_match = re.search(r"from\s+[\"']([^\"']+)[\"']", clean_text)
1094
+ if not source_match:
1095
+ return None
1096
+
1097
+ source = source_match.group(1)
1098
+
1099
+ # Determine import type and extract names
1100
+ if "import * as" in clean_text:
1101
+ # Namespace import
1102
+ namespace_match = re.search(r"import\s+\*\s+as\s+(\w+)", clean_text)
1103
+ if namespace_match:
1104
+ return "namespace", [namespace_match.group(1)], source, False, True
1105
+
1106
+ elif "import {" in clean_text:
1107
+ # Named imports
1108
+ named_match = re.search(r"import\s+\{([^}]+)\}", clean_text)
1109
+ if named_match:
1110
+ names_text = named_match.group(1)
1111
+ names = [name.strip() for name in names_text.split(",")]
1112
+ return "named", names, source, False, False
1113
+
1114
+ else:
1115
+ # Default import
1116
+ default_match = re.search(r"import\s+(\w+)", clean_text)
1117
+ if default_match:
1118
+ return "default", [default_match.group(1)], source, True, False
1119
+
1120
+ return None
1121
+ except Exception:
350
1122
  return None
351
1123
 
1124
+ def _parse_export_statement(
1125
+ self, export_text: str
1126
+ ) -> tuple[str, list[str], bool] | None:
1127
+ """Parse export statement to extract details"""
1128
+ try:
1129
+ clean_text = export_text.strip().rstrip(";")
1130
+
1131
+ if "export default" in clean_text:
1132
+ # Default export
1133
+ default_match = re.search(r"export\s+default\s+(\w+)", clean_text)
1134
+ if default_match:
1135
+ return "default", [default_match.group(1)], True
1136
+ else:
1137
+ return "default", ["default"], True
1138
+
1139
+ elif "export {" in clean_text:
1140
+ # Named exports
1141
+ named_match = re.search(r"export\s+\{([^}]+)\}", clean_text)
1142
+ if named_match:
1143
+ names_text = named_match.group(1)
1144
+ names = [name.strip() for name in names_text.split(",")]
1145
+ return "named", names, False
1146
+
1147
+ elif (
1148
+ clean_text.startswith("export ")
1149
+ and not clean_text == "invalid export statement"
1150
+ ):
1151
+ # Direct export (export function, export class, etc.)
1152
+ # But skip obviously invalid statements
1153
+ direct_match = re.search(
1154
+ r"export\s+(function|class|const|let|var)\s+(\w+)", clean_text
1155
+ )
1156
+ if direct_match:
1157
+ return "direct", [direct_match.group(2)], False
1158
+ else:
1159
+ return "direct", ["unknown"], False
1160
+
1161
+ return None
1162
+ except Exception:
1163
+ return None
1164
+
1165
+ def _find_parent_class_name(self, node: "tree_sitter.Node") -> str | None:
1166
+ """Find parent class name for methods/properties"""
1167
+ current = node.parent
1168
+ while current:
1169
+ if current.type in ["class_declaration", "class_expression"]:
1170
+ for child in current.children:
1171
+ if child.type == "identifier":
1172
+ return self._get_node_text_optimized(child)
1173
+ current = current.parent
1174
+ return None
1175
+
1176
+ def _is_react_component(self, node: "tree_sitter.Node", class_name: str) -> bool:
1177
+ """Check if class is a React component"""
1178
+ if self.framework_type != "react":
1179
+ return False
1180
+
1181
+ # Check if extends React.Component or Component
1182
+ node_text = self._get_node_text_optimized(node)
1183
+ return "extends" in node_text and (
1184
+ "Component" in node_text or "PureComponent" in node_text
1185
+ )
1186
+
1187
+ def _is_exported_class(self, class_name: str) -> bool:
1188
+ """Check if class is exported"""
1189
+ for export in self.exports:
1190
+ if class_name in export.get("names", []):
1191
+ return True
1192
+ return False
1193
+
1194
+ def _infer_type_from_value(self, value: str | None) -> str:
1195
+ """Infer JavaScript type from value"""
1196
+ if not value:
1197
+ return "unknown"
1198
+
1199
+ value = value.strip()
1200
+
1201
+ if value.startswith('"') or value.startswith("'") or value.startswith("`"):
1202
+ return "string"
1203
+ elif value in ["true", "false"]:
1204
+ return "boolean"
1205
+ elif value == "null":
1206
+ return "null"
1207
+ elif value == "undefined":
1208
+ return "undefined"
1209
+ elif value.startswith("[") and value.endswith("]"):
1210
+ return "array"
1211
+ elif value.startswith("{") and value.endswith("}"):
1212
+ return "object"
1213
+ elif value.replace(".", "").replace("-", "").isdigit():
1214
+ return "number"
1215
+ elif "function" in value or "=>" in value:
1216
+ return "function"
1217
+ else:
1218
+ return "unknown"
1219
+
1220
+ def _get_variable_kind(self, var_data: dict | str) -> str:
1221
+ """Get variable declaration kind from variable data or raw text"""
1222
+ if isinstance(var_data, dict):
1223
+ raw_text = var_data.get("raw_text", "")
1224
+ else:
1225
+ raw_text = var_data
1226
+
1227
+ if not raw_text:
1228
+ return "unknown"
1229
+
1230
+ raw_text = str(raw_text).strip()
1231
+ if raw_text.startswith("const"):
1232
+ return "const"
1233
+ elif raw_text.startswith("let"):
1234
+ return "let"
1235
+ elif raw_text.startswith("var"):
1236
+ return "var"
1237
+ else:
1238
+ return "unknown"
1239
+
1240
+ def _extract_jsdoc_for_line(self, target_line: int) -> str | None:
1241
+ """Extract JSDoc comment immediately before the specified line"""
1242
+ if target_line in self._jsdoc_cache:
1243
+ return self._jsdoc_cache[target_line]
1244
+
1245
+ try:
1246
+ if not self.content_lines or target_line <= 1:
1247
+ return None
1248
+
1249
+ # Search backwards from target_line
1250
+ jsdoc_lines = []
1251
+ current_line = target_line - 1
1252
+
1253
+ # Skip empty lines
1254
+ while current_line > 0:
1255
+ line = self.content_lines[current_line - 1].strip()
1256
+ if line:
1257
+ break
1258
+ current_line -= 1
1259
+
1260
+ # Check for JSDoc end
1261
+ if current_line > 0:
1262
+ line = self.content_lines[current_line - 1].strip()
1263
+ if line.endswith("*/"):
1264
+ jsdoc_lines.append(self.content_lines[current_line - 1])
1265
+ current_line -= 1
1266
+
1267
+ # Collect JSDoc content
1268
+ while current_line > 0:
1269
+ line_content = self.content_lines[current_line - 1]
1270
+ line_stripped = line_content.strip()
1271
+ jsdoc_lines.append(line_content)
1272
+
1273
+ if line_stripped.startswith("/**"):
1274
+ jsdoc_lines.reverse()
1275
+ jsdoc_text = "\n".join(jsdoc_lines)
1276
+ cleaned = self._clean_jsdoc(jsdoc_text)
1277
+ self._jsdoc_cache[target_line] = cleaned
1278
+ return cleaned
1279
+ current_line -= 1
1280
+
1281
+ self._jsdoc_cache[target_line] = None
1282
+ return None
1283
+
1284
+ except Exception as e:
1285
+ log_debug(f"Failed to extract JSDoc: {e}")
1286
+ return None
1287
+
1288
+ def _clean_jsdoc(self, jsdoc_text: str) -> str:
1289
+ """Clean JSDoc text by removing comment markers"""
1290
+ if not jsdoc_text:
1291
+ return ""
1292
+
1293
+ lines = jsdoc_text.split("\n")
1294
+ cleaned_lines = []
1295
+
1296
+ for line in lines:
1297
+ line = line.strip()
1298
+
1299
+ if line.startswith("/**"):
1300
+ line = line[3:].strip()
1301
+ elif line.startswith("*/"):
1302
+ line = line[2:].strip()
1303
+ elif line.startswith("*"):
1304
+ line = line[1:].strip()
1305
+
1306
+ if line:
1307
+ cleaned_lines.append(line)
1308
+
1309
+ return " ".join(cleaned_lines) if cleaned_lines else ""
1310
+
1311
+ def _calculate_complexity_optimized(self, node: "tree_sitter.Node") -> int:
1312
+ """Calculate cyclomatic complexity efficiently"""
1313
+ node_id = id(node)
1314
+ if node_id in self._complexity_cache:
1315
+ return self._complexity_cache[node_id]
1316
+
1317
+ complexity = 1
1318
+ try:
1319
+ node_text = self._get_node_text_optimized(node).lower()
1320
+ keywords = [
1321
+ "if",
1322
+ "else if",
1323
+ "while",
1324
+ "for",
1325
+ "catch",
1326
+ "case",
1327
+ "switch",
1328
+ "&&",
1329
+ "||",
1330
+ "?",
1331
+ ]
1332
+ for keyword in keywords:
1333
+ complexity += node_text.count(keyword)
1334
+ except Exception as e:
1335
+ log_debug(f"Failed to calculate complexity: {e}")
1336
+
1337
+ self._complexity_cache[node_id] = complexity
1338
+ return complexity
1339
+
352
1340
 
353
1341
  class JavaScriptPlugin(LanguagePlugin):
354
- """JavaScript language plugin"""
1342
+ """Enhanced JavaScript language plugin with comprehensive feature support"""
355
1343
 
356
1344
  def __init__(self) -> None:
357
1345
  self._extractor = JavaScriptElementExtractor()
@@ -363,7 +1351,7 @@ class JavaScriptPlugin(LanguagePlugin):
363
1351
 
364
1352
  @property
365
1353
  def file_extensions(self) -> list[str]:
366
- return [".js", ".mjs", ".jsx"]
1354
+ return [".js", ".mjs", ".jsx", ".es6", ".es"]
367
1355
 
368
1356
  def get_language_name(self) -> str:
369
1357
  """Return the name of the programming language this plugin supports"""
@@ -371,7 +1359,7 @@ class JavaScriptPlugin(LanguagePlugin):
371
1359
 
372
1360
  def get_file_extensions(self) -> list[str]:
373
1361
  """Return list of file extensions this plugin supports"""
374
- return [".js", ".mjs", ".jsx"]
1362
+ return [".js", ".mjs", ".jsx", ".es6", ".es"]
375
1363
 
376
1364
  def create_extractor(self) -> ElementExtractor:
377
1365
  """Create and return an element extractor for this language"""
@@ -386,6 +1374,52 @@ class JavaScriptPlugin(LanguagePlugin):
386
1374
  self._language = loader.load_language("javascript")
387
1375
  return self._language
388
1376
 
1377
+ def get_supported_queries(self) -> list[str]:
1378
+ """Get list of supported query names for this language"""
1379
+ return [
1380
+ "function",
1381
+ "class",
1382
+ "variable",
1383
+ "import",
1384
+ "export",
1385
+ "async_function",
1386
+ "arrow_function",
1387
+ "method",
1388
+ "constructor",
1389
+ "react_component",
1390
+ "react_hook",
1391
+ "jsx_element",
1392
+ ]
1393
+
1394
+ def is_applicable(self, file_path: str) -> bool:
1395
+ """Check if this plugin is applicable for the given file"""
1396
+ return any(
1397
+ file_path.lower().endswith(ext.lower())
1398
+ for ext in self.get_file_extensions()
1399
+ )
1400
+
1401
+ def get_plugin_info(self) -> dict:
1402
+ """Get information about this plugin"""
1403
+ return {
1404
+ "name": "JavaScript Plugin",
1405
+ "language": self.get_language_name(),
1406
+ "extensions": self.get_file_extensions(),
1407
+ "version": "2.0.0",
1408
+ "supported_queries": self.get_supported_queries(),
1409
+ "features": [
1410
+ "ES6+ syntax support",
1411
+ "Async/await functions",
1412
+ "Arrow functions",
1413
+ "Classes and methods",
1414
+ "Module imports/exports",
1415
+ "JSX support",
1416
+ "React component detection",
1417
+ "CommonJS support",
1418
+ "JSDoc extraction",
1419
+ "Complexity analysis",
1420
+ ],
1421
+ }
1422
+
389
1423
  async def analyze_file(
390
1424
  self, file_path: str, request: AnalysisRequest
391
1425
  ) -> AnalysisResult:
@@ -416,11 +1450,28 @@ class JavaScriptPlugin(LanguagePlugin):
416
1450
  tree = parser.parse(bytes(source_code, "utf8"))
417
1451
 
418
1452
  extractor = self.create_extractor()
1453
+ extractor.current_file = file_path # Set current file for context
1454
+
419
1455
  elements: list[CodeElement] = []
420
- elements.extend(extractor.extract_functions(tree, source_code))
421
- elements.extend(extractor.extract_classes(tree, source_code))
422
- elements.extend(extractor.extract_variables(tree, source_code))
423
- elements.extend(extractor.extract_imports(tree, source_code))
1456
+
1457
+ # Extract all element types
1458
+ functions = extractor.extract_functions(tree, source_code)
1459
+ classes = extractor.extract_classes(tree, source_code)
1460
+ variables = extractor.extract_variables(tree, source_code)
1461
+ imports = extractor.extract_imports(tree, source_code)
1462
+
1463
+ # Add exports if extractor supports it
1464
+ if hasattr(extractor, "extract_exports"):
1465
+ # exports = extractor.extract_exports(tree, source_code)
1466
+ # TODO: Add exports to elements when export extraction is fully implemented
1467
+ # elements.extend(exports)
1468
+ pass
1469
+ # else: exports not needed if not supported
1470
+
1471
+ elements.extend(functions)
1472
+ elements.extend(classes)
1473
+ elements.extend(variables)
1474
+ elements.extend(imports)
424
1475
 
425
1476
  def count_nodes(node: "tree_sitter.Node") -> int:
426
1477
  count = 1