tree-sitter-analyzer 0.2.0__py3-none-any.whl → 0.3.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.

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