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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (149) hide show
  1. tree_sitter_analyzer/__init__.py +132 -0
  2. tree_sitter_analyzer/__main__.py +11 -0
  3. tree_sitter_analyzer/api.py +853 -0
  4. tree_sitter_analyzer/cli/__init__.py +39 -0
  5. tree_sitter_analyzer/cli/__main__.py +12 -0
  6. tree_sitter_analyzer/cli/argument_validator.py +89 -0
  7. tree_sitter_analyzer/cli/commands/__init__.py +26 -0
  8. tree_sitter_analyzer/cli/commands/advanced_command.py +226 -0
  9. tree_sitter_analyzer/cli/commands/base_command.py +181 -0
  10. tree_sitter_analyzer/cli/commands/default_command.py +18 -0
  11. tree_sitter_analyzer/cli/commands/find_and_grep_cli.py +188 -0
  12. tree_sitter_analyzer/cli/commands/list_files_cli.py +133 -0
  13. tree_sitter_analyzer/cli/commands/partial_read_command.py +139 -0
  14. tree_sitter_analyzer/cli/commands/query_command.py +109 -0
  15. tree_sitter_analyzer/cli/commands/search_content_cli.py +161 -0
  16. tree_sitter_analyzer/cli/commands/structure_command.py +156 -0
  17. tree_sitter_analyzer/cli/commands/summary_command.py +116 -0
  18. tree_sitter_analyzer/cli/commands/table_command.py +414 -0
  19. tree_sitter_analyzer/cli/info_commands.py +124 -0
  20. tree_sitter_analyzer/cli_main.py +472 -0
  21. tree_sitter_analyzer/constants.py +85 -0
  22. tree_sitter_analyzer/core/__init__.py +15 -0
  23. tree_sitter_analyzer/core/analysis_engine.py +580 -0
  24. tree_sitter_analyzer/core/cache_service.py +333 -0
  25. tree_sitter_analyzer/core/engine.py +585 -0
  26. tree_sitter_analyzer/core/parser.py +293 -0
  27. tree_sitter_analyzer/core/query.py +605 -0
  28. tree_sitter_analyzer/core/query_filter.py +200 -0
  29. tree_sitter_analyzer/core/query_service.py +340 -0
  30. tree_sitter_analyzer/encoding_utils.py +530 -0
  31. tree_sitter_analyzer/exceptions.py +747 -0
  32. tree_sitter_analyzer/file_handler.py +246 -0
  33. tree_sitter_analyzer/formatters/__init__.py +1 -0
  34. tree_sitter_analyzer/formatters/base_formatter.py +201 -0
  35. tree_sitter_analyzer/formatters/csharp_formatter.py +367 -0
  36. tree_sitter_analyzer/formatters/formatter_config.py +197 -0
  37. tree_sitter_analyzer/formatters/formatter_factory.py +84 -0
  38. tree_sitter_analyzer/formatters/formatter_registry.py +377 -0
  39. tree_sitter_analyzer/formatters/formatter_selector.py +96 -0
  40. tree_sitter_analyzer/formatters/go_formatter.py +368 -0
  41. tree_sitter_analyzer/formatters/html_formatter.py +498 -0
  42. tree_sitter_analyzer/formatters/java_formatter.py +423 -0
  43. tree_sitter_analyzer/formatters/javascript_formatter.py +611 -0
  44. tree_sitter_analyzer/formatters/kotlin_formatter.py +268 -0
  45. tree_sitter_analyzer/formatters/language_formatter_factory.py +123 -0
  46. tree_sitter_analyzer/formatters/legacy_formatter_adapters.py +228 -0
  47. tree_sitter_analyzer/formatters/markdown_formatter.py +725 -0
  48. tree_sitter_analyzer/formatters/php_formatter.py +301 -0
  49. tree_sitter_analyzer/formatters/python_formatter.py +830 -0
  50. tree_sitter_analyzer/formatters/ruby_formatter.py +278 -0
  51. tree_sitter_analyzer/formatters/rust_formatter.py +233 -0
  52. tree_sitter_analyzer/formatters/sql_formatter_wrapper.py +689 -0
  53. tree_sitter_analyzer/formatters/sql_formatters.py +536 -0
  54. tree_sitter_analyzer/formatters/typescript_formatter.py +543 -0
  55. tree_sitter_analyzer/formatters/yaml_formatter.py +462 -0
  56. tree_sitter_analyzer/interfaces/__init__.py +9 -0
  57. tree_sitter_analyzer/interfaces/cli.py +535 -0
  58. tree_sitter_analyzer/interfaces/cli_adapter.py +359 -0
  59. tree_sitter_analyzer/interfaces/mcp_adapter.py +224 -0
  60. tree_sitter_analyzer/interfaces/mcp_server.py +428 -0
  61. tree_sitter_analyzer/language_detector.py +553 -0
  62. tree_sitter_analyzer/language_loader.py +271 -0
  63. tree_sitter_analyzer/languages/__init__.py +10 -0
  64. tree_sitter_analyzer/languages/csharp_plugin.py +1076 -0
  65. tree_sitter_analyzer/languages/css_plugin.py +449 -0
  66. tree_sitter_analyzer/languages/go_plugin.py +836 -0
  67. tree_sitter_analyzer/languages/html_plugin.py +496 -0
  68. tree_sitter_analyzer/languages/java_plugin.py +1299 -0
  69. tree_sitter_analyzer/languages/javascript_plugin.py +1622 -0
  70. tree_sitter_analyzer/languages/kotlin_plugin.py +656 -0
  71. tree_sitter_analyzer/languages/markdown_plugin.py +1928 -0
  72. tree_sitter_analyzer/languages/php_plugin.py +862 -0
  73. tree_sitter_analyzer/languages/python_plugin.py +1636 -0
  74. tree_sitter_analyzer/languages/ruby_plugin.py +757 -0
  75. tree_sitter_analyzer/languages/rust_plugin.py +673 -0
  76. tree_sitter_analyzer/languages/sql_plugin.py +2444 -0
  77. tree_sitter_analyzer/languages/typescript_plugin.py +1892 -0
  78. tree_sitter_analyzer/languages/yaml_plugin.py +695 -0
  79. tree_sitter_analyzer/legacy_table_formatter.py +860 -0
  80. tree_sitter_analyzer/mcp/__init__.py +34 -0
  81. tree_sitter_analyzer/mcp/resources/__init__.py +43 -0
  82. tree_sitter_analyzer/mcp/resources/code_file_resource.py +208 -0
  83. tree_sitter_analyzer/mcp/resources/project_stats_resource.py +586 -0
  84. tree_sitter_analyzer/mcp/server.py +869 -0
  85. tree_sitter_analyzer/mcp/tools/__init__.py +28 -0
  86. tree_sitter_analyzer/mcp/tools/analyze_scale_tool.py +779 -0
  87. tree_sitter_analyzer/mcp/tools/analyze_scale_tool_cli_compatible.py +291 -0
  88. tree_sitter_analyzer/mcp/tools/base_tool.py +139 -0
  89. tree_sitter_analyzer/mcp/tools/fd_rg_utils.py +816 -0
  90. tree_sitter_analyzer/mcp/tools/find_and_grep_tool.py +686 -0
  91. tree_sitter_analyzer/mcp/tools/list_files_tool.py +413 -0
  92. tree_sitter_analyzer/mcp/tools/output_format_validator.py +148 -0
  93. tree_sitter_analyzer/mcp/tools/query_tool.py +443 -0
  94. tree_sitter_analyzer/mcp/tools/read_partial_tool.py +464 -0
  95. tree_sitter_analyzer/mcp/tools/search_content_tool.py +836 -0
  96. tree_sitter_analyzer/mcp/tools/table_format_tool.py +572 -0
  97. tree_sitter_analyzer/mcp/tools/universal_analyze_tool.py +653 -0
  98. tree_sitter_analyzer/mcp/utils/__init__.py +113 -0
  99. tree_sitter_analyzer/mcp/utils/error_handler.py +569 -0
  100. tree_sitter_analyzer/mcp/utils/file_output_factory.py +217 -0
  101. tree_sitter_analyzer/mcp/utils/file_output_manager.py +322 -0
  102. tree_sitter_analyzer/mcp/utils/gitignore_detector.py +358 -0
  103. tree_sitter_analyzer/mcp/utils/path_resolver.py +414 -0
  104. tree_sitter_analyzer/mcp/utils/search_cache.py +343 -0
  105. tree_sitter_analyzer/models.py +840 -0
  106. tree_sitter_analyzer/mypy_current_errors.txt +2 -0
  107. tree_sitter_analyzer/output_manager.py +255 -0
  108. tree_sitter_analyzer/platform_compat/__init__.py +3 -0
  109. tree_sitter_analyzer/platform_compat/adapter.py +324 -0
  110. tree_sitter_analyzer/platform_compat/compare.py +224 -0
  111. tree_sitter_analyzer/platform_compat/detector.py +67 -0
  112. tree_sitter_analyzer/platform_compat/fixtures.py +228 -0
  113. tree_sitter_analyzer/platform_compat/profiles.py +217 -0
  114. tree_sitter_analyzer/platform_compat/record.py +55 -0
  115. tree_sitter_analyzer/platform_compat/recorder.py +155 -0
  116. tree_sitter_analyzer/platform_compat/report.py +92 -0
  117. tree_sitter_analyzer/plugins/__init__.py +280 -0
  118. tree_sitter_analyzer/plugins/base.py +647 -0
  119. tree_sitter_analyzer/plugins/manager.py +384 -0
  120. tree_sitter_analyzer/project_detector.py +328 -0
  121. tree_sitter_analyzer/queries/__init__.py +27 -0
  122. tree_sitter_analyzer/queries/csharp.py +216 -0
  123. tree_sitter_analyzer/queries/css.py +615 -0
  124. tree_sitter_analyzer/queries/go.py +275 -0
  125. tree_sitter_analyzer/queries/html.py +543 -0
  126. tree_sitter_analyzer/queries/java.py +402 -0
  127. tree_sitter_analyzer/queries/javascript.py +724 -0
  128. tree_sitter_analyzer/queries/kotlin.py +192 -0
  129. tree_sitter_analyzer/queries/markdown.py +258 -0
  130. tree_sitter_analyzer/queries/php.py +95 -0
  131. tree_sitter_analyzer/queries/python.py +859 -0
  132. tree_sitter_analyzer/queries/ruby.py +92 -0
  133. tree_sitter_analyzer/queries/rust.py +223 -0
  134. tree_sitter_analyzer/queries/sql.py +555 -0
  135. tree_sitter_analyzer/queries/typescript.py +871 -0
  136. tree_sitter_analyzer/queries/yaml.py +236 -0
  137. tree_sitter_analyzer/query_loader.py +272 -0
  138. tree_sitter_analyzer/security/__init__.py +22 -0
  139. tree_sitter_analyzer/security/boundary_manager.py +277 -0
  140. tree_sitter_analyzer/security/regex_checker.py +297 -0
  141. tree_sitter_analyzer/security/validator.py +599 -0
  142. tree_sitter_analyzer/table_formatter.py +782 -0
  143. tree_sitter_analyzer/utils/__init__.py +53 -0
  144. tree_sitter_analyzer/utils/logging.py +433 -0
  145. tree_sitter_analyzer/utils/tree_sitter_compat.py +289 -0
  146. tree_sitter_analyzer-1.9.17.1.dist-info/METADATA +485 -0
  147. tree_sitter_analyzer-1.9.17.1.dist-info/RECORD +149 -0
  148. tree_sitter_analyzer-1.9.17.1.dist-info/WHEEL +4 -0
  149. tree_sitter_analyzer-1.9.17.1.dist-info/entry_points.txt +25 -0
