tree-sitter-analyzer 0.2.0__py3-none-any.whl → 0.4.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 +134 -121
  2. tree_sitter_analyzer/__main__.py +11 -12
  3. tree_sitter_analyzer/api.py +533 -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 +235 -233
  15. tree_sitter_analyzer/cli/info_commands.py +120 -121
  16. tree_sitter_analyzer/cli_main.py +278 -276
  17. tree_sitter_analyzer/core/__init__.py +15 -20
  18. tree_sitter_analyzer/core/analysis_engine.py +555 -574
  19. tree_sitter_analyzer/core/cache_service.py +320 -330
  20. tree_sitter_analyzer/core/engine.py +559 -560
  21. tree_sitter_analyzer/core/parser.py +293 -288
  22. tree_sitter_analyzer/core/query.py +502 -502
  23. tree_sitter_analyzer/encoding_utils.py +456 -460
  24. tree_sitter_analyzer/exceptions.py +337 -340
  25. tree_sitter_analyzer/file_handler.py +210 -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 +291 -270
  30. tree_sitter_analyzer/formatters/python_formatter.py +259 -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 +343 -319
  34. tree_sitter_analyzer/interfaces/mcp_adapter.py +206 -170
  35. tree_sitter_analyzer/interfaces/mcp_server.py +405 -416
  36. tree_sitter_analyzer/java_analyzer.py +187 -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 +1174 -1113
  41. tree_sitter_analyzer/{plugins → languages}/javascript_plugin.py +446 -439
  42. tree_sitter_analyzer/languages/python_plugin.py +747 -712
  43. tree_sitter_analyzer/mcp/__init__.py +31 -32
  44. tree_sitter_analyzer/mcp/resources/__init__.py +44 -47
  45. tree_sitter_analyzer/mcp/resources/code_file_resource.py +209 -213
  46. tree_sitter_analyzer/mcp/resources/project_stats_resource.py +555 -550
  47. tree_sitter_analyzer/mcp/server.py +333 -345
  48. tree_sitter_analyzer/mcp/tools/__init__.py +30 -31
  49. tree_sitter_analyzer/mcp/tools/analyze_scale_tool.py +654 -557
  50. tree_sitter_analyzer/mcp/tools/analyze_scale_tool_cli_compatible.py +247 -245
  51. tree_sitter_analyzer/mcp/tools/base_tool.py +54 -55
  52. tree_sitter_analyzer/mcp/tools/read_partial_tool.py +300 -302
  53. tree_sitter_analyzer/mcp/tools/table_format_tool.py +362 -359
  54. tree_sitter_analyzer/mcp/tools/universal_analyze_tool.py +543 -476
  55. tree_sitter_analyzer/mcp/utils/__init__.py +107 -106
  56. tree_sitter_analyzer/mcp/utils/error_handler.py +549 -549
  57. tree_sitter_analyzer/models.py +470 -481
  58. tree_sitter_analyzer/output_manager.py +255 -264
  59. tree_sitter_analyzer/plugins/__init__.py +280 -334
  60. tree_sitter_analyzer/plugins/base.py +496 -446
  61. tree_sitter_analyzer/plugins/manager.py +379 -355
  62. tree_sitter_analyzer/queries/__init__.py +26 -27
  63. tree_sitter_analyzer/queries/java.py +391 -394
  64. tree_sitter_analyzer/queries/javascript.py +148 -149
  65. tree_sitter_analyzer/queries/python.py +285 -286
  66. tree_sitter_analyzer/queries/typescript.py +229 -230
  67. tree_sitter_analyzer/query_loader.py +257 -260
  68. tree_sitter_analyzer/table_formatter.py +471 -448
  69. tree_sitter_analyzer/utils.py +277 -277
  70. {tree_sitter_analyzer-0.2.0.dist-info → tree_sitter_analyzer-0.4.0.dist-info}/METADATA +23 -8
  71. tree_sitter_analyzer-0.4.0.dist-info/RECORD +73 -0
  72. {tree_sitter_analyzer-0.2.0.dist-info → tree_sitter_analyzer-0.4.0.dist-info}/entry_points.txt +2 -1
  73. tree_sitter_analyzer/plugins/java_plugin.py +0 -625
  74. tree_sitter_analyzer/plugins/plugin_loader.py +0 -83
  75. tree_sitter_analyzer/plugins/python_plugin.py +0 -598
  76. tree_sitter_analyzer/plugins/registry.py +0 -366
  77. tree_sitter_analyzer-0.2.0.dist-info/RECORD +0 -77
  78. {tree_sitter_analyzer-0.2.0.dist-info → tree_sitter_analyzer-0.4.0.dist-info}/WHEEL +0 -0
