tree-sitter-analyzer 1.9.17.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (149) hide show
  1. tree_sitter_analyzer/__init__.py +132 -0
  2. tree_sitter_analyzer/__main__.py +11 -0
  3. tree_sitter_analyzer/api.py +853 -0
  4. tree_sitter_analyzer/cli/__init__.py +39 -0
  5. tree_sitter_analyzer/cli/__main__.py +12 -0
  6. tree_sitter_analyzer/cli/argument_validator.py +89 -0
  7. tree_sitter_analyzer/cli/commands/__init__.py +26 -0
  8. tree_sitter_analyzer/cli/commands/advanced_command.py +226 -0
  9. tree_sitter_analyzer/cli/commands/base_command.py +181 -0
  10. tree_sitter_analyzer/cli/commands/default_command.py +18 -0
  11. tree_sitter_analyzer/cli/commands/find_and_grep_cli.py +188 -0
  12. tree_sitter_analyzer/cli/commands/list_files_cli.py +133 -0
  13. tree_sitter_analyzer/cli/commands/partial_read_command.py +139 -0
  14. tree_sitter_analyzer/cli/commands/query_command.py +109 -0
  15. tree_sitter_analyzer/cli/commands/search_content_cli.py +161 -0
  16. tree_sitter_analyzer/cli/commands/structure_command.py +156 -0
  17. tree_sitter_analyzer/cli/commands/summary_command.py +116 -0
  18. tree_sitter_analyzer/cli/commands/table_command.py +414 -0
  19. tree_sitter_analyzer/cli/info_commands.py +124 -0
  20. tree_sitter_analyzer/cli_main.py +472 -0
  21. tree_sitter_analyzer/constants.py +85 -0
  22. tree_sitter_analyzer/core/__init__.py +15 -0
  23. tree_sitter_analyzer/core/analysis_engine.py +580 -0
  24. tree_sitter_analyzer/core/cache_service.py +333 -0
  25. tree_sitter_analyzer/core/engine.py +585 -0
  26. tree_sitter_analyzer/core/parser.py +293 -0
  27. tree_sitter_analyzer/core/query.py +605 -0
  28. tree_sitter_analyzer/core/query_filter.py +200 -0
  29. tree_sitter_analyzer/core/query_service.py +340 -0
  30. tree_sitter_analyzer/encoding_utils.py +530 -0
  31. tree_sitter_analyzer/exceptions.py +747 -0
  32. tree_sitter_analyzer/file_handler.py +246 -0
  33. tree_sitter_analyzer/formatters/__init__.py +1 -0
  34. tree_sitter_analyzer/formatters/base_formatter.py +201 -0
  35. tree_sitter_analyzer/formatters/csharp_formatter.py +367 -0
  36. tree_sitter_analyzer/formatters/formatter_config.py +197 -0
  37. tree_sitter_analyzer/formatters/formatter_factory.py +84 -0
  38. tree_sitter_analyzer/formatters/formatter_registry.py +377 -0
  39. tree_sitter_analyzer/formatters/formatter_selector.py +96 -0
  40. tree_sitter_analyzer/formatters/go_formatter.py +368 -0
  41. tree_sitter_analyzer/formatters/html_formatter.py +498 -0
  42. tree_sitter_analyzer/formatters/java_formatter.py +423 -0
  43. tree_sitter_analyzer/formatters/javascript_formatter.py +611 -0
  44. tree_sitter_analyzer/formatters/kotlin_formatter.py +268 -0
  45. tree_sitter_analyzer/formatters/language_formatter_factory.py +123 -0
  46. tree_sitter_analyzer/formatters/legacy_formatter_adapters.py +228 -0
  47. tree_sitter_analyzer/formatters/markdown_formatter.py +725 -0
  48. tree_sitter_analyzer/formatters/php_formatter.py +301 -0
  49. tree_sitter_analyzer/formatters/python_formatter.py +830 -0
  50. tree_sitter_analyzer/formatters/ruby_formatter.py +278 -0
  51. tree_sitter_analyzer/formatters/rust_formatter.py +233 -0
  52. tree_sitter_analyzer/formatters/sql_formatter_wrapper.py +689 -0
  53. tree_sitter_analyzer/formatters/sql_formatters.py +536 -0
  54. tree_sitter_analyzer/formatters/typescript_formatter.py +543 -0
  55. tree_sitter_analyzer/formatters/yaml_formatter.py +462 -0
  56. tree_sitter_analyzer/interfaces/__init__.py +9 -0
  57. tree_sitter_analyzer/interfaces/cli.py +535 -0
  58. tree_sitter_analyzer/interfaces/cli_adapter.py +359 -0
  59. tree_sitter_analyzer/interfaces/mcp_adapter.py +224 -0
  60. tree_sitter_analyzer/interfaces/mcp_server.py +428 -0
  61. tree_sitter_analyzer/language_detector.py +553 -0
  62. tree_sitter_analyzer/language_loader.py +271 -0
  63. tree_sitter_analyzer/languages/__init__.py +10 -0
  64. tree_sitter_analyzer/languages/csharp_plugin.py +1076 -0
  65. tree_sitter_analyzer/languages/css_plugin.py +449 -0
  66. tree_sitter_analyzer/languages/go_plugin.py +836 -0
  67. tree_sitter_analyzer/languages/html_plugin.py +496 -0
  68. tree_sitter_analyzer/languages/java_plugin.py +1299 -0
  69. tree_sitter_analyzer/languages/javascript_plugin.py +1622 -0
  70. tree_sitter_analyzer/languages/kotlin_plugin.py +656 -0
  71. tree_sitter_analyzer/languages/markdown_plugin.py +1928 -0
  72. tree_sitter_analyzer/languages/php_plugin.py +862 -0
  73. tree_sitter_analyzer/languages/python_plugin.py +1636 -0
  74. tree_sitter_analyzer/languages/ruby_plugin.py +757 -0
  75. tree_sitter_analyzer/languages/rust_plugin.py +673 -0
  76. tree_sitter_analyzer/languages/sql_plugin.py +2444 -0
  77. tree_sitter_analyzer/languages/typescript_plugin.py +1892 -0
  78. tree_sitter_analyzer/languages/yaml_plugin.py +695 -0
  79. tree_sitter_analyzer/legacy_table_formatter.py +860 -0
  80. tree_sitter_analyzer/mcp/__init__.py +34 -0
  81. tree_sitter_analyzer/mcp/resources/__init__.py +43 -0
  82. tree_sitter_analyzer/mcp/resources/code_file_resource.py +208 -0
  83. tree_sitter_analyzer/mcp/resources/project_stats_resource.py +586 -0
  84. tree_sitter_analyzer/mcp/server.py +869 -0
  85. tree_sitter_analyzer/mcp/tools/__init__.py +28 -0
  86. tree_sitter_analyzer/mcp/tools/analyze_scale_tool.py +779 -0
  87. tree_sitter_analyzer/mcp/tools/analyze_scale_tool_cli_compatible.py +291 -0
  88. tree_sitter_analyzer/mcp/tools/base_tool.py +139 -0
  89. tree_sitter_analyzer/mcp/tools/fd_rg_utils.py +816 -0
  90. tree_sitter_analyzer/mcp/tools/find_and_grep_tool.py +686 -0
  91. tree_sitter_analyzer/mcp/tools/list_files_tool.py +413 -0
  92. tree_sitter_analyzer/mcp/tools/output_format_validator.py +148 -0
  93. tree_sitter_analyzer/mcp/tools/query_tool.py +443 -0
  94. tree_sitter_analyzer/mcp/tools/read_partial_tool.py +464 -0
  95. tree_sitter_analyzer/mcp/tools/search_content_tool.py +836 -0
  96. tree_sitter_analyzer/mcp/tools/table_format_tool.py +572 -0
  97. tree_sitter_analyzer/mcp/tools/universal_analyze_tool.py +653 -0
  98. tree_sitter_analyzer/mcp/utils/__init__.py +113 -0
  99. tree_sitter_analyzer/mcp/utils/error_handler.py +569 -0
  100. tree_sitter_analyzer/mcp/utils/file_output_factory.py +217 -0
  101. tree_sitter_analyzer/mcp/utils/file_output_manager.py +322 -0
  102. tree_sitter_analyzer/mcp/utils/gitignore_detector.py +358 -0
  103. tree_sitter_analyzer/mcp/utils/path_resolver.py +414 -0
  104. tree_sitter_analyzer/mcp/utils/search_cache.py +343 -0
  105. tree_sitter_analyzer/models.py +840 -0
  106. tree_sitter_analyzer/mypy_current_errors.txt +2 -0
  107. tree_sitter_analyzer/output_manager.py +255 -0
  108. tree_sitter_analyzer/platform_compat/__init__.py +3 -0
  109. tree_sitter_analyzer/platform_compat/adapter.py +324 -0
  110. tree_sitter_analyzer/platform_compat/compare.py +224 -0
  111. tree_sitter_analyzer/platform_compat/detector.py +67 -0
  112. tree_sitter_analyzer/platform_compat/fixtures.py +228 -0
  113. tree_sitter_analyzer/platform_compat/profiles.py +217 -0
  114. tree_sitter_analyzer/platform_compat/record.py +55 -0
  115. tree_sitter_analyzer/platform_compat/recorder.py +155 -0
  116. tree_sitter_analyzer/platform_compat/report.py +92 -0
  117. tree_sitter_analyzer/plugins/__init__.py +280 -0
  118. tree_sitter_analyzer/plugins/base.py +647 -0
  119. tree_sitter_analyzer/plugins/manager.py +384 -0
  120. tree_sitter_analyzer/project_detector.py +328 -0
  121. tree_sitter_analyzer/queries/__init__.py +27 -0
  122. tree_sitter_analyzer/queries/csharp.py +216 -0
  123. tree_sitter_analyzer/queries/css.py +615 -0
  124. tree_sitter_analyzer/queries/go.py +275 -0
  125. tree_sitter_analyzer/queries/html.py +543 -0
  126. tree_sitter_analyzer/queries/java.py +402 -0
  127. tree_sitter_analyzer/queries/javascript.py +724 -0
  128. tree_sitter_analyzer/queries/kotlin.py +192 -0
  129. tree_sitter_analyzer/queries/markdown.py +258 -0
  130. tree_sitter_analyzer/queries/php.py +95 -0
  131. tree_sitter_analyzer/queries/python.py +859 -0
  132. tree_sitter_analyzer/queries/ruby.py +92 -0
  133. tree_sitter_analyzer/queries/rust.py +223 -0
  134. tree_sitter_analyzer/queries/sql.py +555 -0
  135. tree_sitter_analyzer/queries/typescript.py +871 -0
  136. tree_sitter_analyzer/queries/yaml.py +236 -0
  137. tree_sitter_analyzer/query_loader.py +272 -0
  138. tree_sitter_analyzer/security/__init__.py +22 -0
  139. tree_sitter_analyzer/security/boundary_manager.py +277 -0
  140. tree_sitter_analyzer/security/regex_checker.py +297 -0
  141. tree_sitter_analyzer/security/validator.py +599 -0
  142. tree_sitter_analyzer/table_formatter.py +782 -0
  143. tree_sitter_analyzer/utils/__init__.py +53 -0
  144. tree_sitter_analyzer/utils/logging.py +433 -0
  145. tree_sitter_analyzer/utils/tree_sitter_compat.py +289 -0
  146. tree_sitter_analyzer-1.9.17.1.dist-info/METADATA +485 -0
  147. tree_sitter_analyzer-1.9.17.1.dist-info/RECORD +149 -0
  148. tree_sitter_analyzer-1.9.17.1.dist-info/WHEEL +4 -0
  149. tree_sitter_analyzer-1.9.17.1.dist-info/entry_points.txt +25 -0
