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