tree-sitter-analyzer 0.8.3__py3-none-any.whl → 0.9.2__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.

Files changed (62) hide show
  1. tree_sitter_analyzer/__init__.py +132 -132
  2. tree_sitter_analyzer/__main__.py +11 -11
  3. tree_sitter_analyzer/api.py +533 -533
  4. tree_sitter_analyzer/cli/__init__.py +39 -39
  5. tree_sitter_analyzer/cli/__main__.py +12 -12
  6. tree_sitter_analyzer/cli/commands/__init__.py +26 -26
  7. tree_sitter_analyzer/cli/commands/advanced_command.py +88 -88
  8. tree_sitter_analyzer/cli/commands/base_command.py +182 -180
  9. tree_sitter_analyzer/cli/commands/structure_command.py +138 -138
  10. tree_sitter_analyzer/cli/commands/summary_command.py +101 -101
  11. tree_sitter_analyzer/core/__init__.py +15 -15
  12. tree_sitter_analyzer/core/analysis_engine.py +74 -78
  13. tree_sitter_analyzer/core/cache_service.py +320 -320
  14. tree_sitter_analyzer/core/engine.py +566 -566
  15. tree_sitter_analyzer/core/parser.py +293 -293
  16. tree_sitter_analyzer/encoding_utils.py +459 -459
  17. tree_sitter_analyzer/file_handler.py +210 -210
  18. tree_sitter_analyzer/formatters/__init__.py +1 -1
  19. tree_sitter_analyzer/formatters/base_formatter.py +167 -167
  20. tree_sitter_analyzer/formatters/formatter_factory.py +78 -78
  21. tree_sitter_analyzer/formatters/java_formatter.py +18 -18
  22. tree_sitter_analyzer/formatters/python_formatter.py +19 -19
  23. tree_sitter_analyzer/interfaces/__init__.py +9 -9
  24. tree_sitter_analyzer/interfaces/cli.py +528 -528
  25. tree_sitter_analyzer/interfaces/cli_adapter.py +344 -343
  26. tree_sitter_analyzer/interfaces/mcp_adapter.py +206 -206
  27. tree_sitter_analyzer/language_detector.py +53 -53
  28. tree_sitter_analyzer/languages/__init__.py +10 -10
  29. tree_sitter_analyzer/languages/java_plugin.py +1 -1
  30. tree_sitter_analyzer/languages/javascript_plugin.py +446 -446
  31. tree_sitter_analyzer/languages/python_plugin.py +755 -755
  32. tree_sitter_analyzer/mcp/__init__.py +34 -31
  33. tree_sitter_analyzer/mcp/resources/__init__.py +44 -44
  34. tree_sitter_analyzer/mcp/resources/code_file_resource.py +209 -209
  35. tree_sitter_analyzer/mcp/server.py +623 -436
  36. tree_sitter_analyzer/mcp/tools/__init__.py +30 -30
  37. tree_sitter_analyzer/mcp/tools/analyze_scale_tool.py +10 -6
  38. tree_sitter_analyzer/mcp/tools/analyze_scale_tool_cli_compatible.py +247 -242
  39. tree_sitter_analyzer/mcp/tools/base_tool.py +54 -54
  40. tree_sitter_analyzer/mcp/tools/read_partial_tool.py +310 -308
  41. tree_sitter_analyzer/mcp/tools/table_format_tool.py +386 -379
  42. tree_sitter_analyzer/mcp/tools/universal_analyze_tool.py +563 -559
  43. tree_sitter_analyzer/mcp/utils/__init__.py +107 -107
  44. tree_sitter_analyzer/models.py +10 -10
  45. tree_sitter_analyzer/output_manager.py +253 -253
  46. tree_sitter_analyzer/plugins/__init__.py +280 -280
  47. tree_sitter_analyzer/plugins/base.py +529 -529
  48. tree_sitter_analyzer/plugins/manager.py +379 -379
  49. tree_sitter_analyzer/queries/__init__.py +26 -26
  50. tree_sitter_analyzer/queries/java.py +391 -391
  51. tree_sitter_analyzer/queries/javascript.py +148 -148
  52. tree_sitter_analyzer/queries/python.py +285 -285
  53. tree_sitter_analyzer/queries/typescript.py +229 -229
  54. tree_sitter_analyzer/query_loader.py +257 -257
  55. tree_sitter_analyzer/security/boundary_manager.py +237 -279
  56. tree_sitter_analyzer/security/validator.py +60 -58
  57. tree_sitter_analyzer/utils.py +294 -277
  58. {tree_sitter_analyzer-0.8.3.dist-info → tree_sitter_analyzer-0.9.2.dist-info}/METADATA +28 -19
  59. tree_sitter_analyzer-0.9.2.dist-info/RECORD +77 -0
  60. {tree_sitter_analyzer-0.8.3.dist-info → tree_sitter_analyzer-0.9.2.dist-info}/entry_points.txt +1 -0
  61. tree_sitter_analyzer-0.8.3.dist-info/RECORD +0 -77
  62. {tree_sitter_analyzer-0.8.3.dist-info → tree_sitter_analyzer-0.9.2.dist-info}/WHEEL +0 -0