@@ -1,560 +1,559 @@
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 Optional, Union, Dict, Any, List
12
- from tree_sitter import Tree
13
-
14
- from .parser import Parser, ParseResult
15
- from .query import QueryExecutor
16
- from ..models import AnalysisResult, CodeElement
17
- from ..language_detector import LanguageDetector
18
- from ..plugins.manager import PluginManager
19
-
20
- # Configure logging
21
- logger = logging.getLogger(__name__)
22
-
23
-
24
- class AnalysisEngine:
25
- """
26
- Core analysis engine for the new architecture.
27
-
28
- This class orchestrates the analysis workflow by coordinating
29
- parsing, query execution, and element extraction through plugins.
30
- """
31
-
32
- def __init__(self) -> None:
33
- """Initialize the AnalysisEngine with core components."""
34
- try:
35
- self.parser = Parser()
36
- self.query_executor = QueryExecutor()
37
- self.language_detector = LanguageDetector()
38
-
39
- # Initialize plugin system
40
- self.plugin_manager = PluginManager()
41
- self._initialize_plugins()
42
-
43
- logger.info("AnalysisEngine initialized successfully")
44
-
45
- except Exception as e:
46
- logger.error(f"Failed to initialize AnalysisEngine: {e}")
47
- raise
48
-
49
- def analyze_file(
50
- self,
51
- file_path: Union[str, Path],
52
- language: Optional[str] = None
53
- ) -> AnalysisResult:
54
- """
55
- Analyze a source code file.
56
-
57
- Args:
58
- file_path: Path to the file to analyze
59
- language: Optional language override
60
-
61
- Returns:
62
- AnalysisResult containing analysis results
63
- """
64
- file_path_obj = Path(file_path)
65
- file_path_str = str(file_path_obj)
66
-
67
- try:
68
- # Check if file exists
69
- if not file_path_obj.exists():
70
- raise FileNotFoundError(f"File not found: {file_path_str}")
71
-
72
- # Determine language
73
- detected_language = self._determine_language(file_path_obj, language)
74
-
75
- # Parse the file
76
- parse_result = self.parser.parse_file(file_path_str, detected_language)
77
-
78
- if not parse_result.success:
79
- logger.warning(f"Parsing failed for {file_path_str}: {parse_result.error_message}")
80
- return self._create_empty_result(
81
- file_path_str,
82
- detected_language,
83
- error=parse_result.error_message
84
- )
85
-
86
- # Perform analysis
87
- return self._perform_analysis(parse_result)
88
-
89
- except FileNotFoundError:
90
- raise
91
- except PermissionError:
92
- raise
93
- except Exception as e:
94
- logger.error(f"Error analyzing file {file_path_str}: {e}")
95
- return self._create_empty_result(
96
- file_path_str,
97
- language or 'unknown',
98
- error=str(e)
99
- )
100
-
101
- def analyze_code(
102
- self,
103
- source_code: str,
104
- language: Optional[str] = None,
105
- filename: Optional[str] = None
106
- ) -> AnalysisResult:
107
- """
108
- Analyze source code string.
109
-
110
- Args:
111
- source_code: Source code to analyze
112
- language: Programming language (required if no filename)
113
- filename: Optional filename for language detection
114
-
115
- Returns:
116
- AnalysisResult containing analysis results
117
- """
118
- try:
119
- # Determine language
120
- if language is None and filename is not None:
121
- language = self.language_detector.detect_from_extension(filename)
122
- elif language is None:
123
- language = 'unknown'
124
-
125
- # Parse the code
126
- parse_result = self.parser.parse_code(source_code, language, filename)
127
-
128
- if not parse_result.success:
129
- logger.warning(f"Parsing failed for {language} code: {parse_result.error_message}")
130
- return self._create_empty_result(
131
- filename,
132
- language,
133
- error=parse_result.error_message
134
- )
135
-
136
- # Perform analysis
137
- return self._perform_analysis(parse_result)
138
-
139
- except Exception as e:
140
- logger.error(f"Error analyzing {language} code: {e}")
141
- return self._create_empty_result(
142
- filename,
143
- language or 'unknown',
144
- error=str(e)
145
- )
146
-
147
- def _determine_language(
148
- self,
149
- file_path: Path,
150
- language_override: Optional[str]
151
- ) -> str:
152
- """
153
- Determine the programming language for a file.
154
-
155
- Args:
156
- file_path: Path to the file
157
- language_override: Optional language override
158
-
159
- Returns:
160
- Detected or overridden language
161
- """
162
- if language_override:
163
- return language_override
164
-
165
- try:
166
- return self.language_detector.detect_from_extension(str(file_path))
167
- except Exception as e:
168
- logger.warning(f"Language detection failed for {file_path}: {e}")
169
- return 'unknown'
170
-
171
- def _perform_analysis(self, parse_result: ParseResult) -> AnalysisResult:
172
- """
173
- Perform comprehensive analysis on parsed code.
174
-
175
- Args:
176
- parse_result: Result from parsing operation
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(parse_result.tree, plugin)
187
-
188
- # Extract elements
189
- elements = self._extract_elements(parse_result, plugin)
190
-
191
- # Count nodes
192
- node_count = self._count_nodes(parse_result.tree)
193
-
194
- # Create analysis result using existing AnalysisResult structure
195
- return AnalysisResult(
196
- file_path=parse_result.file_path or "",
197
- language=parse_result.language,
198
- elements=elements,
199
- node_count=node_count,
200
- query_results=query_results,
201
- source_code=parse_result.source_code,
202
- line_count=len(parse_result.source_code.splitlines()) if parse_result.source_code else 0,
203
- success=True,
204
- error_message=None,
205
- analysis_time=0.0,
206
- # Initialize empty collections for now
207
- classes=[],
208
- methods=[],
209
- fields=[],
210
- imports=[],
211
- annotations=[],
212
- package=None
213
- )
214
-
215
- except Exception as e:
216
- logger.error(f"Error performing analysis: {e}")
217
- return self._create_empty_result(
218
- parse_result.file_path,
219
- parse_result.language,
220
- error=str(e)
221
- )
222
-
223
- def _get_language_plugin(self, language: str) -> Optional[Any]:
224
- """
225
- Get the appropriate language plugin.
226
-
227
- Args:
228
- language: Programming language name
229
-
230
- Returns:
231
- Language plugin instance or None
232
- """
233
- if self.plugin_manager is not None:
234
- try:
235
- return self.plugin_manager.get_plugin(language)
236
- except Exception as e:
237
- logger.error(f"Error getting plugin for {language}: {e}")
238
-
239
- return None
240
-
241
- def _execute_queries(
242
- self,
243
- tree: Optional[Tree],
244
- plugin: Optional[Any]
245
- ) -> Dict[str, Any]:
246
- """
247
- Execute queries on the parsed tree.
248
-
249
- Args:
250
- tree: Parsed Tree-sitter tree
251
- plugin: Language plugin
252
-
253
- Returns:
254
- Dictionary of query results
255
- """
256
- if tree is None:
257
- return {}
258
-
259
- try:
260
- # If plugin is available, use its supported queries
261
- if plugin and hasattr(plugin, 'get_supported_queries'):
262
- query_names = plugin.get_supported_queries()
263
- else:
264
- # Fallback to common queries that exist in the system
265
- query_names = ['class', 'method', 'field'] # Use actual query names
266
-
267
- # Get language object for query execution
268
- language_obj = self._get_language_object(tree)
269
- if language_obj is None:
270
- return {}
271
-
272
- # Execute queries
273
- results = {}
274
- for query_name in query_names:
275
- try:
276
- result = self.query_executor.execute_query(
277
- tree,
278
- language_obj,
279
- query_name,
280
- "" # Source code would be passed here
281
- )
282
- results[query_name] = result
283
- except Exception as e:
284
- logger.error(f"Error executing query {query_name}: {e}")
285
- results[query_name] = {'error': str(e), 'captures': []}
286
-
287
- return results
288
-
289
- except Exception as e:
290
- logger.error(f"Error executing queries: {e}")
291
- return {}
292
-
293
- def _extract_elements(
294
- self,
295
- parse_result: ParseResult,
296
- plugin: Optional[Any]
297
- ) -> List[CodeElement]:
298
- """
299
- Extract code elements using the language plugin.
300
-
301
- Args:
302
- parse_result: Result from parsing operation
303
- plugin: Language plugin
304
-
305
- Returns:
306
- List of extracted code elements
307
- """
308
- try:
309
- if plugin and hasattr(plugin, 'create_extractor'):
310
- extractor = plugin.create_extractor()
311
- if extractor:
312
- # Extract different types of elements
313
- elements = []
314
-
315
- # Extract functions/methods
316
- if hasattr(extractor, 'extract_functions'):
317
- functions = extractor.extract_functions(parse_result.tree, parse_result.source_code)
318
- elements.extend(functions)
319
-
320
- # Extract classes
321
- if hasattr(extractor, 'extract_classes'):
322
- classes = extractor.extract_classes(parse_result.tree, parse_result.source_code)
323
- elements.extend(classes)
324
-
325
- # Extract variables/fields
326
- if hasattr(extractor, 'extract_variables'):
327
- variables = extractor.extract_variables(parse_result.tree, parse_result.source_code)
328
- elements.extend(variables)
329
-
330
- # Extract imports
331
- if hasattr(extractor, 'extract_imports'):
332
- imports = extractor.extract_imports(parse_result.tree, parse_result.source_code)
333
- elements.extend(imports)
334
-
335
- return elements
336
-
337
- # Fallback: create basic elements from query results
338
- return self._create_basic_elements(parse_result)
339
-
340
- except Exception as e:
341
- logger.error(f"Error extracting elements: {e}")
342
- return []
343
-
344
- def _create_basic_elements(self, parse_result: ParseResult) -> List[CodeElement]:
345
- """
346
- Create basic code elements as fallback.
347
-
348
- Args:
349
- parse_result: Result from parsing operation
350
-
351
- Returns:
352
- List of basic code elements
353
- """
354
- # This is a basic fallback implementation
355
- # Real implementation would extract meaningful elements
356
- elements = []
357
-
358
- try:
359
- if parse_result.tree and parse_result.tree.root_node:
360
- # Create a basic element representing the file
361
- from ..models import CodeElement
362
-
363
- # Note: CodeElement is abstract, so we'd need a concrete implementation
364
- # For now, return empty list until concrete element types are available
365
- pass
366
-
367
- except Exception as e:
368
- logger.error(f"Error creating basic elements: {e}")
369
-
370
- return elements
371
-
372
- def _count_nodes(self, tree: Optional[Tree]) -> int:
373
- """
374
- Count nodes in the AST tree.
375
-
376
- Args:
377
- tree: Tree-sitter tree
378
-
379
- Returns:
380
- Number of nodes in the tree
381
- """
382
- if tree is None or tree.root_node is None:
383
- return 0
384
-
385
- try:
386
- def count_recursive(node: Any) -> int:
387
- """Recursively count nodes."""
388
- count = 1 # Count current node
389
- if hasattr(node, 'children'):
390
- for child in node.children:
391
- count += count_recursive(child)
392
- return count
393
-
394
- return count_recursive(tree.root_node)
395
-
396
- except Exception as e:
397
- logger.error(f"Error counting nodes: {e}")
398
- return 0
399
-
400
- def _get_language_object(self, tree: Tree) -> Optional[Any]:
401
- """
402
- Get the language object associated with a tree.
403
-
404
- Args:
405
- tree: Tree-sitter tree
406
-
407
- Returns:
408
- Language object or None
409
- """
410
- try:
411
- # Tree-sitter trees have a language property
412
- if hasattr(tree, 'language'):
413
- return tree.language
414
- return None
415
- except Exception as e:
416
- logger.error(f"Error getting language object: {e}")
417
- return None
418
-
419
- def _initialize_plugins(self) -> None:
420
- """Initialize the plugin system."""
421
- try:
422
- if self.plugin_manager:
423
- plugins = self.plugin_manager.load_plugins()
424
- logger.info(f"Loaded {len(plugins)} plugins successfully")
425
- else:
426
- logger.warning("Plugin manager not available")
427
- except Exception as e:
428
- logger.error(f"Plugin initialization failed: {e}")
429
- # Don't raise here to allow engine to work without plugins
430
- logger.warning("Continuing without plugins")
431
-
432
- def _create_empty_result(
433
- self,
434
- file_path: Optional[str],
435
- language: str,
436
- error: Optional[str] = None
437
- ) -> AnalysisResult:
438
- """
439
- Create an empty analysis result.
440
-
441
- Args:
442
- file_path: File path
443
- language: Programming language
444
- error: Optional error message
445
-
446
- Returns:
447
- Empty AnalysisResult
448
- """
449
- return AnalysisResult(
450
- file_path=file_path or "",
451
- language=language,
452
- line_count=0,
453
- success=error is None,
454
- elements=[],
455
- node_count=0,
456
- query_results={},
457
- source_code="",
458
- error_message=error,
459
- analysis_time=0.0,
460
- # Initialize empty collections
461
- classes=[],
462
- methods=[],
463
- fields=[],
464
- imports=[],
465
- annotations=[],
466
- package=None
467
- )
468
- def get_supported_languages(self) -> List[str]:
469
- """
470
- Get list of supported programming languages.
471
-
472
- Returns:
473
- List of supported language names
474
- """
475
- try:
476
- if self.plugin_manager:
477
- return list(self.plugin_manager.get_supported_languages())
478
- return []
479
- except Exception as e:
480
- logger.error(f"Error getting supported languages: {e}")
481
- def get_available_queries(self, language: str) -> List[str]:
482
- """
483
- Get available queries for a specific language.
484
-
485
- Args:
486
- language: Programming language name
487
-
488
- Returns:
489
- List of available query names
490
- """
491
- try:
492
- plugin = self._get_language_plugin(language)
493
- if plugin and hasattr(plugin, 'get_supported_queries'):
494
- return plugin.get_supported_queries()
495
- else:
496
- # Return default queries
497
- return ['class', 'method', 'field']
498
- except Exception as e:
499
- logger.error(f"Error getting available queries for {language}: {e}")
500
- # Add compatibility methods for API layer
501
- @property
502
- def language_registry(self):
503
- """Provide compatibility with API layer expecting language_registry"""
504
- return self
505
-
506
- def detect_language_from_file(self, file_path: Path) -> Optional[str]:
507
- """
508
- Detect language from file path (compatibility method)
509
-
510
- Args:
511
- file_path: Path to the file
512
-
513
- Returns:
514
- Detected language name or None
515
- """
516
- try:
517
- return self.language_detector.detect_from_extension(str(file_path))
518
- except Exception as e:
519
- logger.error(f"Error detecting language for {file_path}: {e}")
520
- return None
521
-
522
- def get_extensions_for_language(self, language: str) -> List[str]:
523
- """
524
- Get file extensions for a language (compatibility method)
525
-
526
- Args:
527
- language: Programming language name
528
-
529
- Returns:
530
- List of file extensions
531
- """
532
- try:
533
- # Get extensions from language detector
534
- extensions = []
535
- for ext, lang in self.language_detector.EXTENSION_MAPPING.items():
536
- if lang == language:
537
- extensions.append(ext)
538
- return extensions
539
- except Exception as e:
540
- logger.error(f"Error getting extensions for {language}: {e}")
541
- return []
542
-
543
- def get_registry_info(self) -> dict:
544
- """
545
- Get registry information (compatibility method)
546
-
547
- Returns:
548
- Registry information dictionary
549
- """
550
- try:
551
- return {
552
- "supported_languages": self.get_supported_languages(),
553
- "total_languages": len(self.get_supported_languages()),
554
- "language_detector_available": self.language_detector is not None,
555
- "plugin_manager_available": self.plugin_manager is not None
556
- }
557
- except Exception as e:
558
- logger.error(f"Error getting registry info: {e}")
559
- return {}
560
- return []
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, file_path: str | Path, language: str | None = None
52
+ ) -> AnalysisResult:
53
+ """
54
+ Analyze a source code file.
55
+
56
+ Args:
57
+ file_path: Path to the file to analyze
58
+ language: Optional language override
59
+
60
+ Returns:
61
+ AnalysisResult containing analysis results
62
+ """
63
+ file_path_obj = Path(file_path)
64
+ file_path_str = str(file_path_obj)
65
+
66
+ try:
67
+ # Check if file exists
68
+ if not file_path_obj.exists():
69
+ raise FileNotFoundError(f"File not found: {file_path_str}")
70
+
71
+ # Determine language
72
+ detected_language = self._determine_language(file_path_obj, language)
73
+
74
+ # Parse the file
75
+ parse_result = self.parser.parse_file(file_path_str, detected_language)
76
+
77
+ if not parse_result.success:
78
+ logger.warning(
79
+ f"Parsing failed for {file_path_str}: {parse_result.error_message}"
80
+ )
81
+ return self._create_empty_result(
82
+ file_path_str, detected_language, error=parse_result.error_message
83
+ )
84
+
85
+ # Perform analysis
86
+ return self._perform_analysis(parse_result)
87
+
88
+ except FileNotFoundError:
89
+ raise
90
+ except PermissionError:
91
+ raise
92
+ except Exception as e:
93
+ logger.error(f"Error analyzing file {file_path_str}: {e}")
94
+ return self._create_empty_result(
95
+ file_path_str, language or "unknown", error=str(e)
96
+ )
97
+
98
+ def analyze_code(
99
+ self,
100
+ source_code: str,
101
+ language: str | None = None,
102
+ filename: str | None = None,
103
+ ) -> AnalysisResult:
104
+ """
105
+ Analyze source code string.
106
+
107
+ Args:
108
+ source_code: Source code to analyze
109
+ language: Programming language (required if no filename)
110
+ filename: Optional filename for language detection
111
+
112
+ Returns:
113
+ AnalysisResult containing analysis results
114
+ """
115
+ try:
116
+ # Determine language
117
+ if language is None and filename is not None:
118
+ language = self.language_detector.detect_from_extension(filename)
119
+ elif language is None:
120
+ language = "unknown"
121
+
122
+ # Parse the code
123
+ parse_result = self.parser.parse_code(source_code, language, filename)
124
+
125
+ if not parse_result.success:
126
+ logger.warning(
127
+ f"Parsing failed for {language} code: {parse_result.error_message}"
128
+ )
129
+ return self._create_empty_result(
130
+ filename, language, error=parse_result.error_message
131
+ )
132
+
133
+ # Perform analysis
134
+ return self._perform_analysis(parse_result)
135
+
136
+ except Exception as e:
137
+ logger.error(f"Error analyzing {language} code: {e}")
138
+ return self._create_empty_result(
139
+ filename, language or "unknown", error=str(e)
140
+ )
141
+
142
+ def _determine_language(
143
+ self, file_path: Path, language_override: str | None
144
+ ) -> str:
145
+ """
146
+ Determine the programming language for a file.
147
+
148
+ Args:
149
+ file_path: Path to the file
150
+ language_override: Optional language override
151
+
152
+ Returns:
153
+ Detected or overridden language
154
+ """
155
+ if language_override:
156
+ return language_override
157
+
158
+ try:
159
+ return self.language_detector.detect_from_extension(str(file_path))
160
+ except Exception as e:
161
+ logger.warning(f"Language detection failed for {file_path}: {e}")
162
+ return "unknown"
163
+
164
+ def _perform_analysis(self, parse_result: ParseResult) -> AnalysisResult:
165
+ """
166
+ Perform comprehensive analysis on parsed code.
167
+
168
+ Args:
169
+ parse_result: Result from parsing operation
170
+
171
+ Returns:
172
+ AnalysisResult containing analysis results
173
+ """
174
+ try:
175
+ # Get plugin for the language
176
+ plugin = self._get_language_plugin(parse_result.language)
177
+
178
+ # Execute queries
179
+ query_results = self._execute_queries(parse_result.tree, plugin)
180
+
181
+ # Extract elements
182
+ elements = self._extract_elements(parse_result, plugin)
183
+
184
+ # Count nodes
185
+ node_count = self._count_nodes(parse_result.tree)
186
+
187
+ # Create analysis result using existing AnalysisResult structure
188
+ return AnalysisResult(
189
+ file_path=parse_result.file_path or "",
190
+ language=parse_result.language,
191
+ elements=elements,
192
+ node_count=node_count,
193
+ query_results=query_results,
194
+ source_code=parse_result.source_code,
195
+ line_count=(
196
+ len(parse_result.source_code.splitlines())
197
+ if parse_result.source_code
198
+ else 0
199
+ ),
200
+ success=True,
201
+ error_message=None,
202
+ analysis_time=0.0,
203
+ # Initialize empty collections for now
204
+ classes=[],
205
+ methods=[],
206
+ fields=[],
207
+ imports=[],
208
+ annotations=[],
209
+ package=None,
210
+ )
211
+
212
+ except Exception as e:
213
+ logger.error(f"Error performing analysis: {e}")
214
+ return self._create_empty_result(
215
+ parse_result.file_path, parse_result.language, error=str(e)
216
+ )
217
+
218
+ def _get_language_plugin(self, language: str) -> Any | None:
219
+ """
220
+ Get the appropriate language plugin.
221
+
222
+ Args:
223
+ language: Programming language name
224
+
225
+ Returns:
226
+ Language plugin instance or None
227
+ """
228
+ if self.plugin_manager is not None:
229
+ try:
230
+ return self.plugin_manager.get_plugin(language)
231
+ except Exception as e:
232
+ logger.error(f"Error getting plugin for {language}: {e}")
233
+
234
+ return None
235
+
236
+ def _execute_queries(self, tree: Tree | None, plugin: Any | None) -> dict[str, Any]:
237
+ """
238
+ Execute queries on the parsed tree.
239
+
240
+ Args:
241
+ tree: Parsed Tree-sitter tree
242
+ plugin: Language plugin
243
+
244
+ Returns:
245
+ Dictionary of query results
246
+ """
247
+ if tree is None:
248
+ return {}
249
+
250
+ try:
251
+ # If plugin is available, use its supported queries
252
+ if plugin and hasattr(plugin, "get_supported_queries"):
253
+ query_names = plugin.get_supported_queries()
254
+ else:
255
+ # Fallback to common queries that exist in the system
256
+ query_names = ["class", "method", "field"] # Use actual query names
257
+
258
+ # Get language object for query execution
259
+ language_obj = self._get_language_object(tree)
260
+ if language_obj is None:
261
+ return {}
262
+
263
+ # Execute queries
264
+ results = {}
265
+ for query_name in query_names:
266
+ try:
267
+ result = self.query_executor.execute_query(
268
+ tree,
269
+ language_obj,
270
+ query_name,
271
+ "", # Source code would be passed here
272
+ )
273
+ results[query_name] = result
274
+ except Exception as e:
275
+ logger.error(f"Error executing query {query_name}: {e}")
276
+ results[query_name] = {"error": str(e), "captures": []}
277
+
278
+ return results
279
+
280
+ except Exception as e:
281
+ logger.error(f"Error executing queries: {e}")
282
+ return {}
283
+
284
+ def _extract_elements(
285
+ self, parse_result: ParseResult, plugin: Any | None
286
+ ) -> list[CodeElement]:
287
+ """
288
+ Extract code elements using the language plugin.
289
+
290
+ Args:
291
+ parse_result: Result from parsing operation
292
+ plugin: Language plugin
293
+
294
+ Returns:
295
+ List of extracted code elements
296
+ """
297
+ try:
298
+ if plugin and hasattr(plugin, "create_extractor"):
299
+ extractor = plugin.create_extractor()
300
+ if extractor:
301
+ # Extract different types of elements
302
+ elements = []
303
+
304
+ # Extract functions/methods
305
+ if hasattr(extractor, "extract_functions"):
306
+ functions = extractor.extract_functions(
307
+ parse_result.tree, parse_result.source_code
308
+ )
309
+ elements.extend(functions)
310
+
311
+ # Extract classes
312
+ if hasattr(extractor, "extract_classes"):
313
+ classes = extractor.extract_classes(
314
+ parse_result.tree, parse_result.source_code
315
+ )
316
+ elements.extend(classes)
317
+
318
+ # Extract variables/fields
319
+ if hasattr(extractor, "extract_variables"):
320
+ variables = extractor.extract_variables(
321
+ parse_result.tree, parse_result.source_code
322
+ )
323
+ elements.extend(variables)
324
+
325
+ # Extract imports
326
+ if hasattr(extractor, "extract_imports"):
327
+ imports = extractor.extract_imports(
328
+ parse_result.tree, parse_result.source_code
329
+ )
330
+ elements.extend(imports)
331
+
332
+ return elements
333
+
334
+ # Fallback: create basic elements from query results
335
+ return self._create_basic_elements(parse_result)
336
+
337
+ except Exception as e:
338
+ logger.error(f"Error extracting elements: {e}")
339
+ return []
340
+
341
+ def _create_basic_elements(self, parse_result: ParseResult) -> list[CodeElement]:
342
+ """
343
+ Create basic code elements as fallback.
344
+
345
+ Args:
346
+ parse_result: Result from parsing operation
347
+
348
+ Returns:
349
+ List of basic code elements
350
+ """
351
+ # This is a basic fallback implementation
352
+ # Real implementation would extract meaningful elements
353
+ elements: list[Any] = []
354
+
355
+ try:
356
+ if parse_result.tree and parse_result.tree.root_node:
357
+ # Create a basic element representing the file
358
+
359
+ # Note: CodeElement is abstract, so we'd need a concrete implementation
360
+ # For now, return empty list until concrete element types are available
361
+ pass
362
+
363
+ except Exception as e:
364
+ logger.error(f"Error creating basic elements: {e}")
365
+
366
+ return elements
367
+
368
+ def _count_nodes(self, tree: Tree | None) -> int:
369
+ """
370
+ Count nodes in the AST tree.
371
+
372
+ Args:
373
+ tree: Tree-sitter tree
374
+
375
+ Returns:
376
+ Number of nodes in the tree
377
+ """
378
+ if tree is None or tree.root_node is None:
379
+ return 0
380
+
381
+ try:
382
+
383
+ def count_recursive(node: Any) -> int:
384
+ """Recursively count nodes."""
385
+ count = 1 # Count current node
386
+ if hasattr(node, "children"):
387
+ for child in node.children:
388
+ count += count_recursive(child)
389
+ return count
390
+
391
+ return count_recursive(tree.root_node)
392
+
393
+ except Exception as e:
394
+ logger.error(f"Error counting nodes: {e}")
395
+ return 0
396
+
397
+ def _get_language_object(self, tree: Tree) -> Any | None:
398
+ """
399
+ Get the language object associated with a tree.
400
+
401
+ Args:
402
+ tree: Tree-sitter tree
403
+
404
+ Returns:
405
+ Language object or None
406
+ """
407
+ try:
408
+ # Tree-sitter trees have a language property
409
+ if hasattr(tree, "language"):
410
+ return tree.language
411
+ return None
412
+ except Exception as e:
413
+ logger.error(f"Error getting language object: {e}")
414
+ return None
415
+
416
+ def _initialize_plugins(self) -> None:
417
+ """Initialize the plugin system."""
418
+ try:
419
+ if self.plugin_manager:
420
+ plugins = self.plugin_manager.load_plugins()
421
+ logger.info(f"Loaded {len(plugins)} plugins successfully")
422
+ else:
423
+ logger.warning("Plugin manager not available")
424
+ except Exception as e:
425
+ logger.error(f"Plugin initialization failed: {e}")
426
+ # Don't raise here to allow engine to work without plugins
427
+ logger.warning("Continuing without plugins")
428
+
429
+ def _create_empty_result(
430
+ self, file_path: str | None, language: str, error: str | None = None
431
+ ) -> AnalysisResult:
432
+ """
433
+ Create an empty analysis result.
434
+
435
+ Args:
436
+ file_path: File path
437
+ language: Programming language
438
+ error: Optional error message
439
+
440
+ Returns:
441
+ Empty AnalysisResult
442
+ """
443
+ return AnalysisResult(
444
+ file_path=file_path or "",
445
+ language=language,
446
+ line_count=0,
447
+ success=error is None,
448
+ elements=[],
449
+ node_count=0,
450
+ query_results={},
451
+ source_code="",
452
+ error_message=error,
453
+ analysis_time=0.0,
454
+ # Initialize empty collections
455
+ classes=[],
456
+ methods=[],
457
+ fields=[],
458
+ imports=[],
459
+ annotations=[],
460
+ package=None,
461
+ )
462
+
463
+ def get_supported_languages(self) -> list[str]:
464
+ """
465
+ Get list of supported programming languages.
466
+
467
+ Returns:
468
+ List of supported language names
469
+ """
470
+ try:
471
+ if self.plugin_manager:
472
+ return list(self.plugin_manager.get_supported_languages())
473
+ return []
474
+ except Exception as e:
475
+ logger.error(f"Error getting supported languages: {e}")
476
+ return []
477
+
478
+ def get_available_queries(self, language: str) -> list[str]:
479
+ """
480
+ Get available queries for a specific language.
481
+
482
+ Args:
483
+ language: Programming language name
484
+
485
+ Returns:
486
+ List of available query names
487
+ """
488
+ try:
489
+ plugin = self._get_language_plugin(language)
490
+ if plugin and hasattr(plugin, "get_supported_queries"):
491
+ queries = plugin.get_supported_queries()
492
+ return list(queries) if queries else []
493
+ else:
494
+ # Return default queries
495
+ return ["class", "method", "field"]
496
+ except Exception as e:
497
+ logger.error(f"Error getting available queries for {language}: {e}")
498
+ return []
499
+
500
+ # Add compatibility methods for API layer
501
+ @property
502
+ def language_registry(self) -> "AnalysisEngine":
503
+ """Provide compatibility with API layer expecting language_registry"""
504
+ return self
505
+
506
+ def detect_language_from_file(self, file_path: Path) -> str | None:
507
+ """
508
+ Detect language from file path (compatibility method)
509
+
510
+ Args:
511
+ file_path: Path to the file
512
+
513
+ Returns:
514
+ Detected language name or None
515
+ """
516
+ try:
517
+ return self.language_detector.detect_from_extension(str(file_path))
518
+ except Exception as e:
519
+ logger.error(f"Error detecting language for {file_path}: {e}")
520
+ return None
521
+
522
+ def get_extensions_for_language(self, language: str) -> list[str]:
523
+ """
524
+ Get file extensions for a language (compatibility method)
525
+
526
+ Args:
527
+ language: Programming language name
528
+
529
+ Returns:
530
+ List of file extensions
531
+ """
532
+ try:
533
+ # Get extensions from language detector
534
+ extensions = []
535
+ for ext, lang in self.language_detector.EXTENSION_MAPPING.items():
536
+ if lang == language:
537
+ extensions.append(ext)
538
+ return extensions
539
+ except Exception as e:
540
+ logger.error(f"Error getting extensions for {language}: {e}")
541
+ return []
542
+
543
+ def get_registry_info(self) -> dict:
544
+ """
545
+ Get registry information (compatibility method)
546
+
547
+ Returns:
548
+ Registry information dictionary
549
+ """
550
+ try:
551
+ return {
552
+ "supported_languages": self.get_supported_languages(),
553
+ "total_languages": len(self.get_supported_languages()),
554
+ "language_detector_available": self.language_detector is not None,
555
+ "plugin_manager_available": self.plugin_manager is not None,
556
+ }
557
+ except Exception as e:
558
+ logger.error(f"Error getting registry info: {e}")
559
+ return {}