@@ -0,0 +1,1622 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ JavaScript Language Plugin
4
+
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.
9
+ """
10
+
11
+ import re
12
+ from typing import TYPE_CHECKING, Any, Optional
13
+
14
+ if TYPE_CHECKING:
15
+ import tree_sitter
16
+
17
+ try:
18
+ import tree_sitter
19
+
20
+ TREE_SITTER_AVAILABLE = True
21
+ except ImportError:
22
+ TREE_SITTER_AVAILABLE = False
23
+
24
+ from ..core.analysis_engine import AnalysisRequest
25
+ from ..encoding_utils import extract_text_slice, safe_encode
26
+ from ..language_loader import loader
27
+ from ..models import AnalysisResult, Class, CodeElement, Function, Import, Variable
28
+ from ..plugins.base import ElementExtractor, LanguagePlugin
29
+ from ..utils import log_debug, log_error, log_warning
30
+
31
+
32
+ class JavaScriptElementExtractor(ElementExtractor):
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.
55
+
56
+ def extract_functions(
57
+ self, tree: "tree_sitter.Tree", source_code: str
58
+ ) -> list[Function]:
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")
81
+ return functions
82
+
83
+ def extract_classes(
84
+ self, tree: "tree_sitter.Tree", source_code: str
85
+ ) -> list[Class]:
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()
90
+
91
+ classes: list[Class] = []
92
+
93
+ # Extract both class declarations and expressions
94
+ extractors = {
95
+ "class_declaration": self._extract_class_optimized,
96
+ "class_expression": self._extract_class_optimized,
97
+ }
98
+
99
+ self._traverse_and_extract_iterative(
100
+ tree.root_node, extractors, classes, "class"
101
+ )
102
+
103
+ log_debug(f"Extracted {len(classes)} JavaScript classes")
104
+ return classes
105
+
106
+ def extract_variables(
107
+ self, tree: "tree_sitter.Tree", source_code: str
108
+ ) -> list[Variable]:
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()
113
+
114
+ variables: list[Variable] = []
115
+
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
+ )
126
+
127
+ log_debug(f"Extracted {len(variables)} JavaScript variables")
128
+ return variables
129
+
130
+ def extract_imports(
131
+ self, tree: "tree_sitter.Tree", source_code: str
132
+ ) -> list[Import]:
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
157
+
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: Optional["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]
298
+
299
+ try:
300
+ start_byte = node.start_byte
301
+ end_byte = node.end_byte
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
309
+ except Exception as 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
+ result: str = line[start_point[1] : end_point[1]]
319
+ return result
320
+ else:
321
+ lines = []
322
+ for i in range(start_point[0], end_point[0] + 1):
323
+ if i < len(self.content_lines):
324
+ line = self.content_lines[i]
325
+ if i == start_point[0]:
326
+ lines.append(line[start_point[1] :])
327
+ elif i == end_point[0]:
328
+ lines.append(line[: end_point[1]])
329
+ else:
330
+ lines.append(line)
331
+ return "\n".join(lines)
332
+ except Exception as fallback_error:
333
+ log_error(f"Fallback text extraction also failed: {fallback_error}")
334
+ return ""
335
+
336
+ def _extract_function_optimized(self, node: "tree_sitter.Node") -> Function | None:
337
+ """Extract regular function information with detailed metadata"""
338
+ try:
339
+ start_line = node.start_point[0] + 1
340
+ end_line = node.end_point[0] + 1
341
+
342
+ # Extract function details
343
+ function_info = self._parse_function_signature_optimized(node)
344
+ if not function_info:
345
+ return None
346
+
347
+ name, parameters, is_async, is_generator = function_info
348
+
349
+ # Extract JSDoc
350
+ jsdoc = self._extract_jsdoc_for_line(start_line)
351
+
352
+ # Calculate complexity
353
+ complexity_score = self._calculate_complexity_optimized(node)
354
+
355
+ # Extract raw text
356
+ start_line_idx = max(0, start_line - 1)
357
+ end_line_idx = min(len(self.content_lines), end_line)
358
+ raw_text = "\n".join(self.content_lines[start_line_idx:end_line_idx])
359
+
360
+ return Function(
361
+ name=name,
362
+ start_line=start_line,
363
+ end_line=end_line,
364
+ raw_text=raw_text,
365
+ language="javascript",
366
+ parameters=parameters,
367
+ return_type="unknown", # JavaScript is dynamically typed
368
+ is_async=is_async,
369
+ is_generator=is_generator,
370
+ docstring=jsdoc,
371
+ complexity_score=complexity_score,
372
+ # JavaScript-specific properties
373
+ is_arrow=False,
374
+ is_method=False,
375
+ framework_type=self.framework_type,
376
+ )
377
+ except Exception as e:
378
+ log_error(f"Failed to extract function info: {e}")
379
+ import traceback
380
+
381
+ traceback.print_exc()
382
+ return None
383
+
384
+ def _extract_arrow_function_optimized(
385
+ self, node: "tree_sitter.Node"
386
+ ) -> Function | None:
387
+ """Extract arrow function information"""
388
+ try:
389
+ start_line = node.start_point[0] + 1
390
+ end_line = node.end_point[0] + 1
391
+
392
+ # For arrow functions, we need to find the variable declaration
393
+ parent = node.parent
394
+ name = "anonymous"
395
+
396
+ if parent and parent.type == "variable_declarator":
397
+ for child in parent.children:
398
+ if child.type == "identifier":
399
+ name = self._get_node_text_optimized(child)
400
+ break
401
+
402
+ # Extract parameters
403
+ parameters = []
404
+ for child in node.children:
405
+ if child.type == "formal_parameters":
406
+ parameters = self._extract_parameters(child)
407
+ elif child.type == "identifier":
408
+ # Single parameter without parentheses
409
+ param_name = self._get_node_text_optimized(child)
410
+ parameters = [param_name]
411
+
412
+ # Check if async
413
+ is_async = "async" in self._get_node_text_optimized(node)
414
+
415
+ # Extract JSDoc (look at parent variable declaration)
416
+ jsdoc = self._extract_jsdoc_for_line(start_line)
417
+
418
+ # Calculate complexity
419
+ complexity_score = self._calculate_complexity_optimized(node)
420
+
421
+ # Extract raw text
422
+ raw_text = self._get_node_text_optimized(node)
423
+
424
+ return Function(
425
+ name=name,
426
+ start_line=start_line,
427
+ end_line=end_line,
428
+ raw_text=raw_text,
429
+ language="javascript",
430
+ parameters=parameters,
431
+ return_type="unknown",
432
+ is_async=is_async,
433
+ is_generator=False,
434
+ docstring=jsdoc,
435
+ complexity_score=complexity_score,
436
+ # JavaScript-specific properties
437
+ is_arrow=True,
438
+ is_method=False,
439
+ framework_type=self.framework_type,
440
+ )
441
+ except Exception as e:
442
+ log_debug(f"Failed to extract arrow function info: {e}")
443
+ return None
444
+
445
+ def _extract_method_optimized(self, node: "tree_sitter.Node") -> Function | None:
446
+ """Extract method information from class"""
447
+ try:
448
+ start_line = node.start_point[0] + 1
449
+ end_line = node.end_point[0] + 1
450
+
451
+ # Extract method details
452
+ method_info = self._parse_method_signature_optimized(node)
453
+ if not method_info:
454
+ return None
455
+
456
+ (
457
+ name,
458
+ parameters,
459
+ is_async,
460
+ is_static,
461
+ is_getter,
462
+ is_setter,
463
+ is_constructor,
464
+ ) = method_info
465
+
466
+ # Find parent class (currently not used but may be needed for future enhancements)
467
+ # class_name = self._find_parent_class_name(node)
468
+
469
+ # Extract JSDoc
470
+ jsdoc = self._extract_jsdoc_for_line(start_line)
471
+
472
+ # Calculate complexity
473
+ complexity_score = self._calculate_complexity_optimized(node)
474
+
475
+ # Extract raw text
476
+ raw_text = self._get_node_text_optimized(node)
477
+
478
+ return Function(
479
+ name=name,
480
+ start_line=start_line,
481
+ end_line=end_line,
482
+ raw_text=raw_text,
483
+ language="javascript",
484
+ parameters=parameters,
485
+ return_type="unknown",
486
+ is_async=is_async,
487
+ is_static=is_static,
488
+ is_constructor=is_constructor,
489
+ docstring=jsdoc,
490
+ complexity_score=complexity_score,
491
+ # JavaScript-specific properties
492
+ is_arrow=False,
493
+ is_method=True,
494
+ framework_type=self.framework_type,
495
+ )
496
+ except Exception as e:
497
+ log_debug(f"Failed to extract method info: {e}")
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)
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
+ )
541
+ except Exception as e:
542
+ log_debug(f"Failed to extract generator function info: {e}")
543
+ return None
544
+
545
+ def _extract_class_optimized(self, node: "tree_sitter.Node") -> Class | None:
546
+ """Extract class information with detailed metadata"""
547
+ try:
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
554
+
555
+ for child in node.children:
556
+ if child.type == "identifier":
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
+ # Support both simple names (Component) and dotted names (React.Component)
562
+ match = re.search(r"extends\s+([\w.]+)", heritage_text)
563
+ if match:
564
+ superclass = match.group(1)
565
+
566
+ if not class_name:
567
+ return None
568
+
569
+ # Extract JSDoc
570
+ jsdoc = self._extract_jsdoc_for_line(start_line)
571
+
572
+ # Check if it's a React component
573
+ is_react_component = self._is_react_component(node, class_name)
574
+
575
+ # Extract raw text
576
+ raw_text = self._get_node_text_optimized(node)
577
+
578
+ return Class(
579
+ name=class_name,
580
+ start_line=start_line,
581
+ end_line=end_line,
582
+ raw_text=raw_text,
583
+ language="javascript",
584
+ class_type="class",
585
+ superclass=superclass,
586
+ docstring=jsdoc,
587
+ # JavaScript-specific properties
588
+ is_react_component=is_react_component,
589
+ framework_type=self.framework_type,
590
+ is_exported=self._is_exported_class(class_name),
591
+ )
592
+ except Exception as e:
593
+ log_debug(f"Failed to extract class info: {e}")
594
+ return None
595
+
596
+ def _extract_variable_optimized(self, node: "tree_sitter.Node") -> list[Variable]:
597
+ """Extract var declaration variables"""
598
+ return self._extract_variables_from_declaration(node, "var")
599
+
600
+ def _extract_lexical_variable_optimized(
601
+ self, node: "tree_sitter.Node"
602
+ ) -> list[Variable]:
603
+ """Extract let/const declaration variables"""
604
+ # Determine if it's let or const
605
+ node_text = self._get_node_text_optimized(node)
606
+ kind = "let" if node_text.strip().startswith("let") else "const"
607
+ return self._extract_variables_from_declaration(node, kind)
608
+
609
+ def _extract_property_optimized(self, node: "tree_sitter.Node") -> Variable | None:
610
+ """Extract class property definition"""
611
+ try:
612
+ start_line = node.start_point[0] + 1
613
+ end_line = node.end_point[0] + 1
614
+
615
+ # Extract property name
616
+ prop_name = None
617
+ prop_value = None
618
+ is_static = False
619
+
620
+ for child in node.children:
621
+ if child.type == "property_identifier":
622
+ prop_name = self._get_node_text_optimized(child)
623
+ elif child.type in ["string", "number", "true", "false", "null"]:
624
+ prop_value = self._get_node_text_optimized(child)
625
+
626
+ # Check if static (would be in parent modifiers)
627
+ parent = node.parent
628
+ if parent:
629
+ parent_text = self._get_node_text_optimized(parent)
630
+ is_static = "static" in parent_text
631
+
632
+ if not prop_name:
633
+ return None
634
+
635
+ # Find parent class (currently not used but may be needed for future enhancements)
636
+ # class_name = self._find_parent_class_name(node)
637
+
638
+ # Extract raw text
639
+ raw_text = self._get_node_text_optimized(node)
640
+
641
+ return Variable(
642
+ name=prop_name,
643
+ start_line=start_line,
644
+ end_line=end_line,
645
+ raw_text=raw_text,
646
+ language="javascript",
647
+ variable_type=self._infer_type_from_value(prop_value),
648
+ is_static=is_static,
649
+ is_constant=False, # Class properties are not const
650
+ initializer=prop_value,
651
+ )
652
+ except Exception as e:
653
+ log_debug(f"Failed to extract property info: {e}")
654
+ return None
655
+
656
+ def _extract_variables_from_declaration(
657
+ self, node: "tree_sitter.Node", kind: str
658
+ ) -> list[Variable]:
659
+ """Extract variables from declaration node"""
660
+ variables: list[Variable] = []
661
+
662
+ try:
663
+ start_line = node.start_point[0] + 1
664
+ end_line = node.end_point[0] + 1
665
+
666
+ # Find variable declarators
667
+ for child in node.children:
668
+ if child.type == "variable_declarator":
669
+ var_info = self._parse_variable_declarator(
670
+ child, kind, start_line, end_line
671
+ )
672
+ if var_info:
673
+ variables.append(var_info)
674
+
675
+ except Exception as e:
676
+ log_debug(f"Failed to extract variables from declaration: {e}")
677
+
678
+ return variables
679
+
680
+ def _parse_variable_declarator(
681
+ self, node: "tree_sitter.Node", kind: str, start_line: int, end_line: int
682
+ ) -> Variable | None:
683
+ """Parse individual variable declarator"""
684
+ try:
685
+ var_name = None
686
+ var_value = None
687
+
688
+ # Find identifier and value in children
689
+ for child in node.children:
690
+ if child.type == "identifier":
691
+ var_name = self._get_node_text_optimized(child)
692
+ elif child.type == "=" and child.next_sibling:
693
+ # Get the value after the assignment operator
694
+ value_node = child.next_sibling
695
+ var_value = self._get_node_text_optimized(value_node)
696
+ elif child.type in [
697
+ "string",
698
+ "number",
699
+ "true",
700
+ "false",
701
+ "null",
702
+ "object",
703
+ "array",
704
+ "function_expression",
705
+ "arrow_function",
706
+ "call_expression",
707
+ "member_expression",
708
+ "template_literal",
709
+ ]:
710
+ var_value = self._get_node_text_optimized(child)
711
+
712
+ # If no value found through assignment, try to find any value node
713
+ if not var_value and len(node.children) >= 3:
714
+ # Pattern: identifier = value
715
+ for i, child in enumerate(node.children):
716
+ if child.type == "=" and i + 1 < len(node.children):
717
+ value_node = node.children[i + 1]
718
+ # Skip arrow functions - they should be handled by function extractor
719
+ if value_node.type == "arrow_function":
720
+ return None
721
+ var_value = self._get_node_text_optimized(value_node)
722
+ break
723
+
724
+ if not var_name:
725
+ return None
726
+
727
+ # Skip variables that are arrow functions
728
+ for child in node.children:
729
+ if child.type == "arrow_function":
730
+ return None
731
+
732
+ # Extract JSDoc
733
+ jsdoc = self._extract_jsdoc_for_line(start_line)
734
+
735
+ # Extract raw text with declaration keyword
736
+ raw_text = self._get_node_text_optimized(node)
737
+
738
+ # Try to get parent declaration for complete text
739
+ parent = node.parent
740
+ if parent and parent.type in [
741
+ "lexical_declaration",
742
+ "variable_declaration",
743
+ ]:
744
+ parent_text = self._get_node_text_optimized(parent)
745
+ if parent_text and len(parent_text) > len(raw_text):
746
+ # Only use parent text if it contains our node text
747
+ if raw_text in parent_text:
748
+ raw_text = parent_text
749
+
750
+ return Variable(
751
+ name=var_name,
752
+ start_line=start_line,
753
+ end_line=end_line,
754
+ raw_text=raw_text,
755
+ language="javascript",
756
+ variable_type=self._infer_type_from_value(var_value),
757
+ is_static=False,
758
+ is_constant=(kind == "const"),
759
+ docstring=jsdoc,
760
+ initializer=var_value, # Use initializer instead of value
761
+ )
762
+ except Exception as e:
763
+ log_debug(f"Failed to parse variable declarator: {e}")
764
+ return None
765
+
766
+ def _parse_function_signature_optimized(
767
+ self, node: "tree_sitter.Node"
768
+ ) -> tuple[str, list[str], bool, bool] | None:
769
+ """Parse function signature for regular functions"""
770
+ try:
771
+ name = None
772
+ parameters = []
773
+ is_async = False
774
+ is_generator = False
775
+
776
+ # Check for async/generator keywords
777
+ node_text = self._get_node_text_optimized(node)
778
+ is_async = "async" in node_text
779
+ is_generator = node.type == "generator_function_declaration"
780
+
781
+ for child in node.children:
782
+ if child.type == "identifier":
783
+ name = child.text.decode("utf8") if child.text else None
784
+ elif child.type == "formal_parameters":
785
+ parameters = self._extract_parameters(child)
786
+
787
+ return name or "", parameters, is_async, is_generator
788
+ except Exception:
789
+ return None
790
+
791
+ def _parse_method_signature_optimized(
792
+ self, node: "tree_sitter.Node"
793
+ ) -> tuple[str, list[str], bool, bool, bool, bool, bool] | None:
794
+ """Parse method signature for class methods"""
795
+ try:
796
+ name = None
797
+ parameters = []
798
+ is_async = False
799
+ is_static = False
800
+ is_getter = False
801
+ is_setter = False
802
+ is_constructor = False
803
+
804
+ # Check for method type
805
+ node_text = self._get_node_text_optimized(node)
806
+ is_async = "async" in node_text
807
+ is_static = "static" in node_text
808
+
809
+ for child in node.children:
810
+ if child.type == "property_identifier":
811
+ name = self._get_node_text_optimized(child)
812
+ is_constructor = name == "constructor"
813
+ elif child.type == "formal_parameters":
814
+ parameters = self._extract_parameters(child)
815
+
816
+ # Check for getter/setter
817
+ if "get " in node_text:
818
+ is_getter = True
819
+ elif "set " in node_text:
820
+ is_setter = True
821
+
822
+ return (
823
+ name or "",
824
+ parameters,
825
+ is_async,
826
+ is_static,
827
+ is_getter,
828
+ is_setter,
829
+ is_constructor,
830
+ )
831
+ except Exception:
832
+ return None
833
+
834
+ def _extract_parameters(self, params_node: "tree_sitter.Node") -> list[str]:
835
+ """Extract function parameters"""
836
+ parameters = []
837
+
838
+ for child in params_node.children:
839
+ if child.type == "identifier":
840
+ param_name = self._get_node_text_optimized(child)
841
+ parameters.append(param_name)
842
+ elif child.type == "rest_parameter":
843
+ # Handle rest parameters (...args)
844
+ rest_text = self._get_node_text_optimized(child)
845
+ parameters.append(rest_text)
846
+ elif child.type in ["object_pattern", "array_pattern"]:
847
+ # Handle destructuring parameters
848
+ destructure_text = self._get_node_text_optimized(child)
849
+ parameters.append(destructure_text)
850
+
851
+ return parameters
852
+
853
+ def _extract_import_info_simple(self, node: "tree_sitter.Node") -> Import | None:
854
+ """Extract import information from import_statement node"""
855
+ try:
856
+ start_line = node.start_point[0] + 1
857
+ end_line = node.end_point[0] + 1
858
+
859
+ # Get raw text using byte positions
860
+ start_byte = node.start_byte
861
+ end_byte = node.end_byte
862
+ source_bytes = self.source_code.encode("utf-8")
863
+ raw_text = source_bytes[start_byte:end_byte].decode("utf-8")
864
+
865
+ # Extract import details from AST structure
866
+ import_names = []
867
+ module_path = ""
868
+
869
+ for child in node.children:
870
+ if child.type == "import_clause":
871
+ import_names.extend(self._extract_import_names(child))
872
+ elif child.type == "string":
873
+ # Module path
874
+ module_text = source_bytes[
875
+ child.start_byte : child.end_byte
876
+ ].decode("utf-8")
877
+ module_path = module_text.strip("\"'")
878
+
879
+ # Use first import name or "unknown"
880
+ primary_name = import_names[0] if import_names else "unknown"
881
+
882
+ return Import(
883
+ name=primary_name,
884
+ start_line=start_line,
885
+ end_line=end_line,
886
+ raw_text=raw_text,
887
+ language="javascript",
888
+ module_path=module_path,
889
+ module_name=module_path,
890
+ imported_names=import_names,
891
+ )
892
+
893
+ except Exception as e:
894
+ log_debug(f"Failed to extract import info: {e}")
895
+ return None
896
+
897
+ def _extract_import_names(
898
+ self, import_clause_node: "tree_sitter.Node"
899
+ ) -> list[str]:
900
+ """Extract import names from import clause"""
901
+ names = []
902
+ source_bytes = self.source_code.encode("utf-8")
903
+
904
+ for child in import_clause_node.children:
905
+ if child.type == "import_default_specifier":
906
+ # Default import
907
+ for grandchild in child.children:
908
+ if grandchild.type == "identifier":
909
+ name_text = source_bytes[
910
+ grandchild.start_byte : grandchild.end_byte
911
+ ].decode("utf-8")
912
+ names.append(name_text)
913
+ elif child.type == "named_imports":
914
+ # Named imports
915
+ for grandchild in child.children:
916
+ if grandchild.type == "import_specifier":
917
+ for ggchild in grandchild.children:
918
+ if ggchild.type == "identifier":
919
+ name_text = source_bytes[
920
+ ggchild.start_byte : ggchild.end_byte
921
+ ].decode("utf-8")
922
+ names.append(name_text)
923
+
924
+ return names
925
+
926
+ def _extract_import_info_enhanced(
927
+ self, node: "tree_sitter.Node", source_code: str
928
+ ) -> Import | None:
929
+ """Extract enhanced import information"""
930
+ try:
931
+ import_text = self._get_node_text_optimized(node)
932
+
933
+ # Parse different import types
934
+ import_info = self._parse_import_statement(import_text)
935
+ if not import_info:
936
+ return None
937
+
938
+ import_type, names, source, is_default, is_namespace = import_info
939
+
940
+ return Import(
941
+ name=names[0] if names else "unknown",
942
+ start_line=node.start_point[0] + 1,
943
+ end_line=node.end_point[0] + 1,
944
+ raw_text=import_text,
945
+ language="javascript",
946
+ module_path=source,
947
+ module_name=source,
948
+ imported_names=names,
949
+ )
950
+ except Exception as e:
951
+ log_debug(f"Failed to extract import info: {e}")
952
+ return None
953
+
954
+ def _extract_dynamic_import(self, node: "tree_sitter.Node") -> Import | None:
955
+ """Extract dynamic import() calls"""
956
+ try:
957
+ node_text = self._get_node_text_optimized(node)
958
+
959
+ # Look for import() calls
960
+ import_match = re.search(
961
+ r"import\s*\(\s*[\"']([^\"']+)[\"']\s*\)", node_text
962
+ )
963
+ if not import_match:
964
+ return None
965
+
966
+ source = import_match.group(1)
967
+
968
+ return Import(
969
+ name="dynamic_import",
970
+ start_line=node.start_point[0] + 1,
971
+ end_line=node.end_point[0] + 1,
972
+ raw_text=node_text,
973
+ language="javascript",
974
+ module_path=source,
975
+ module_name=source,
976
+ imported_names=["dynamic_import"],
977
+ )
978
+ except Exception as e:
979
+ log_debug(f"Failed to extract dynamic import: {e}")
980
+ return None
981
+
982
+ def _extract_commonjs_requires(
983
+ self, tree: "tree_sitter.Tree", source_code: str
984
+ ) -> list[Import]:
985
+ """Extract CommonJS require() statements"""
986
+ imports = []
987
+
988
+ try:
989
+ # Use regex to find require statements
990
+ require_pattern = r"(?:const|let|var)\s+(\w+)\s*=\s*require\s*\(\s*[\"']([^\"']+)[\"']\s*\)"
991
+
992
+ for match in re.finditer(require_pattern, source_code):
993
+ var_name = match.group(1)
994
+ module_path = match.group(2)
995
+
996
+ # Find line number
997
+ line_num = source_code[: match.start()].count("\n") + 1
998
+
999
+ import_obj = Import(
1000
+ name=var_name,
1001
+ start_line=line_num,
1002
+ end_line=line_num,
1003
+ raw_text=match.group(0),
1004
+ language="javascript",
1005
+ module_path=module_path,
1006
+ module_name=module_path,
1007
+ imported_names=[var_name],
1008
+ )
1009
+ imports.append(import_obj)
1010
+
1011
+ except Exception as e:
1012
+ log_debug(f"Failed to extract CommonJS requires: {e}")
1013
+ raise
1014
+
1015
+ return imports
1016
+
1017
+ def _extract_export_info(self, node: "tree_sitter.Node") -> dict[str, Any] | None:
1018
+ """Extract export information"""
1019
+ try:
1020
+ export_text = self._get_node_text_optimized(node)
1021
+
1022
+ # Parse export type
1023
+ export_info = self._parse_export_statement(export_text)
1024
+ if not export_info:
1025
+ return None
1026
+
1027
+ export_type, names, is_default = export_info
1028
+
1029
+ return {
1030
+ "type": export_type,
1031
+ "names": names,
1032
+ "is_default": is_default,
1033
+ "start_line": node.start_point[0] + 1,
1034
+ "end_line": node.end_point[0] + 1,
1035
+ "raw_text": export_text,
1036
+ }
1037
+ except Exception as e:
1038
+ log_debug(f"Failed to extract export info: {e}")
1039
+ return None
1040
+
1041
+ def _extract_commonjs_exports(
1042
+ self, tree: "tree_sitter.Tree", source_code: str
1043
+ ) -> list[dict[str, Any]]:
1044
+ """Extract CommonJS module.exports statements"""
1045
+ exports = []
1046
+
1047
+ try:
1048
+ # Look for module.exports patterns
1049
+ patterns = [
1050
+ r"module\.exports\s*=\s*(\w+)",
1051
+ r"module\.exports\.(\w+)\s*=",
1052
+ r"exports\.(\w+)\s*=",
1053
+ ]
1054
+
1055
+ for pattern in patterns:
1056
+ for match in re.finditer(pattern, source_code):
1057
+ name = match.group(1)
1058
+ line_num = source_code[: match.start()].count("\n") + 1
1059
+
1060
+ export_obj = {
1061
+ "type": "commonjs",
1062
+ "names": [name],
1063
+ "is_default": "module.exports =" in match.group(0),
1064
+ "start_line": line_num,
1065
+ "end_line": line_num,
1066
+ "raw_text": match.group(0),
1067
+ }
1068
+ exports.append(export_obj)
1069
+
1070
+ except Exception as e:
1071
+ log_debug(f"Failed to extract CommonJS exports: {e}")
1072
+
1073
+ return exports
1074
+
1075
+ def _parse_import_statement(
1076
+ self, import_text: str
1077
+ ) -> tuple[str, list[str], str, bool, bool] | None:
1078
+ """Parse import statement to extract details"""
1079
+ try:
1080
+ # Remove semicolon and clean up
1081
+ clean_text = import_text.strip().rstrip(";")
1082
+
1083
+ # Extract source
1084
+ source_match = re.search(r"from\s+[\"']([^\"']+)[\"']", clean_text)
1085
+ if not source_match:
1086
+ return None
1087
+
1088
+ source = source_match.group(1)
1089
+
1090
+ # Determine import type and extract names
1091
+ if "import * as" in clean_text:
1092
+ # Namespace import
1093
+ namespace_match = re.search(r"import\s+\*\s+as\s+(\w+)", clean_text)
1094
+ if namespace_match:
1095
+ return "namespace", [namespace_match.group(1)], source, False, True
1096
+
1097
+ elif "import {" in clean_text:
1098
+ # Named imports
1099
+ named_match = re.search(r"import\s+\{([^}]+)\}", clean_text)
1100
+ if named_match:
1101
+ names_text = named_match.group(1)
1102
+ names = [name.strip() for name in names_text.split(",")]
1103
+ return "named", names, source, False, False
1104
+
1105
+ else:
1106
+ # Default import
1107
+ default_match = re.search(r"import\s+(\w+)", clean_text)
1108
+ if default_match:
1109
+ return "default", [default_match.group(1)], source, True, False
1110
+
1111
+ return None
1112
+ except Exception:
1113
+ return None
1114
+
1115
+ def _parse_export_statement(
1116
+ self, export_text: str
1117
+ ) -> tuple[str, list[str], bool] | None:
1118
+ """Parse export statement to extract details"""
1119
+ try:
1120
+ clean_text = export_text.strip().rstrip(";")
1121
+
1122
+ if "export default" in clean_text:
1123
+ # Default export
1124
+ default_match = re.search(r"export\s+default\s+(\w+)", clean_text)
1125
+ if default_match:
1126
+ return "default", [default_match.group(1)], True
1127
+ else:
1128
+ return "default", ["default"], True
1129
+
1130
+ elif "export {" in clean_text:
1131
+ # Named exports
1132
+ named_match = re.search(r"export\s+\{([^}]+)\}", clean_text)
1133
+ if named_match:
1134
+ names_text = named_match.group(1)
1135
+ names = [name.strip() for name in names_text.split(",")]
1136
+ return "named", names, False
1137
+
1138
+ elif (
1139
+ clean_text.startswith("export ")
1140
+ and clean_text != "invalid export statement"
1141
+ ):
1142
+ # Direct export (export function, export class, etc.)
1143
+ # But skip obviously invalid statements
1144
+ direct_match = re.search(
1145
+ r"export\s+(function|class|const|let|var)\s+(\w+)", clean_text
1146
+ )
1147
+ if direct_match:
1148
+ return "direct", [direct_match.group(2)], False
1149
+ else:
1150
+ return "direct", ["unknown"], False
1151
+
1152
+ return None
1153
+ except Exception:
1154
+ return None
1155
+
1156
+ def _find_parent_class_name(self, node: "tree_sitter.Node") -> str | None:
1157
+ """Find parent class name for methods/properties"""
1158
+ current = node.parent
1159
+ while current:
1160
+ if current.type in ["class_declaration", "class_expression"]:
1161
+ for child in current.children:
1162
+ if child.type == "identifier":
1163
+ return self._get_node_text_optimized(child)
1164
+ current = current.parent
1165
+ return None
1166
+
1167
+ def _is_react_component(self, node: "tree_sitter.Node", class_name: str) -> bool:
1168
+ """Check if class is a React component"""
1169
+ if self.framework_type != "react":
1170
+ return False
1171
+
1172
+ # Check if extends React.Component or Component
1173
+ node_text = self._get_node_text_optimized(node)
1174
+ return "extends" in node_text and (
1175
+ "Component" in node_text or "PureComponent" in node_text
1176
+ )
1177
+
1178
+ def _is_exported_class(self, class_name: str) -> bool:
1179
+ """Check if class is exported"""
1180
+ return any(class_name in export.get("names", []) for export in self.exports)
1181
+
1182
+ def _infer_type_from_value(self, value: str | None) -> str:
1183
+ """Infer JavaScript type from value"""
1184
+ if not value:
1185
+ return "unknown"
1186
+
1187
+ value = value.strip()
1188
+
1189
+ if value.startswith('"') or value.startswith("'") or value.startswith("`"):
1190
+ return "string"
1191
+ elif value in ["true", "false"]:
1192
+ return "boolean"
1193
+ elif value == "null":
1194
+ return "null"
1195
+ elif value == "undefined":
1196
+ return "undefined"
1197
+ elif value.startswith("[") and value.endswith("]"):
1198
+ return "array"
1199
+ elif value.startswith("{") and value.endswith("}"):
1200
+ return "object"
1201
+ elif value.replace(".", "").replace("-", "").isdigit():
1202
+ return "number"
1203
+ elif "function" in value or "=>" in value:
1204
+ return "function"
1205
+ else:
1206
+ return "unknown"
1207
+
1208
+ def extract_elements(
1209
+ self, tree: "tree_sitter.Tree", source_code: str
1210
+ ) -> list[CodeElement]:
1211
+ """Extract elements from source code using tree-sitter AST"""
1212
+ elements: list[CodeElement] = []
1213
+
1214
+ try:
1215
+ elements.extend(self.extract_functions(tree, source_code))
1216
+ elements.extend(self.extract_classes(tree, source_code))
1217
+ elements.extend(self.extract_variables(tree, source_code))
1218
+ elements.extend(self.extract_imports(tree, source_code))
1219
+ except Exception as e:
1220
+ log_error(f"Failed to extract elements: {e}")
1221
+
1222
+ return elements
1223
+
1224
+ def _get_variable_kind(self, var_data: dict | str) -> str:
1225
+ """Get variable declaration kind from variable data or raw text"""
1226
+ if isinstance(var_data, dict):
1227
+ raw_text = var_data.get("raw_text", "")
1228
+ else:
1229
+ raw_text = var_data
1230
+
1231
+ if not raw_text:
1232
+ return "unknown"
1233
+
1234
+ raw_text = str(raw_text).strip()
1235
+ if raw_text.startswith("const"):
1236
+ return "const"
1237
+ elif raw_text.startswith("let"):
1238
+ return "let"
1239
+ elif raw_text.startswith("var"):
1240
+ return "var"
1241
+ else:
1242
+ return "unknown"
1243
+
1244
+ def _extract_jsdoc_for_line(self, target_line: int) -> str | None:
1245
+ """Extract JSDoc comment immediately before the specified line"""
1246
+ if target_line in self._jsdoc_cache:
1247
+ return self._jsdoc_cache[target_line]
1248
+
1249
+ try:
1250
+ if not self.content_lines or target_line <= 1:
1251
+ return None
1252
+
1253
+ # Search backwards from target_line
1254
+ jsdoc_lines = []
1255
+ current_line = target_line - 1
1256
+
1257
+ # Skip empty lines
1258
+ while current_line > 0:
1259
+ line = self.content_lines[current_line - 1].strip()
1260
+ if line:
1261
+ break
1262
+ current_line -= 1
1263
+
1264
+ # Check for JSDoc end
1265
+ if current_line > 0:
1266
+ line = self.content_lines[current_line - 1].strip()
1267
+ if line.endswith("*/"):
1268
+ jsdoc_lines.append(self.content_lines[current_line - 1])
1269
+ current_line -= 1
1270
+
1271
+ # Collect JSDoc content
1272
+ while current_line > 0:
1273
+ line_content = self.content_lines[current_line - 1]
1274
+ line_stripped = line_content.strip()
1275
+ jsdoc_lines.append(line_content)
1276
+
1277
+ if line_stripped.startswith("/**"):
1278
+ jsdoc_lines.reverse()
1279
+ jsdoc_text = "\n".join(jsdoc_lines)
1280
+ cleaned = self._clean_jsdoc(jsdoc_text)
1281
+ self._jsdoc_cache[target_line] = cleaned
1282
+ return cleaned
1283
+ current_line -= 1
1284
+
1285
+ self._jsdoc_cache[target_line] = ""
1286
+ return None
1287
+
1288
+ except Exception as e:
1289
+ log_debug(f"Failed to extract JSDoc: {e}")
1290
+ return None
1291
+
1292
+ def _clean_jsdoc(self, jsdoc_text: str) -> str:
1293
+ """Clean JSDoc text by removing comment markers"""
1294
+ if not jsdoc_text:
1295
+ return ""
1296
+
1297
+ lines = jsdoc_text.split("\n")
1298
+ cleaned_lines = []
1299
+
1300
+ for line in lines:
1301
+ line = line.strip()
1302
+
1303
+ if line.startswith("/**"):
1304
+ line = line[3:].strip()
1305
+ elif line.startswith("*/"):
1306
+ line = line[2:].strip()
1307
+ elif line.startswith("*"):
1308
+ line = line[1:].strip()
1309
+
1310
+ if line:
1311
+ cleaned_lines.append(line)
1312
+
1313
+ return " ".join(cleaned_lines) if cleaned_lines else ""
1314
+
1315
+ def _calculate_complexity_optimized(self, node: "tree_sitter.Node") -> int:
1316
+ """Calculate cyclomatic complexity efficiently"""
1317
+ node_id = id(node)
1318
+ if node_id in self._complexity_cache:
1319
+ return self._complexity_cache[node_id]
1320
+
1321
+ complexity = 1
1322
+ try:
1323
+ node_text = self._get_node_text_optimized(node).lower()
1324
+ keywords = [
1325
+ "if",
1326
+ "else if",
1327
+ "while",
1328
+ "for",
1329
+ "catch",
1330
+ "case",
1331
+ "switch",
1332
+ "&&",
1333
+ "||",
1334
+ "?",
1335
+ ]
1336
+ for keyword in keywords:
1337
+ complexity += node_text.count(keyword)
1338
+ except Exception as e:
1339
+ log_debug(f"Failed to calculate complexity: {e}")
1340
+
1341
+ self._complexity_cache[node_id] = complexity
1342
+ return complexity
1343
+
1344
+
1345
+ class JavaScriptPlugin(LanguagePlugin):
1346
+ """Enhanced JavaScript language plugin with comprehensive feature support"""
1347
+
1348
+ def __init__(self) -> None:
1349
+ self._extractor = JavaScriptElementExtractor()
1350
+ self._language: tree_sitter.Language | None = None
1351
+
1352
+ # Legacy compatibility attributes for tests
1353
+ self.language = "javascript"
1354
+ self.extractor = self._extractor
1355
+ self.supported_extensions = [".js", ".mjs", ".jsx", ".es6", ".es", ".cjs"]
1356
+
1357
+ @property
1358
+ def language_name(self) -> str:
1359
+ return "javascript"
1360
+
1361
+ @property
1362
+ def file_extensions(self) -> list[str]:
1363
+ return [".js", ".mjs", ".jsx", ".es6", ".es"]
1364
+
1365
+ def get_language_name(self) -> str:
1366
+ """Return the name of the programming language this plugin supports"""
1367
+ return "javascript"
1368
+
1369
+ def get_file_extensions(self) -> list[str]:
1370
+ """Return list of file extensions this plugin supports"""
1371
+ return [".js", ".mjs", ".jsx", ".es6", ".es"]
1372
+
1373
+ def create_extractor(self) -> ElementExtractor:
1374
+ """Create and return an element extractor for this language"""
1375
+ return JavaScriptElementExtractor()
1376
+
1377
+ def get_extractor(self) -> ElementExtractor:
1378
+ return self._extractor
1379
+
1380
+ def get_tree_sitter_language(self) -> Optional["tree_sitter.Language"]:
1381
+ """Load and return JavaScript tree-sitter language"""
1382
+ if self._language is None:
1383
+ self._language = loader.load_language("javascript")
1384
+ return self._language
1385
+
1386
+ def get_supported_queries(self) -> list[str]:
1387
+ """Get list of supported query names for this language"""
1388
+ return [
1389
+ "function",
1390
+ "class",
1391
+ "variable",
1392
+ "import",
1393
+ "export",
1394
+ "async_function",
1395
+ "arrow_function",
1396
+ "method",
1397
+ "constructor",
1398
+ "react_component",
1399
+ "react_hook",
1400
+ "jsx_element",
1401
+ ]
1402
+
1403
+ def is_applicable(self, file_path: str) -> bool:
1404
+ """Check if this plugin is applicable for the given file"""
1405
+ return any(
1406
+ file_path.lower().endswith(ext.lower())
1407
+ for ext in self.get_file_extensions()
1408
+ )
1409
+
1410
+ def get_plugin_info(self) -> dict:
1411
+ """Get information about this plugin"""
1412
+ return {
1413
+ "name": "JavaScript Plugin",
1414
+ "language": self.get_language_name(),
1415
+ "extensions": self.get_file_extensions(),
1416
+ "version": "2.0.0",
1417
+ "supported_queries": self.get_supported_queries(),
1418
+ "features": [
1419
+ "ES6+ syntax support",
1420
+ "Async/await functions",
1421
+ "Arrow functions",
1422
+ "Classes and methods",
1423
+ "Module imports/exports",
1424
+ "JSX support",
1425
+ "React component detection",
1426
+ "CommonJS support",
1427
+ "JSDoc extraction",
1428
+ "Complexity analysis",
1429
+ ],
1430
+ }
1431
+
1432
+ def execute_query_strategy(
1433
+ self, query_key: str | None, language: str
1434
+ ) -> str | None:
1435
+ """Execute query strategy for JavaScript language"""
1436
+ queries = self.get_queries()
1437
+ return queries.get(query_key) if query_key else None
1438
+
1439
+ def _get_node_type_for_element(self, element: Any) -> str:
1440
+ """Get appropriate node type for element"""
1441
+ from ..models import Class, Function, Import, Variable
1442
+
1443
+ if isinstance(element, Function):
1444
+ if hasattr(element, "is_arrow") and element.is_arrow:
1445
+ return "arrow_function"
1446
+ elif hasattr(element, "is_method") and element.is_method:
1447
+ return "method_definition"
1448
+ else:
1449
+ return "function_declaration"
1450
+ elif isinstance(element, Class):
1451
+ return "class_declaration"
1452
+ elif isinstance(element, Variable):
1453
+ return "variable_declaration"
1454
+ elif isinstance(element, Import):
1455
+ return "import_statement"
1456
+ else:
1457
+ return "unknown"
1458
+
1459
+ def get_element_categories(self) -> dict[str, list[str]]:
1460
+ """
1461
+ Get element categories mapping query keys to node types
1462
+
1463
+ Returns:
1464
+ Dictionary mapping query keys to lists of node types
1465
+ """
1466
+ return {
1467
+ # Function-related queries
1468
+ "function": ["function_declaration", "function_expression"],
1469
+ "functions": ["function_declaration", "function_expression"],
1470
+ "async_function": ["function_declaration", "function_expression"],
1471
+ "async_functions": ["function_declaration", "function_expression"],
1472
+ "arrow_function": ["arrow_function"],
1473
+ "arrow_functions": ["arrow_function"],
1474
+ "method": ["method_definition"],
1475
+ "methods": ["method_definition"],
1476
+ "constructor": ["method_definition"],
1477
+ "constructors": ["method_definition"],
1478
+ # Class-related queries
1479
+ "class": ["class_declaration", "class_expression"],
1480
+ "classes": ["class_declaration", "class_expression"],
1481
+ # Variable-related queries
1482
+ "variable": ["variable_declaration", "lexical_declaration"],
1483
+ "variables": ["variable_declaration", "lexical_declaration"],
1484
+ # Import/Export-related queries
1485
+ "import": ["import_statement"],
1486
+ "imports": ["import_statement"],
1487
+ "export": ["export_statement"],
1488
+ "exports": ["export_statement"],
1489
+ # React-specific queries
1490
+ "react_component": ["class_declaration", "function_declaration"],
1491
+ "react_components": ["class_declaration", "function_declaration"],
1492
+ "react_hook": ["function_declaration"],
1493
+ "react_hooks": ["function_declaration"],
1494
+ "jsx_element": ["jsx_element", "jsx_self_closing_element"],
1495
+ "jsx_elements": ["jsx_element", "jsx_self_closing_element"],
1496
+ # Generic queries
1497
+ "all_elements": [
1498
+ "function_declaration",
1499
+ "function_expression",
1500
+ "arrow_function",
1501
+ "method_definition",
1502
+ "class_declaration",
1503
+ "class_expression",
1504
+ "variable_declaration",
1505
+ "lexical_declaration",
1506
+ "import_statement",
1507
+ "export_statement",
1508
+ "jsx_element",
1509
+ "jsx_self_closing_element",
1510
+ ],
1511
+ }
1512
+
1513
+ async def analyze_file(
1514
+ self, file_path: str, request: AnalysisRequest
1515
+ ) -> AnalysisResult:
1516
+ """Analyze a JavaScript file and return the analysis results."""
1517
+ if not TREE_SITTER_AVAILABLE:
1518
+ return AnalysisResult(
1519
+ file_path=file_path,
1520
+ language=self.language_name,
1521
+ success=False,
1522
+ error_message="Tree-sitter library not available.",
1523
+ )
1524
+
1525
+ language = self.get_tree_sitter_language()
1526
+ if not language:
1527
+ return AnalysisResult(
1528
+ file_path=file_path,
1529
+ language=self.language_name,
1530
+ success=False,
1531
+ error_message="Could not load JavaScript language for parsing.",
1532
+ )
1533
+
1534
+ try:
1535
+ from ..encoding_utils import read_file_safe
1536
+
1537
+ source_code, _ = read_file_safe(file_path)
1538
+
1539
+ parser = tree_sitter.Parser()
1540
+ parser.language = language
1541
+ tree = parser.parse(bytes(source_code, "utf8"))
1542
+
1543
+ extractor = self.create_extractor()
1544
+ extractor.current_file = file_path # Set current file for context
1545
+
1546
+ elements: list[CodeElement] = []
1547
+
1548
+ # Extract all element types
1549
+ functions = extractor.extract_functions(tree, source_code)
1550
+ classes = extractor.extract_classes(tree, source_code)
1551
+ variables = extractor.extract_variables(tree, source_code)
1552
+ imports = extractor.extract_imports(tree, source_code)
1553
+
1554
+ # Add exports if extractor supports it
1555
+ if hasattr(extractor, "extract_exports"):
1556
+ # exports = extractor.extract_exports(tree, source_code)
1557
+ # TODO: Add exports to elements when export extraction is fully implemented
1558
+ # elements.extend(exports)
1559
+ pass
1560
+ # else: exports not needed if not supported
1561
+
1562
+ elements.extend(functions)
1563
+ elements.extend(classes)
1564
+ elements.extend(variables)
1565
+ elements.extend(imports)
1566
+
1567
+ def count_nodes(node: "tree_sitter.Node") -> int:
1568
+ count = 1
1569
+ for child in node.children:
1570
+ count += count_nodes(child)
1571
+ return count
1572
+
1573
+ return AnalysisResult(
1574
+ file_path=file_path,
1575
+ language=self.language_name,
1576
+ success=True,
1577
+ elements=elements,
1578
+ line_count=len(source_code.splitlines()),
1579
+ node_count=count_nodes(tree.root_node),
1580
+ )
1581
+ except Exception as e:
1582
+ log_error(f"Error analyzing JavaScript file {file_path}: {e}")
1583
+ return AnalysisResult(
1584
+ file_path=file_path,
1585
+ language=self.language_name,
1586
+ success=False,
1587
+ error_message=str(e),
1588
+ )
1589
+
1590
+ def extract_elements(self, tree: "tree_sitter.Tree", source_code: str) -> dict:
1591
+ """Extract elements from source code using tree-sitter AST"""
1592
+ try:
1593
+ if tree is None or not hasattr(tree, "root_node") or tree.root_node is None:
1594
+ return {
1595
+ "functions": [],
1596
+ "classes": [],
1597
+ "variables": [],
1598
+ "imports": [],
1599
+ "exports": [],
1600
+ }
1601
+
1602
+ functions = self._extractor.extract_functions(tree, source_code)
1603
+ classes = self._extractor.extract_classes(tree, source_code)
1604
+ variables = self._extractor.extract_variables(tree, source_code)
1605
+ imports = self._extractor.extract_imports(tree, source_code)
1606
+
1607
+ return {
1608
+ "functions": functions,
1609
+ "classes": classes,
1610
+ "variables": variables,
1611
+ "imports": imports,
1612
+ "exports": [], # TODO: Implement exports extraction
1613
+ }
1614
+ except Exception as e:
1615
+ log_error(f"Failed to extract elements: {e}")
1616
+ return {
1617
+ "functions": [],
1618
+ "classes": [],
1619
+ "variables": [],
1620
+ "imports": [],
1621
+ "exports": [],
1622
+ }