@@ -1,446 +1,446 @@
1
- #!/usr/bin/env python3
2
- """
3
- JavaScript Language Plugin
4
-
5
- Provides JavaScript-specific parsing and element extraction functionality.
6
- """
7
-
8
- from typing import TYPE_CHECKING, Optional
9
-
10
- if TYPE_CHECKING:
11
- import tree_sitter
12
-
13
- try:
14
- import tree_sitter
15
-
16
- TREE_SITTER_AVAILABLE = True
17
- except ImportError:
18
- TREE_SITTER_AVAILABLE = False
19
-
20
- from ..core.analysis_engine import AnalysisRequest
21
- from ..language_loader import loader
22
- from ..models import AnalysisResult, Class, CodeElement, Function, Import, Variable
23
- from ..plugins.base import ElementExtractor, LanguagePlugin
24
- from ..utils import log_error, log_warning
25
-
26
-
27
- class JavaScriptElementExtractor(ElementExtractor):
28
- """JavaScript-specific element extractor"""
29
-
30
- def extract_functions(
31
- self, tree: "tree_sitter.Tree", source_code: str
32
- ) -> list[Function]:
33
- """Extract JavaScript function definitions"""
34
- functions = []
35
-
36
- # Multiple function patterns in JavaScript
37
- queries = [
38
- # Regular function declarations
39
- """
40
- (function_declaration
41
- name: (identifier) @func.name
42
- parameters: (formal_parameters) @func.params
43
- body: (statement_block) @func.body) @func.declaration
44
- """,
45
- # Method definitions (class methods)
46
- """
47
- (method_definition
48
- name: (property_identifier) @func.name
49
- parameters: (formal_parameters) @func.params
50
- body: (statement_block) @func.body) @func.method
51
- """,
52
- # Arrow functions
53
- """
54
- (variable_declarator
55
- name: (identifier) @func.name
56
- value: (arrow_function
57
- parameters: (formal_parameters) @func.params
58
- body: (_) @func.body)) @func.arrow
59
- """,
60
- # Function expressions
61
- """
62
- (variable_declarator
63
- name: (identifier) @func.name
64
- value: (function_expression
65
- parameters: (formal_parameters) @func.params
66
- body: (statement_block) @func.body)) @func.expression
67
- """,
68
- ]
69
-
70
- try:
71
- language = tree.language if hasattr(tree, "language") else None
72
- if language:
73
- for query_string in queries:
74
- query = language.query(query_string)
75
- captures = query.captures(tree.root_node)
76
-
77
- if isinstance(captures, dict):
78
- # Handle different function types
79
- for capture_key in [
80
- "func.declaration",
81
- "func.method",
82
- "func.arrow",
83
- "func.expression",
84
- ]:
85
- func_nodes = captures.get(capture_key, [])
86
- for node in func_nodes:
87
- function = self._extract_function_info(
88
- node, source_code
89
- )
90
- if function:
91
- functions.append(function)
92
-
93
- except Exception as e:
94
- log_warning(f"Could not extract JavaScript functions: {e}")
95
-
96
- return functions
97
-
98
- def extract_classes(
99
- self, tree: "tree_sitter.Tree", source_code: str
100
- ) -> list[Class]:
101
- """Extract JavaScript class definitions"""
102
- classes = []
103
-
104
- query_string = """
105
- (class_declaration
106
- name: (identifier) @class.name
107
- body: (class_body) @class.body) @class.declaration
108
- """
109
-
110
- try:
111
- language = tree.language if hasattr(tree, "language") else None
112
- if language:
113
- query = language.query(query_string)
114
- captures = query.captures(tree.root_node)
115
-
116
- if isinstance(captures, dict):
117
- class_nodes = captures.get("class.declaration", [])
118
- for node in class_nodes:
119
- cls = self._extract_class_info(node, source_code)
120
- if cls:
121
- classes.append(cls)
122
-
123
- except Exception as e:
124
- log_warning(f"Could not extract JavaScript classes: {e}")
125
-
126
- return classes
127
-
128
- def extract_variables(
129
- self, tree: "tree_sitter.Tree", source_code: str
130
- ) -> list[Variable]:
131
- """Extract JavaScript variable definitions"""
132
- variables = []
133
-
134
- # Variable declarations (let, const, var)
135
- queries = [
136
- # var declarations
137
- """
138
- (variable_declaration
139
- (variable_declarator
140
- name: (identifier) @var.name
141
- value: (_)? @var.value)) @var.declaration
142
- """,
143
- # let/const declarations
144
- """
145
- (lexical_declaration
146
- (variable_declarator
147
- name: (identifier) @var.name
148
- value: (_)? @var.value)) @var.lexical
149
- """,
150
- ]
151
-
152
- try:
153
- language = tree.language if hasattr(tree, "language") else None
154
- if language:
155
- for query_string in queries:
156
- query = language.query(query_string)
157
- captures = query.captures(tree.root_node)
158
-
159
- if isinstance(captures, dict):
160
- # Handle both var and lexical declarations
161
- for capture_key in ["var.declaration", "var.lexical"]:
162
- var_nodes = captures.get(capture_key, [])
163
- for node in var_nodes:
164
- variable = self._extract_variable_info(
165
- node, source_code
166
- )
167
- if variable:
168
- variables.append(variable)
169
-
170
- except Exception as e:
171
- log_warning(f"Could not extract JavaScript variables: {e}")
172
-
173
- return variables
174
-
175
- def extract_imports(
176
- self, tree: "tree_sitter.Tree", source_code: str
177
- ) -> list[Import]:
178
- """Extract JavaScript import statements"""
179
- imports = []
180
-
181
- # ES6 import statements
182
- query_string = """
183
- (import_statement
184
- source: (string) @import.source) @import.declaration
185
- """
186
-
187
- try:
188
- language = tree.language if hasattr(tree, "language") else None
189
- if language:
190
- query = language.query(query_string)
191
- captures = query.captures(tree.root_node)
192
-
193
- if isinstance(captures, dict):
194
- import_nodes = captures.get("import.declaration", [])
195
- for node in import_nodes:
196
- imp = self._extract_import_info(node, source_code)
197
- if imp:
198
- imports.append(imp)
199
-
200
- except Exception as e:
201
- log_warning(f"Could not extract JavaScript imports: {e}")
202
-
203
- return imports
204
-
205
- def _extract_function_info(
206
- self, node: "tree_sitter.Node", source_code: str
207
- ) -> Function | None:
208
- """Extract function information from AST node"""
209
- try:
210
- name_node = None
211
- params_node = None
212
-
213
- # Navigate the node structure to find name and parameters
214
- for child in node.children:
215
- if child.type == "identifier":
216
- name_node = child
217
- elif child.type == "property_identifier": # For method definitions
218
- name_node = child
219
- elif child.type == "formal_parameters":
220
- params_node = child
221
- elif child.type == "variable_declarator":
222
- # For arrow functions and function expressions
223
- for subchild in child.children:
224
- if subchild.type == "identifier":
225
- name_node = subchild
226
- elif subchild.type in ["arrow_function", "function_expression"]:
227
- for funcchild in subchild.children:
228
- if funcchild.type == "formal_parameters":
229
- params_node = funcchild
230
-
231
- if not name_node:
232
- return None
233
-
234
- name = source_code[name_node.start_byte : name_node.end_byte]
235
-
236
- # Extract parameters
237
- parameters = []
238
- if params_node:
239
- for child in params_node.children:
240
- if child.type == "identifier":
241
- param_name = source_code[child.start_byte : child.end_byte]
242
- parameters.append(param_name)
243
-
244
- return Function(
245
- name=name,
246
- parameters=parameters,
247
- start_line=node.start_point[0] + 1,
248
- end_line=node.end_point[0] + 1,
249
- raw_text=source_code[node.start_byte : node.end_byte],
250
- language="javascript",
251
- )
252
-
253
- except Exception as e:
254
- log_warning(f"Could not extract function info: {e}")
255
- return None
256
-
257
- def _extract_class_info(
258
- self, node: "tree_sitter.Node", source_code: str
259
- ) -> Class | None:
260
- """Extract class information from AST node"""
261
- try:
262
- name_node = None
263
-
264
- for child in node.children:
265
- if child.type == "identifier":
266
- name_node = child
267
- break
268
-
269
- if not name_node:
270
- return None
271
-
272
- name = source_code[name_node.start_byte : name_node.end_byte]
273
-
274
- return Class(
275
- name=name,
276
- class_type="class",
277
- start_line=node.start_point[0] + 1,
278
- end_line=node.end_point[0] + 1,
279
- raw_text=source_code[node.start_byte : node.end_byte],
280
- language="javascript",
281
- )
282
-
283
- except Exception as e:
284
- log_warning(f"Could not extract class info: {e}")
285
- return None
286
-
287
- def _extract_variable_info(
288
- self, node: "tree_sitter.Node", source_code: str
289
- ) -> Variable | None:
290
- """Extract variable information from AST node"""
291
- try:
292
- name_node = None
293
-
294
- # Find the identifier in variable declarator
295
- for child in node.children:
296
- if child.type == "variable_declarator":
297
- for subchild in child.children:
298
- if subchild.type == "identifier":
299
- name_node = subchild
300
- break
301
- break
302
-
303
- if not name_node:
304
- return None
305
-
306
- name = source_code[name_node.start_byte : name_node.end_byte]
307
-
308
- return Variable(
309
- name=name,
310
- start_line=node.start_point[0] + 1,
311
- end_line=node.end_point[0] + 1,
312
- raw_text=source_code[node.start_byte : node.end_byte],
313
- language="javascript",
314
- )
315
-
316
- except Exception as e:
317
- log_warning(f"Could not extract variable info: {e}")
318
- return None
319
-
320
- def _extract_import_info(
321
- self, node: "tree_sitter.Node", source_code: str
322
- ) -> Import | None:
323
- """Extract import information from AST node"""
324
- try:
325
- source_node = None
326
-
327
- for child in node.children:
328
- if child.type == "string":
329
- source_node = child
330
- break
331
-
332
- if not source_node:
333
- return None
334
-
335
- module_path = source_code[source_node.start_byte : source_node.end_byte]
336
- # Remove quotes from string
337
- module_path = module_path.strip("\"'")
338
-
339
- return Import(
340
- name="import",
341
- module_path=module_path,
342
- start_line=node.start_point[0] + 1,
343
- end_line=node.end_point[0] + 1,
344
- raw_text=source_code[node.start_byte : node.end_byte],
345
- language="javascript",
346
- )
347
-
348
- except Exception as e:
349
- log_warning(f"Could not extract import info: {e}")
350
- return None
351
-
352
-
353
- class JavaScriptPlugin(LanguagePlugin):
354
- """JavaScript language plugin"""
355
-
356
- def __init__(self) -> None:
357
- self._extractor = JavaScriptElementExtractor()
358
- self._language: tree_sitter.Language | None = None
359
-
360
- @property
361
- def language_name(self) -> str:
362
- return "javascript"
363
-
364
- @property
365
- def file_extensions(self) -> list[str]:
366
- return [".js", ".mjs", ".jsx"]
367
-
368
- def get_language_name(self) -> str:
369
- """Return the name of the programming language this plugin supports"""
370
- return "javascript"
371
-
372
- def get_file_extensions(self) -> list[str]:
373
- """Return list of file extensions this plugin supports"""
374
- return [".js", ".mjs", ".jsx"]
375
-
376
- def create_extractor(self) -> ElementExtractor:
377
- """Create and return an element extractor for this language"""
378
- return JavaScriptElementExtractor()
379
-
380
- def get_extractor(self) -> ElementExtractor:
381
- return self._extractor
382
-
383
- def get_tree_sitter_language(self) -> Optional["tree_sitter.Language"]:
384
- """Load and return JavaScript tree-sitter language"""
385
- if self._language is None:
386
- self._language = loader.load_language("javascript")
387
- return self._language
388
-
389
- async def analyze_file(
390
- self, file_path: str, request: AnalysisRequest
391
- ) -> AnalysisResult:
392
- """Analyze a JavaScript file and return the analysis results."""
393
- if not TREE_SITTER_AVAILABLE:
394
- return AnalysisResult(
395
- file_path=file_path,
396
- language=self.language_name,
397
- success=False,
398
- error_message="Tree-sitter library not available.",
399
- )
400
-
401
- language = self.get_tree_sitter_language()
402
- if not language:
403
- return AnalysisResult(
404
- file_path=file_path,
405
- language=self.language_name,
406
- success=False,
407
- error_message="Could not load JavaScript language for parsing.",
408
- )
409
-
410
- try:
411
- with open(file_path, encoding="utf-8") as f:
412
- source_code = f.read()
413
-
414
- parser = tree_sitter.Parser()
415
- parser.language = language
416
- tree = parser.parse(bytes(source_code, "utf8"))
417
-
418
- extractor = self.create_extractor()
419
- elements: list[CodeElement] = []
420
- elements.extend(extractor.extract_functions(tree, source_code))
421
- elements.extend(extractor.extract_classes(tree, source_code))
422
- elements.extend(extractor.extract_variables(tree, source_code))
423
- elements.extend(extractor.extract_imports(tree, source_code))
424
-
425
- def count_nodes(node: "tree_sitter.Node") -> int:
426
- count = 1
427
- for child in node.children:
428
- count += count_nodes(child)
429
- return count
430
-
431
- return AnalysisResult(
432
- file_path=file_path,
433
- language=self.language_name,
434
- success=True,
435
- elements=elements,
436
- line_count=len(source_code.splitlines()),
437
- node_count=count_nodes(tree.root_node),
438
- )
439
- except Exception as e:
440
- log_error(f"Error analyzing JavaScript file {file_path}: {e}")
441
- return AnalysisResult(
442
- file_path=file_path,
443
- language=self.language_name,
444
- success=False,
445
- error_message=str(e),
446
- )
1
+ #!/usr/bin/env python3
2
+ """
3
+ JavaScript Language Plugin
4
+
5
+ Provides JavaScript-specific parsing and element extraction functionality.
6
+ """
7
+
8
+ from typing import TYPE_CHECKING, Optional
9
+
10
+ if TYPE_CHECKING:
11
+ import tree_sitter
12
+
13
+ try:
14
+ import tree_sitter
15
+
16
+ TREE_SITTER_AVAILABLE = True
17
+ except ImportError:
18
+ TREE_SITTER_AVAILABLE = False
19
+
20
+ from ..core.analysis_engine import AnalysisRequest
21
+ from ..language_loader import loader
22
+ from ..models import AnalysisResult, Class, CodeElement, Function, Import, Variable
23
+ from ..plugins.base import ElementExtractor, LanguagePlugin
24
+ from ..utils import log_error, log_warning
25
+
26
+
27
+ class JavaScriptElementExtractor(ElementExtractor):
28
+ """JavaScript-specific element extractor"""
29
+
30
+ def extract_functions(
31
+ self, tree: "tree_sitter.Tree", source_code: str
32
+ ) -> list[Function]:
33
+ """Extract JavaScript function definitions"""
34
+ functions = []
35
+
36
+ # Multiple function patterns in JavaScript
37
+ queries = [
38
+ # Regular function declarations
39
+ """
40
+ (function_declaration
41
+ name: (identifier) @func.name
42
+ parameters: (formal_parameters) @func.params
43
+ body: (statement_block) @func.body) @func.declaration
44
+ """,
45
+ # Method definitions (class methods)
46
+ """
47
+ (method_definition
48
+ name: (property_identifier) @func.name
49
+ parameters: (formal_parameters) @func.params
50
+ body: (statement_block) @func.body) @func.method
51
+ """,
52
+ # Arrow functions
53
+ """
54
+ (variable_declarator
55
+ name: (identifier) @func.name
56
+ value: (arrow_function
57
+ parameters: (formal_parameters) @func.params
58
+ body: (_) @func.body)) @func.arrow
59
+ """,
60
+ # Function expressions
61
+ """
62
+ (variable_declarator
63
+ name: (identifier) @func.name
64
+ value: (function_expression
65
+ parameters: (formal_parameters) @func.params
66
+ body: (statement_block) @func.body)) @func.expression
67
+ """,
68
+ ]
69
+
70
+ try:
71
+ language = tree.language if hasattr(tree, "language") else None
72
+ if language:
73
+ for query_string in queries:
74
+ query = language.query(query_string)
75
+ captures = query.captures(tree.root_node)
76
+
77
+ if isinstance(captures, dict):
78
+ # Handle different function types
79
+ for capture_key in [
80
+ "func.declaration",
81
+ "func.method",
82
+ "func.arrow",
83
+ "func.expression",
84
+ ]:
85
+ func_nodes = captures.get(capture_key, [])
86
+ for node in func_nodes:
87
+ function = self._extract_function_info(
88
+ node, source_code
89
+ )
90
+ if function:
91
+ functions.append(function)
92
+
93
+ except Exception as e:
94
+ log_warning(f"Could not extract JavaScript functions: {e}")
95
+
96
+ return functions
97
+
98
+ def extract_classes(
99
+ self, tree: "tree_sitter.Tree", source_code: str
100
+ ) -> list[Class]:
101
+ """Extract JavaScript class definitions"""
102
+ classes = []
103
+
104
+ query_string = """
105
+ (class_declaration
106
+ name: (identifier) @class.name
107
+ body: (class_body) @class.body) @class.declaration
108
+ """
109
+
110
+ try:
111
+ language = tree.language if hasattr(tree, "language") else None
112
+ if language:
113
+ query = language.query(query_string)
114
+ captures = query.captures(tree.root_node)
115
+
116
+ if isinstance(captures, dict):
117
+ class_nodes = captures.get("class.declaration", [])
118
+ for node in class_nodes:
119
+ cls = self._extract_class_info(node, source_code)
120
+ if cls:
121
+ classes.append(cls)
122
+
123
+ except Exception as e:
124
+ log_warning(f"Could not extract JavaScript classes: {e}")
125
+
126
+ return classes
127
+
128
+ def extract_variables(
129
+ self, tree: "tree_sitter.Tree", source_code: str
130
+ ) -> list[Variable]:
131
+ """Extract JavaScript variable definitions"""
132
+ variables = []
133
+
134
+ # Variable declarations (let, const, var)
135
+ queries = [
136
+ # var declarations
137
+ """
138
+ (variable_declaration
139
+ (variable_declarator
140
+ name: (identifier) @var.name
141
+ value: (_)? @var.value)) @var.declaration
142
+ """,
143
+ # let/const declarations
144
+ """
145
+ (lexical_declaration
146
+ (variable_declarator
147
+ name: (identifier) @var.name
148
+ value: (_)? @var.value)) @var.lexical
149
+ """,
150
+ ]
151
+
152
+ try:
153
+ language = tree.language if hasattr(tree, "language") else None
154
+ if language:
155
+ for query_string in queries:
156
+ query = language.query(query_string)
157
+ captures = query.captures(tree.root_node)
158
+
159
+ if isinstance(captures, dict):
160
+ # Handle both var and lexical declarations
161
+ for capture_key in ["var.declaration", "var.lexical"]:
162
+ var_nodes = captures.get(capture_key, [])
163
+ for node in var_nodes:
164
+ variable = self._extract_variable_info(
165
+ node, source_code
166
+ )
167
+ if variable:
168
+ variables.append(variable)
169
+
170
+ except Exception as e:
171
+ log_warning(f"Could not extract JavaScript variables: {e}")
172
+
173
+ return variables
174
+
175
+ def extract_imports(
176
+ self, tree: "tree_sitter.Tree", source_code: str
177
+ ) -> list[Import]:
178
+ """Extract JavaScript import statements"""
179
+ imports = []
180
+
181
+ # ES6 import statements
182
+ query_string = """
183
+ (import_statement
184
+ source: (string) @import.source) @import.declaration
185
+ """
186
+
187
+ try:
188
+ language = tree.language if hasattr(tree, "language") else None
189
+ if language:
190
+ query = language.query(query_string)
191
+ captures = query.captures(tree.root_node)
192
+
193
+ if isinstance(captures, dict):
194
+ import_nodes = captures.get("import.declaration", [])
195
+ for node in import_nodes:
196
+ imp = self._extract_import_info(node, source_code)
197
+ if imp:
198
+ imports.append(imp)
199
+
200
+ except Exception as e:
201
+ log_warning(f"Could not extract JavaScript imports: {e}")
202
+
203
+ return imports
204
+
205
+ def _extract_function_info(
206
+ self, node: "tree_sitter.Node", source_code: str
207
+ ) -> Function | None:
208
+ """Extract function information from AST node"""
209
+ try:
210
+ name_node = None
211
+ params_node = None
212
+
213
+ # Navigate the node structure to find name and parameters
214
+ for child in node.children:
215
+ if child.type == "identifier":
216
+ name_node = child
217
+ elif child.type == "property_identifier": # For method definitions
218
+ name_node = child
219
+ elif child.type == "formal_parameters":
220
+ params_node = child
221
+ elif child.type == "variable_declarator":
222
+ # For arrow functions and function expressions
223
+ for subchild in child.children:
224
+ if subchild.type == "identifier":
225
+ name_node = subchild
226
+ elif subchild.type in ["arrow_function", "function_expression"]:
227
+ for funcchild in subchild.children:
228
+ if funcchild.type == "formal_parameters":
229
+ params_node = funcchild
230
+
231
+ if not name_node:
232
+ return None
233
+
234
+ name = source_code[name_node.start_byte : name_node.end_byte]
235
+
236
+ # Extract parameters
237
+ parameters = []
238
+ if params_node:
239
+ for child in params_node.children:
240
+ if child.type == "identifier":
241
+ param_name = source_code[child.start_byte : child.end_byte]
242
+ parameters.append(param_name)
243
+
244
+ return Function(
245
+ name=name,
246
+ parameters=parameters,
247
+ start_line=node.start_point[0] + 1,
248
+ end_line=node.end_point[0] + 1,
249
+ raw_text=source_code[node.start_byte : node.end_byte],
250
+ language="javascript",
251
+ )
252
+
253
+ except Exception as e:
254
+ log_warning(f"Could not extract function info: {e}")
255
+ return None
256
+
257
+ def _extract_class_info(
258
+ self, node: "tree_sitter.Node", source_code: str
259
+ ) -> Class | None:
260
+ """Extract class information from AST node"""
261
+ try:
262
+ name_node = None
263
+
264
+ for child in node.children:
265
+ if child.type == "identifier":
266
+ name_node = child
267
+ break
268
+
269
+ if not name_node:
270
+ return None
271
+
272
+ name = source_code[name_node.start_byte : name_node.end_byte]
273
+
274
+ return Class(
275
+ name=name,
276
+ class_type="class",
277
+ start_line=node.start_point[0] + 1,
278
+ end_line=node.end_point[0] + 1,
279
+ raw_text=source_code[node.start_byte : node.end_byte],
280
+ language="javascript",
281
+ )
282
+
283
+ except Exception as e:
284
+ log_warning(f"Could not extract class info: {e}")
285
+ return None
286
+
287
+ def _extract_variable_info(
288
+ self, node: "tree_sitter.Node", source_code: str
289
+ ) -> Variable | None:
290
+ """Extract variable information from AST node"""
291
+ try:
292
+ name_node = None
293
+
294
+ # Find the identifier in variable declarator
295
+ for child in node.children:
296
+ if child.type == "variable_declarator":
297
+ for subchild in child.children:
298
+ if subchild.type == "identifier":
299
+ name_node = subchild
300
+ break
301
+ break
302
+
303
+ if not name_node:
304
+ return None
305
+
306
+ name = source_code[name_node.start_byte : name_node.end_byte]
307
+
308
+ return Variable(
309
+ name=name,
310
+ start_line=node.start_point[0] + 1,
311
+ end_line=node.end_point[0] + 1,
312
+ raw_text=source_code[node.start_byte : node.end_byte],
313
+ language="javascript",
314
+ )
315
+
316
+ except Exception as e:
317
+ log_warning(f"Could not extract variable info: {e}")
318
+ return None
319
+
320
+ def _extract_import_info(
321
+ self, node: "tree_sitter.Node", source_code: str
322
+ ) -> Import | None:
323
+ """Extract import information from AST node"""
324
+ try:
325
+ source_node = None
326
+
327
+ for child in node.children:
328
+ if child.type == "string":
329
+ source_node = child
330
+ break
331
+
332
+ if not source_node:
333
+ return None
334
+
335
+ module_path = source_code[source_node.start_byte : source_node.end_byte]
336
+ # Remove quotes from string
337
+ module_path = module_path.strip("\"'")
338
+
339
+ return Import(
340
+ name="import",
341
+ module_path=module_path,
342
+ start_line=node.start_point[0] + 1,
343
+ end_line=node.end_point[0] + 1,
344
+ raw_text=source_code[node.start_byte : node.end_byte],
345
+ language="javascript",
346
+ )
347
+
348
+ except Exception as e:
349
+ log_warning(f"Could not extract import info: {e}")
350
+ return None
351
+
352
+
353
+ class JavaScriptPlugin(LanguagePlugin):
354
+ """JavaScript language plugin"""
355
+
356
+ def __init__(self) -> None:
357
+ self._extractor = JavaScriptElementExtractor()
358
+ self._language: tree_sitter.Language | None = None
359
+
360
+ @property
361
+ def language_name(self) -> str:
362
+ return "javascript"
363
+
364
+ @property
365
+ def file_extensions(self) -> list[str]:
366
+ return [".js", ".mjs", ".jsx"]
367
+
368
+ def get_language_name(self) -> str:
369
+ """Return the name of the programming language this plugin supports"""
370
+ return "javascript"
371
+
372
+ def get_file_extensions(self) -> list[str]:
373
+ """Return list of file extensions this plugin supports"""
374
+ return [".js", ".mjs", ".jsx"]
375
+
376
+ def create_extractor(self) -> ElementExtractor:
377
+ """Create and return an element extractor for this language"""
378
+ return JavaScriptElementExtractor()
379
+
380
+ def get_extractor(self) -> ElementExtractor:
381
+ return self._extractor
382
+
383
+ def get_tree_sitter_language(self) -> Optional["tree_sitter.Language"]:
384
+ """Load and return JavaScript tree-sitter language"""
385
+ if self._language is None:
386
+ self._language = loader.load_language("javascript")
387
+ return self._language
388
+
389
+ async def analyze_file(
390
+ self, file_path: str, request: AnalysisRequest
391
+ ) -> AnalysisResult:
392
+ """Analyze a JavaScript file and return the analysis results."""
393
+ if not TREE_SITTER_AVAILABLE:
394
+ return AnalysisResult(
395
+ file_path=file_path,
396
+ language=self.language_name,
397
+ success=False,
398
+ error_message="Tree-sitter library not available.",
399
+ )
400
+
401
+ language = self.get_tree_sitter_language()
402
+ if not language:
403
+ return AnalysisResult(
404
+ file_path=file_path,
405
+ language=self.language_name,
406
+ success=False,
407
+ error_message="Could not load JavaScript language for parsing.",
408
+ )
409
+
410
+ try:
411
+ with open(file_path, encoding="utf-8") as f:
412
+ source_code = f.read()
413
+
414
+ parser = tree_sitter.Parser()
415
+ parser.language = language
416
+ tree = parser.parse(bytes(source_code, "utf8"))
417
+
418
+ extractor = self.create_extractor()
419
+ elements: list[CodeElement] = []
420
+ elements.extend(extractor.extract_functions(tree, source_code))
421
+ elements.extend(extractor.extract_classes(tree, source_code))
422
+ elements.extend(extractor.extract_variables(tree, source_code))
423
+ elements.extend(extractor.extract_imports(tree, source_code))
424
+
425
+ def count_nodes(node: "tree_sitter.Node") -> int:
426
+ count = 1
427
+ for child in node.children:
428
+ count += count_nodes(child)
429
+ return count
430
+
431
+ return AnalysisResult(
432
+ file_path=file_path,
433
+ language=self.language_name,
434
+ success=True,
435
+ elements=elements,
436
+ line_count=len(source_code.splitlines()),
437
+ node_count=count_nodes(tree.root_node),
438
+ )
439
+ except Exception as e:
440
+ log_error(f"Error analyzing JavaScript file {file_path}: {e}")
441
+ return AnalysisResult(
442
+ file_path=file_path,
443
+ language=self.language_name,
444
+ success=False,
445
+ error_message=str(e),
446
+ )