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.
- tree_sitter_analyzer/__init__.py +132 -0
- tree_sitter_analyzer/__main__.py +11 -0
- tree_sitter_analyzer/api.py +853 -0
- tree_sitter_analyzer/cli/__init__.py +39 -0
- tree_sitter_analyzer/cli/__main__.py +12 -0
- tree_sitter_analyzer/cli/argument_validator.py +89 -0
- tree_sitter_analyzer/cli/commands/__init__.py +26 -0
- tree_sitter_analyzer/cli/commands/advanced_command.py +226 -0
- tree_sitter_analyzer/cli/commands/base_command.py +181 -0
- tree_sitter_analyzer/cli/commands/default_command.py +18 -0
- tree_sitter_analyzer/cli/commands/find_and_grep_cli.py +188 -0
- tree_sitter_analyzer/cli/commands/list_files_cli.py +133 -0
- tree_sitter_analyzer/cli/commands/partial_read_command.py +139 -0
- tree_sitter_analyzer/cli/commands/query_command.py +109 -0
- tree_sitter_analyzer/cli/commands/search_content_cli.py +161 -0
- tree_sitter_analyzer/cli/commands/structure_command.py +156 -0
- tree_sitter_analyzer/cli/commands/summary_command.py +116 -0
- tree_sitter_analyzer/cli/commands/table_command.py +414 -0
- tree_sitter_analyzer/cli/info_commands.py +124 -0
- tree_sitter_analyzer/cli_main.py +472 -0
- tree_sitter_analyzer/constants.py +85 -0
- tree_sitter_analyzer/core/__init__.py +15 -0
- tree_sitter_analyzer/core/analysis_engine.py +580 -0
- tree_sitter_analyzer/core/cache_service.py +333 -0
- tree_sitter_analyzer/core/engine.py +585 -0
- tree_sitter_analyzer/core/parser.py +293 -0
- tree_sitter_analyzer/core/query.py +605 -0
- tree_sitter_analyzer/core/query_filter.py +200 -0
- tree_sitter_analyzer/core/query_service.py +340 -0
- tree_sitter_analyzer/encoding_utils.py +530 -0
- tree_sitter_analyzer/exceptions.py +747 -0
- tree_sitter_analyzer/file_handler.py +246 -0
- tree_sitter_analyzer/formatters/__init__.py +1 -0
- tree_sitter_analyzer/formatters/base_formatter.py +201 -0
- tree_sitter_analyzer/formatters/csharp_formatter.py +367 -0
- tree_sitter_analyzer/formatters/formatter_config.py +197 -0
- tree_sitter_analyzer/formatters/formatter_factory.py +84 -0
- tree_sitter_analyzer/formatters/formatter_registry.py +377 -0
- tree_sitter_analyzer/formatters/formatter_selector.py +96 -0
- tree_sitter_analyzer/formatters/go_formatter.py +368 -0
- tree_sitter_analyzer/formatters/html_formatter.py +498 -0
- tree_sitter_analyzer/formatters/java_formatter.py +423 -0
- tree_sitter_analyzer/formatters/javascript_formatter.py +611 -0
- tree_sitter_analyzer/formatters/kotlin_formatter.py +268 -0
- tree_sitter_analyzer/formatters/language_formatter_factory.py +123 -0
- tree_sitter_analyzer/formatters/legacy_formatter_adapters.py +228 -0
- tree_sitter_analyzer/formatters/markdown_formatter.py +725 -0
- tree_sitter_analyzer/formatters/php_formatter.py +301 -0
- tree_sitter_analyzer/formatters/python_formatter.py +830 -0
- tree_sitter_analyzer/formatters/ruby_formatter.py +278 -0
- tree_sitter_analyzer/formatters/rust_formatter.py +233 -0
- tree_sitter_analyzer/formatters/sql_formatter_wrapper.py +689 -0
- tree_sitter_analyzer/formatters/sql_formatters.py +536 -0
- tree_sitter_analyzer/formatters/typescript_formatter.py +543 -0
- tree_sitter_analyzer/formatters/yaml_formatter.py +462 -0
- tree_sitter_analyzer/interfaces/__init__.py +9 -0
- tree_sitter_analyzer/interfaces/cli.py +535 -0
- tree_sitter_analyzer/interfaces/cli_adapter.py +359 -0
- tree_sitter_analyzer/interfaces/mcp_adapter.py +224 -0
- tree_sitter_analyzer/interfaces/mcp_server.py +428 -0
- tree_sitter_analyzer/language_detector.py +553 -0
- tree_sitter_analyzer/language_loader.py +271 -0
- tree_sitter_analyzer/languages/__init__.py +10 -0
- tree_sitter_analyzer/languages/csharp_plugin.py +1076 -0
- tree_sitter_analyzer/languages/css_plugin.py +449 -0
- tree_sitter_analyzer/languages/go_plugin.py +836 -0
- tree_sitter_analyzer/languages/html_plugin.py +496 -0
- tree_sitter_analyzer/languages/java_plugin.py +1299 -0
- tree_sitter_analyzer/languages/javascript_plugin.py +1622 -0
- tree_sitter_analyzer/languages/kotlin_plugin.py +656 -0
- tree_sitter_analyzer/languages/markdown_plugin.py +1928 -0
- tree_sitter_analyzer/languages/php_plugin.py +862 -0
- tree_sitter_analyzer/languages/python_plugin.py +1636 -0
- tree_sitter_analyzer/languages/ruby_plugin.py +757 -0
- tree_sitter_analyzer/languages/rust_plugin.py +673 -0
- tree_sitter_analyzer/languages/sql_plugin.py +2444 -0
- tree_sitter_analyzer/languages/typescript_plugin.py +1892 -0
- tree_sitter_analyzer/languages/yaml_plugin.py +695 -0
- tree_sitter_analyzer/legacy_table_formatter.py +860 -0
- tree_sitter_analyzer/mcp/__init__.py +34 -0
- tree_sitter_analyzer/mcp/resources/__init__.py +43 -0
- tree_sitter_analyzer/mcp/resources/code_file_resource.py +208 -0
- tree_sitter_analyzer/mcp/resources/project_stats_resource.py +586 -0
- tree_sitter_analyzer/mcp/server.py +869 -0
- tree_sitter_analyzer/mcp/tools/__init__.py +28 -0
- tree_sitter_analyzer/mcp/tools/analyze_scale_tool.py +779 -0
- tree_sitter_analyzer/mcp/tools/analyze_scale_tool_cli_compatible.py +291 -0
- tree_sitter_analyzer/mcp/tools/base_tool.py +139 -0
- tree_sitter_analyzer/mcp/tools/fd_rg_utils.py +816 -0
- tree_sitter_analyzer/mcp/tools/find_and_grep_tool.py +686 -0
- tree_sitter_analyzer/mcp/tools/list_files_tool.py +413 -0
- tree_sitter_analyzer/mcp/tools/output_format_validator.py +148 -0
- tree_sitter_analyzer/mcp/tools/query_tool.py +443 -0
- tree_sitter_analyzer/mcp/tools/read_partial_tool.py +464 -0
- tree_sitter_analyzer/mcp/tools/search_content_tool.py +836 -0
- tree_sitter_analyzer/mcp/tools/table_format_tool.py +572 -0
- tree_sitter_analyzer/mcp/tools/universal_analyze_tool.py +653 -0
- tree_sitter_analyzer/mcp/utils/__init__.py +113 -0
- tree_sitter_analyzer/mcp/utils/error_handler.py +569 -0
- tree_sitter_analyzer/mcp/utils/file_output_factory.py +217 -0
- tree_sitter_analyzer/mcp/utils/file_output_manager.py +322 -0
- tree_sitter_analyzer/mcp/utils/gitignore_detector.py +358 -0
- tree_sitter_analyzer/mcp/utils/path_resolver.py +414 -0
- tree_sitter_analyzer/mcp/utils/search_cache.py +343 -0
- tree_sitter_analyzer/models.py +840 -0
- tree_sitter_analyzer/mypy_current_errors.txt +2 -0
- tree_sitter_analyzer/output_manager.py +255 -0
- tree_sitter_analyzer/platform_compat/__init__.py +3 -0
- tree_sitter_analyzer/platform_compat/adapter.py +324 -0
- tree_sitter_analyzer/platform_compat/compare.py +224 -0
- tree_sitter_analyzer/platform_compat/detector.py +67 -0
- tree_sitter_analyzer/platform_compat/fixtures.py +228 -0
- tree_sitter_analyzer/platform_compat/profiles.py +217 -0
- tree_sitter_analyzer/platform_compat/record.py +55 -0
- tree_sitter_analyzer/platform_compat/recorder.py +155 -0
- tree_sitter_analyzer/platform_compat/report.py +92 -0
- tree_sitter_analyzer/plugins/__init__.py +280 -0
- tree_sitter_analyzer/plugins/base.py +647 -0
- tree_sitter_analyzer/plugins/manager.py +384 -0
- tree_sitter_analyzer/project_detector.py +328 -0
- tree_sitter_analyzer/queries/__init__.py +27 -0
- tree_sitter_analyzer/queries/csharp.py +216 -0
- tree_sitter_analyzer/queries/css.py +615 -0
- tree_sitter_analyzer/queries/go.py +275 -0
- tree_sitter_analyzer/queries/html.py +543 -0
- tree_sitter_analyzer/queries/java.py +402 -0
- tree_sitter_analyzer/queries/javascript.py +724 -0
- tree_sitter_analyzer/queries/kotlin.py +192 -0
- tree_sitter_analyzer/queries/markdown.py +258 -0
- tree_sitter_analyzer/queries/php.py +95 -0
- tree_sitter_analyzer/queries/python.py +859 -0
- tree_sitter_analyzer/queries/ruby.py +92 -0
- tree_sitter_analyzer/queries/rust.py +223 -0
- tree_sitter_analyzer/queries/sql.py +555 -0
- tree_sitter_analyzer/queries/typescript.py +871 -0
- tree_sitter_analyzer/queries/yaml.py +236 -0
- tree_sitter_analyzer/query_loader.py +272 -0
- tree_sitter_analyzer/security/__init__.py +22 -0
- tree_sitter_analyzer/security/boundary_manager.py +277 -0
- tree_sitter_analyzer/security/regex_checker.py +297 -0
- tree_sitter_analyzer/security/validator.py +599 -0
- tree_sitter_analyzer/table_formatter.py +782 -0
- tree_sitter_analyzer/utils/__init__.py +53 -0
- tree_sitter_analyzer/utils/logging.py +433 -0
- tree_sitter_analyzer/utils/tree_sitter_compat.py +289 -0
- tree_sitter_analyzer-1.9.17.1.dist-info/METADATA +485 -0
- tree_sitter_analyzer-1.9.17.1.dist-info/RECORD +149 -0
- tree_sitter_analyzer-1.9.17.1.dist-info/WHEEL +4 -0
- 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
|
+
}
|