@@ -0,0 +1,585 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Updated Engine module for tree_sitter_analyzer.core.
4
+
5
+ This module provides the AnalysisEngine class which is the core component
6
+ of the new architecture responsible for file analysis workflow.
7
+ """
8
+
9
+ import logging
10
+ from pathlib import Path
11
+ from typing import Any
12
+
13
+ from tree_sitter import Tree
14
+
15
+ from ..language_detector import LanguageDetector
16
+ from ..models import AnalysisResult, CodeElement
17
+ from ..plugins.manager import PluginManager
18
+ from .parser import Parser, ParseResult
19
+ from .query import QueryExecutor
20
+
21
+ # Configure logging
22
+ logger = logging.getLogger(__name__)
23
+
24
+
25
+ class AnalysisEngine:
26
+ """
27
+ Core analysis engine for the new architecture.
28
+
29
+ This class orchestrates the analysis workflow by coordinating
30
+ parsing, query execution, and element extraction through plugins.
31
+ """
32
+
33
+ def __init__(self) -> None:
34
+ """Initialize the AnalysisEngine with core components."""
35
+ try:
36
+ self.parser = Parser()
37
+ self.query_executor = QueryExecutor()
38
+ self.language_detector = LanguageDetector()
39
+
40
+ # Initialize plugin system
41
+ self.plugin_manager = PluginManager()
42
+ self._initialize_plugins()
43
+
44
+ logger.info("AnalysisEngine initialized successfully")
45
+
46
+ except Exception as e:
47
+ logger.error(f"Failed to initialize AnalysisEngine: {e}")
48
+ raise
49
+
50
+ def analyze_file(
51
+ self,
52
+ file_path: str | Path,
53
+ language: str | None = None,
54
+ queries: list[str] | None = None,
55
+ ) -> AnalysisResult:
56
+ """
57
+ Analyze a source code file.
58
+
59
+ Args:
60
+ file_path: Path to the file to analyze
61
+ language: Optional language override
62
+ queries: List of query names to execute (all available if not specified)
63
+
64
+ Returns:
65
+ AnalysisResult containing analysis results
66
+ """
67
+ file_path_obj = Path(file_path)
68
+ file_path_str = str(file_path_obj)
69
+
70
+ try:
71
+ # Check if file exists
72
+ if not file_path_obj.exists():
73
+ raise FileNotFoundError(f"File not found: {file_path_str}")
74
+
75
+ # Determine language
76
+ detected_language = self._determine_language(file_path_obj, language)
77
+
78
+ # Parse the file
79
+ parse_result = self.parser.parse_file(file_path_str, detected_language)
80
+
81
+ if not parse_result.success:
82
+ logger.warning(
83
+ f"Parsing failed for {file_path_str}: {parse_result.error_message}"
84
+ )
85
+ return self._create_empty_result(
86
+ file_path_str, detected_language, error=parse_result.error_message
87
+ )
88
+
89
+ # Perform analysis
90
+ return self._perform_analysis(parse_result, queries=queries)
91
+
92
+ except FileNotFoundError:
93
+ raise
94
+ except PermissionError:
95
+ raise
96
+ except Exception as e:
97
+ logger.error(f"Error analyzing file {file_path_str}: {e}")
98
+ return self._create_empty_result(
99
+ file_path_str, language or "unknown", error=str(e)
100
+ )
101
+
102
+ def analyze_code(
103
+ self,
104
+ source_code: str,
105
+ language: str | None = None,
106
+ filename: str | None = None,
107
+ ) -> AnalysisResult:
108
+ """
109
+ Analyze source code string.
110
+
111
+ Args:
112
+ source_code: Source code to analyze
113
+ language: Programming language (required if no filename)
114
+ filename: Optional filename for language detection
115
+
116
+ Returns:
117
+ AnalysisResult containing analysis results
118
+ """
119
+ try:
120
+ # Determine language
121
+ if language is None and filename is not None:
122
+ language = self.language_detector.detect_from_extension(filename)
123
+ elif language is None:
124
+ language = "unknown"
125
+
126
+ # Parse the code
127
+ parse_result = self.parser.parse_code(source_code, language, filename)
128
+
129
+ if not parse_result.success:
130
+ logger.warning(
131
+ f"Parsing failed for {language} code: {parse_result.error_message}"
132
+ )
133
+ return self._create_empty_result(
134
+ filename, language, error=parse_result.error_message
135
+ )
136
+
137
+ # Perform analysis
138
+ return self._perform_analysis(parse_result)
139
+
140
+ except Exception as e:
141
+ logger.error(f"Error analyzing {language} code: {e}")
142
+ return self._create_empty_result(
143
+ filename, language or "unknown", error=str(e)
144
+ )
145
+
146
+ def _determine_language(
147
+ self, file_path: Path, language_override: str | None
148
+ ) -> str:
149
+ """
150
+ Determine the programming language for a file.
151
+
152
+ Args:
153
+ file_path: Path to the file
154
+ language_override: Optional language override
155
+
156
+ Returns:
157
+ Detected or overridden language
158
+ """
159
+ if language_override:
160
+ return language_override
161
+
162
+ try:
163
+ return self.language_detector.detect_from_extension(str(file_path))
164
+ except Exception as e:
165
+ logger.warning(f"Language detection failed for {file_path}: {e}")
166
+ return "unknown"
167
+
168
+ def _perform_analysis(
169
+ self, parse_result: ParseResult, queries: list[str] | None = None
170
+ ) -> AnalysisResult:
171
+ """
172
+ Perform comprehensive analysis on parsed code.
173
+
174
+ Args:
175
+ parse_result: Result from parsing operation
176
+ queries: Optional list of query names to execute (default: all queries)
177
+
178
+ Returns:
179
+ AnalysisResult containing analysis results
180
+ """
181
+ try:
182
+ # Get plugin for the language
183
+ plugin = self._get_language_plugin(parse_result.language)
184
+
185
+ # Execute queries
186
+ query_results = self._execute_queries(
187
+ parse_result.tree,
188
+ plugin,
189
+ queries=queries,
190
+ source_code=parse_result.source_code or "",
191
+ language_name=parse_result.language,
192
+ )
193
+
194
+ # Extract elements
195
+ elements = self._extract_elements(parse_result, plugin)
196
+
197
+ # Count nodes
198
+ node_count = self._count_nodes(parse_result.tree)
199
+
200
+ # Create analysis result using existing AnalysisResult structure
201
+ return AnalysisResult(
202
+ file_path=parse_result.file_path or "",
203
+ language=parse_result.language,
204
+ elements=elements,
205
+ node_count=node_count,
206
+ query_results=query_results,
207
+ source_code=parse_result.source_code,
208
+ line_count=(
209
+ len(parse_result.source_code.splitlines())
210
+ if parse_result.source_code
211
+ else 0
212
+ ),
213
+ success=True,
214
+ error_message=None,
215
+ analysis_time=0.0,
216
+ package=None,
217
+ )
218
+
219
+ except Exception as e:
220
+ logger.error(f"Error performing analysis: {e}")
221
+ return self._create_empty_result(
222
+ parse_result.file_path, parse_result.language, error=str(e)
223
+ )
224
+
225
+ def _get_language_plugin(self, language: str) -> Any | None:
226
+ """
227
+ Get the appropriate language plugin.
228
+
229
+ Args:
230
+ language: Programming language name
231
+
232
+ Returns:
233
+ Language plugin instance or None
234
+ """
235
+ if self.plugin_manager is not None:
236
+ try:
237
+ return self.plugin_manager.get_plugin(language)
238
+ except Exception as e:
239
+ logger.error(f"Error getting plugin for {language}: {e}")
240
+
241
+ return None
242
+
243
+ def _execute_queries(
244
+ self,
245
+ tree: Tree | None,
246
+ plugin: Any | None,
247
+ queries: list[str] | None = None,
248
+ source_code: str = "",
249
+ language_name: str = "unknown",
250
+ ) -> dict[str, Any]:
251
+ """
252
+ Execute queries on the parsed tree.
253
+
254
+ Args:
255
+ tree: Parsed Tree-sitter tree
256
+ plugin: Language plugin
257
+ queries: Optional list of query names to execute (default: uses plugin queries or ["class", "method", "field"])
258
+ source_code: Source code for context
259
+ language_name: Name of the programming language
260
+
261
+ Returns:
262
+ Dictionary of query results
263
+ """
264
+ if tree is None:
265
+ return {}
266
+
267
+ try:
268
+ # Use provided queries or determine from plugin/fallback
269
+ if queries is not None:
270
+ query_names = queries
271
+ elif plugin and hasattr(plugin, "get_supported_queries"):
272
+ # If plugin is available, use its supported queries
273
+ query_names = plugin.get_supported_queries()
274
+ else:
275
+ # Fallback to common queries that exist in the system
276
+ query_names = ["class", "method", "field"] # Use actual query names
277
+
278
+ # Get language object for query execution
279
+ language_obj = self._get_language_object(tree)
280
+ if language_obj is None:
281
+ return {}
282
+
283
+ # Execute queries
284
+ results = {}
285
+ for query_name in query_names:
286
+ try:
287
+ result = self.query_executor.execute_query_with_language_name(
288
+ tree,
289
+ language_obj,
290
+ query_name,
291
+ source_code,
292
+ language_name,
293
+ )
294
+ results[query_name] = result
295
+ except Exception as e:
296
+ logger.error(f"Error executing query {query_name}: {e}")
297
+ results[query_name] = {"error": str(e), "captures": []}
298
+
299
+ return results
300
+
301
+ except Exception as e:
302
+ logger.error(f"Error executing queries: {e}")
303
+ return {}
304
+
305
+ def _extract_elements(
306
+ self, parse_result: ParseResult, plugin: Any | None
307
+ ) -> list[CodeElement]:
308
+ """
309
+ Extract code elements using the language plugin.
310
+
311
+ Args:
312
+ parse_result: Result from parsing operation
313
+ plugin: Language plugin
314
+
315
+ Returns:
316
+ List of extracted code elements
317
+ """
318
+ try:
319
+ if plugin and hasattr(plugin, "create_extractor"):
320
+ extractor = plugin.create_extractor()
321
+ if extractor:
322
+ # Set current file path for package detection
323
+ if hasattr(extractor, "current_file"):
324
+ extractor.current_file = parse_result.file_path or ""
325
+
326
+ # Extract different types of elements
327
+ elements = []
328
+
329
+ # Extract packages first (needed for proper class package resolution)
330
+ if hasattr(extractor, "extract_packages"):
331
+ packages = extractor.extract_packages(
332
+ parse_result.tree, parse_result.source_code
333
+ )
334
+ elements.extend(packages)
335
+
336
+ # Extract functions/methods
337
+ if hasattr(extractor, "extract_functions"):
338
+ functions = extractor.extract_functions(
339
+ parse_result.tree, parse_result.source_code
340
+ )
341
+ elements.extend(functions)
342
+
343
+ # Extract classes
344
+ if hasattr(extractor, "extract_classes"):
345
+ classes = extractor.extract_classes(
346
+ parse_result.tree, parse_result.source_code
347
+ )
348
+ elements.extend(classes)
349
+
350
+ # Extract variables/fields
351
+ if hasattr(extractor, "extract_variables"):
352
+ variables = extractor.extract_variables(
353
+ parse_result.tree, parse_result.source_code
354
+ )
355
+ elements.extend(variables)
356
+
357
+ # Extract imports
358
+ if hasattr(extractor, "extract_imports"):
359
+ imports = extractor.extract_imports(
360
+ parse_result.tree, parse_result.source_code
361
+ )
362
+ elements.extend(imports)
363
+
364
+ return elements
365
+
366
+ # Fallback: create basic elements from query results
367
+ return self._create_basic_elements(parse_result)
368
+
369
+ except Exception as e:
370
+ logger.error(f"Error extracting elements: {e}")
371
+ return []
372
+
373
+ def _create_basic_elements(self, parse_result: ParseResult) -> list[CodeElement]:
374
+ """
375
+ Create basic code elements as fallback.
376
+
377
+ Args:
378
+ parse_result: Result from parsing operation
379
+
380
+ Returns:
381
+ List of basic code elements
382
+ """
383
+ # This is a basic fallback implementation
384
+ # Real implementation would extract meaningful elements
385
+ elements: list[Any] = []
386
+
387
+ try:
388
+ if parse_result.tree and parse_result.tree.root_node:
389
+ # Create a basic element representing the file
390
+
391
+ # Note: CodeElement is abstract, so we'd need a concrete implementation
392
+ # For now, return empty list until concrete element types are available
393
+ pass
394
+
395
+ except Exception as e:
396
+ logger.error(f"Error creating basic elements: {e}")
397
+
398
+ return elements
399
+
400
+ def _count_nodes(self, tree: Tree | None) -> int:
401
+ """
402
+ Count nodes in the AST tree.
403
+
404
+ Args:
405
+ tree: Tree-sitter tree
406
+
407
+ Returns:
408
+ Number of nodes in the tree
409
+ """
410
+ if tree is None or tree.root_node is None:
411
+ return 0
412
+
413
+ try:
414
+
415
+ def count_recursive(node: Any) -> int:
416
+ """Recursively count nodes."""
417
+ count = 1 # Count current node
418
+ if hasattr(node, "children"):
419
+ for child in node.children:
420
+ count += count_recursive(child)
421
+ return count
422
+
423
+ return count_recursive(tree.root_node)
424
+
425
+ except Exception as e:
426
+ logger.error(f"Error counting nodes: {e}")
427
+ return 0
428
+
429
+ def _get_language_object(self, tree: Tree) -> Any | None:
430
+ """
431
+ Get the language object associated with a tree.
432
+
433
+ Args:
434
+ tree: Tree-sitter tree
435
+
436
+ Returns:
437
+ Language object or None
438
+ """
439
+ try:
440
+ # Tree-sitter trees have a language property
441
+ if hasattr(tree, "language"):
442
+ return tree.language
443
+ return None
444
+ except Exception as e:
445
+ logger.error(f"Error getting language object: {e}")
446
+ return None
447
+
448
+ def _initialize_plugins(self) -> None:
449
+ """Initialize the plugin system."""
450
+ try:
451
+ if self.plugin_manager:
452
+ plugins = self.plugin_manager.load_plugins()
453
+ logger.info(f"Loaded {len(plugins)} plugins successfully")
454
+ else:
455
+ logger.warning("Plugin manager not available")
456
+ except Exception as e:
457
+ logger.error(f"Plugin initialization failed: {e}")
458
+ # Don't raise here to allow engine to work without plugins
459
+ logger.warning("Continuing without plugins")
460
+
461
+ def _create_empty_result(
462
+ self, file_path: str | None, language: str, error: str | None = None
463
+ ) -> AnalysisResult:
464
+ """
465
+ Create an empty analysis result.
466
+
467
+ Args:
468
+ file_path: File path
469
+ language: Programming language
470
+ error: Optional error message
471
+
472
+ Returns:
473
+ Empty AnalysisResult
474
+ """
475
+ return AnalysisResult(
476
+ file_path=file_path or "",
477
+ language=language,
478
+ line_count=0,
479
+ success=error is None,
480
+ elements=[],
481
+ node_count=0,
482
+ query_results={},
483
+ source_code="",
484
+ error_message=error,
485
+ analysis_time=0.0,
486
+ package=None,
487
+ )
488
+
489
+ def get_supported_languages(self) -> list[str]:
490
+ """
491
+ Get list of supported programming languages.
492
+
493
+ Returns:
494
+ List of supported language names
495
+ """
496
+ try:
497
+ if self.plugin_manager:
498
+ return list(self.plugin_manager.get_supported_languages())
499
+ return []
500
+ except Exception as e:
501
+ logger.error(f"Error getting supported languages: {e}")
502
+ return []
503
+
504
+ def get_available_queries(self, language: str) -> list[str]:
505
+ """
506
+ Get available queries for a specific language.
507
+
508
+ Args:
509
+ language: Programming language name
510
+
511
+ Returns:
512
+ List of available query names
513
+ """
514
+ try:
515
+ plugin = self._get_language_plugin(language)
516
+ if plugin and hasattr(plugin, "get_supported_queries"):
517
+ queries = plugin.get_supported_queries()
518
+ return list(queries) if queries else []
519
+ else:
520
+ # Return default queries
521
+ return ["class", "method", "field"]
522
+ except Exception as e:
523
+ logger.error(f"Error getting available queries for {language}: {e}")
524
+ return []
525
+
526
+ # Add compatibility methods for API layer
527
+ @property
528
+ def language_registry(self) -> "AnalysisEngine":
529
+ """Provide compatibility with API layer expecting language_registry"""
530
+ return self
531
+
532
+ def detect_language_from_file(self, file_path: Path) -> str | None:
533
+ """
534
+ Detect language from file path (compatibility method)
535
+
536
+ Args:
537
+ file_path: Path to the file
538
+
539
+ Returns:
540
+ Detected language name or None
541
+ """
542
+ try:
543
+ return self.language_detector.detect_from_extension(str(file_path))
544
+ except Exception as e:
545
+ logger.error(f"Error detecting language for {file_path}: {e}")
546
+ return None
547
+
548
+ def get_extensions_for_language(self, language: str) -> list[str]:
549
+ """
550
+ Get file extensions for a language (compatibility method)
551
+
552
+ Args:
553
+ language: Programming language name
554
+
555
+ Returns:
556
+ List of file extensions
557
+ """
558
+ try:
559
+ # Get extensions from language detector
560
+ extensions = []
561
+ for ext, lang in self.language_detector.EXTENSION_MAPPING.items():
562
+ if lang == language:
563
+ extensions.append(ext)
564
+ return extensions
565
+ except Exception as e:
566
+ logger.error(f"Error getting extensions for {language}: {e}")
567
+ return []
568
+
569
+ def get_registry_info(self) -> dict[str, Any]:
570
+ """
571
+ Get registry information (compatibility method)
572
+
573
+ Returns:
574
+ Registry information dictionary
575
+ """
576
+ try:
577
+ return {
578
+ "supported_languages": self.get_supported_languages(),
579
+ "total_languages": len(self.get_supported_languages()),
580
+ "language_detector_available": self.language_detector is not None,
581
+ "plugin_manager_available": self.plugin_manager is not None,
582
+ }
583
+ except Exception as e:
584
+ logger.error(f"Error getting registry info: {e}")
585
+ return {}