tree-sitter-analyzer 0.8.3__py3-none-any.whl → 0.9.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of tree-sitter-analyzer might be problematic. Click here for more details.

Files changed (62) hide show
  1. tree_sitter_analyzer/__init__.py +132 -132
  2. tree_sitter_analyzer/__main__.py +11 -11
  3. tree_sitter_analyzer/api.py +533 -533
  4. tree_sitter_analyzer/cli/__init__.py +39 -39
  5. tree_sitter_analyzer/cli/__main__.py +12 -12
  6. tree_sitter_analyzer/cli/commands/__init__.py +26 -26
  7. tree_sitter_analyzer/cli/commands/advanced_command.py +88 -88
  8. tree_sitter_analyzer/cli/commands/base_command.py +182 -180
  9. tree_sitter_analyzer/cli/commands/structure_command.py +138 -138
  10. tree_sitter_analyzer/cli/commands/summary_command.py +101 -101
  11. tree_sitter_analyzer/core/__init__.py +15 -15
  12. tree_sitter_analyzer/core/analysis_engine.py +74 -78
  13. tree_sitter_analyzer/core/cache_service.py +320 -320
  14. tree_sitter_analyzer/core/engine.py +566 -566
  15. tree_sitter_analyzer/core/parser.py +293 -293
  16. tree_sitter_analyzer/encoding_utils.py +459 -459
  17. tree_sitter_analyzer/file_handler.py +210 -210
  18. tree_sitter_analyzer/formatters/__init__.py +1 -1
  19. tree_sitter_analyzer/formatters/base_formatter.py +167 -167
  20. tree_sitter_analyzer/formatters/formatter_factory.py +78 -78
  21. tree_sitter_analyzer/formatters/java_formatter.py +18 -18
  22. tree_sitter_analyzer/formatters/python_formatter.py +19 -19
  23. tree_sitter_analyzer/interfaces/__init__.py +9 -9
  24. tree_sitter_analyzer/interfaces/cli.py +528 -528
  25. tree_sitter_analyzer/interfaces/cli_adapter.py +344 -343
  26. tree_sitter_analyzer/interfaces/mcp_adapter.py +206 -206
  27. tree_sitter_analyzer/language_detector.py +53 -53
  28. tree_sitter_analyzer/languages/__init__.py +10 -10
  29. tree_sitter_analyzer/languages/java_plugin.py +1 -1
  30. tree_sitter_analyzer/languages/javascript_plugin.py +446 -446
  31. tree_sitter_analyzer/languages/python_plugin.py +755 -755
  32. tree_sitter_analyzer/mcp/__init__.py +34 -31
  33. tree_sitter_analyzer/mcp/resources/__init__.py +44 -44
  34. tree_sitter_analyzer/mcp/resources/code_file_resource.py +209 -209
  35. tree_sitter_analyzer/mcp/server.py +623 -436
  36. tree_sitter_analyzer/mcp/tools/__init__.py +30 -30
  37. tree_sitter_analyzer/mcp/tools/analyze_scale_tool.py +10 -6
  38. tree_sitter_analyzer/mcp/tools/analyze_scale_tool_cli_compatible.py +247 -242
  39. tree_sitter_analyzer/mcp/tools/base_tool.py +54 -54
  40. tree_sitter_analyzer/mcp/tools/read_partial_tool.py +310 -308
  41. tree_sitter_analyzer/mcp/tools/table_format_tool.py +386 -379
  42. tree_sitter_analyzer/mcp/tools/universal_analyze_tool.py +563 -559
  43. tree_sitter_analyzer/mcp/utils/__init__.py +107 -107
  44. tree_sitter_analyzer/models.py +10 -10
  45. tree_sitter_analyzer/output_manager.py +253 -253
  46. tree_sitter_analyzer/plugins/__init__.py +280 -280
  47. tree_sitter_analyzer/plugins/base.py +529 -529
  48. tree_sitter_analyzer/plugins/manager.py +379 -379
  49. tree_sitter_analyzer/queries/__init__.py +26 -26
  50. tree_sitter_analyzer/queries/java.py +391 -391
  51. tree_sitter_analyzer/queries/javascript.py +148 -148
  52. tree_sitter_analyzer/queries/python.py +285 -285
  53. tree_sitter_analyzer/queries/typescript.py +229 -229
  54. tree_sitter_analyzer/query_loader.py +257 -257
  55. tree_sitter_analyzer/security/boundary_manager.py +237 -279
  56. tree_sitter_analyzer/security/validator.py +60 -58
  57. tree_sitter_analyzer/utils.py +294 -277
  58. {tree_sitter_analyzer-0.8.3.dist-info → tree_sitter_analyzer-0.9.2.dist-info}/METADATA +28 -19
  59. tree_sitter_analyzer-0.9.2.dist-info/RECORD +77 -0
  60. {tree_sitter_analyzer-0.8.3.dist-info → tree_sitter_analyzer-0.9.2.dist-info}/entry_points.txt +1 -0
  61. tree_sitter_analyzer-0.8.3.dist-info/RECORD +0 -77
  62. {tree_sitter_analyzer-0.8.3.dist-info → tree_sitter_analyzer-0.9.2.dist-info}/WHEEL +0 -0
@@ -1,566 +1,566 @@
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 packages first (needed for proper class package resolution)
305
- if hasattr(extractor, "extract_packages"):
306
- packages = extractor.extract_packages(
307
- parse_result.tree, parse_result.source_code
308
- )
309
- elements.extend(packages)
310
-
311
- # Extract functions/methods
312
- if hasattr(extractor, "extract_functions"):
313
- functions = extractor.extract_functions(
314
- parse_result.tree, parse_result.source_code
315
- )
316
- elements.extend(functions)
317
-
318
- # Extract classes
319
- if hasattr(extractor, "extract_classes"):
320
- classes = extractor.extract_classes(
321
- parse_result.tree, parse_result.source_code
322
- )
323
- elements.extend(classes)
324
-
325
- # Extract variables/fields
326
- if hasattr(extractor, "extract_variables"):
327
- variables = extractor.extract_variables(
328
- parse_result.tree, parse_result.source_code
329
- )
330
- elements.extend(variables)
331
-
332
- # Extract imports
333
- if hasattr(extractor, "extract_imports"):
334
- imports = extractor.extract_imports(
335
- parse_result.tree, parse_result.source_code
336
- )
337
- elements.extend(imports)
338
-
339
- return elements
340
-
341
- # Fallback: create basic elements from query results
342
- return self._create_basic_elements(parse_result)
343
-
344
- except Exception as e:
345
- logger.error(f"Error extracting elements: {e}")
346
- return []
347
-
348
- def _create_basic_elements(self, parse_result: ParseResult) -> list[CodeElement]:
349
- """
350
- Create basic code elements as fallback.
351
-
352
- Args:
353
- parse_result: Result from parsing operation
354
-
355
- Returns:
356
- List of basic code elements
357
- """
358
- # This is a basic fallback implementation
359
- # Real implementation would extract meaningful elements
360
- elements: list[Any] = []
361
-
362
- try:
363
- if parse_result.tree and parse_result.tree.root_node:
364
- # Create a basic element representing the file
365
-
366
- # Note: CodeElement is abstract, so we'd need a concrete implementation
367
- # For now, return empty list until concrete element types are available
368
- pass
369
-
370
- except Exception as e:
371
- logger.error(f"Error creating basic elements: {e}")
372
-
373
- return elements
374
-
375
- def _count_nodes(self, tree: Tree | None) -> int:
376
- """
377
- Count nodes in the AST tree.
378
-
379
- Args:
380
- tree: Tree-sitter tree
381
-
382
- Returns:
383
- Number of nodes in the tree
384
- """
385
- if tree is None or tree.root_node is None:
386
- return 0
387
-
388
- try:
389
-
390
- def count_recursive(node: Any) -> int:
391
- """Recursively count nodes."""
392
- count = 1 # Count current node
393
- if hasattr(node, "children"):
394
- for child in node.children:
395
- count += count_recursive(child)
396
- return count
397
-
398
- return count_recursive(tree.root_node)
399
-
400
- except Exception as e:
401
- logger.error(f"Error counting nodes: {e}")
402
- return 0
403
-
404
- def _get_language_object(self, tree: Tree) -> Any | None:
405
- """
406
- Get the language object associated with a tree.
407
-
408
- Args:
409
- tree: Tree-sitter tree
410
-
411
- Returns:
412
- Language object or None
413
- """
414
- try:
415
- # Tree-sitter trees have a language property
416
- if hasattr(tree, "language"):
417
- return tree.language
418
- return None
419
- except Exception as e:
420
- logger.error(f"Error getting language object: {e}")
421
- return None
422
-
423
- def _initialize_plugins(self) -> None:
424
- """Initialize the plugin system."""
425
- try:
426
- if self.plugin_manager:
427
- plugins = self.plugin_manager.load_plugins()
428
- logger.info(f"Loaded {len(plugins)} plugins successfully")
429
- else:
430
- logger.warning("Plugin manager not available")
431
- except Exception as e:
432
- logger.error(f"Plugin initialization failed: {e}")
433
- # Don't raise here to allow engine to work without plugins
434
- logger.warning("Continuing without plugins")
435
-
436
- def _create_empty_result(
437
- self, file_path: str | None, language: str, error: str | None = None
438
- ) -> AnalysisResult:
439
- """
440
- Create an empty analysis result.
441
-
442
- Args:
443
- file_path: File path
444
- language: Programming language
445
- error: Optional error message
446
-
447
- Returns:
448
- Empty AnalysisResult
449
- """
450
- return AnalysisResult(
451
- file_path=file_path or "",
452
- language=language,
453
- line_count=0,
454
- success=error is None,
455
- elements=[],
456
- node_count=0,
457
- query_results={},
458
- source_code="",
459
- error_message=error,
460
- analysis_time=0.0,
461
- # Initialize empty collections
462
- classes=[],
463
- methods=[],
464
- fields=[],
465
- imports=[],
466
- annotations=[],
467
- package=None,
468
- )
469
-
470
- def get_supported_languages(self) -> list[str]:
471
- """
472
- Get list of supported programming languages.
473
-
474
- Returns:
475
- List of supported language names
476
- """
477
- try:
478
- if self.plugin_manager:
479
- return list(self.plugin_manager.get_supported_languages())
480
- return []
481
- except Exception as e:
482
- logger.error(f"Error getting supported languages: {e}")
483
- return []
484
-
485
- def get_available_queries(self, language: str) -> list[str]:
486
- """
487
- Get available queries for a specific language.
488
-
489
- Args:
490
- language: Programming language name
491
-
492
- Returns:
493
- List of available query names
494
- """
495
- try:
496
- plugin = self._get_language_plugin(language)
497
- if plugin and hasattr(plugin, "get_supported_queries"):
498
- queries = plugin.get_supported_queries()
499
- return list(queries) if queries else []
500
- else:
501
- # Return default queries
502
- return ["class", "method", "field"]
503
- except Exception as e:
504
- logger.error(f"Error getting available queries for {language}: {e}")
505
- return []
506
-
507
- # Add compatibility methods for API layer
508
- @property
509
- def language_registry(self) -> "AnalysisEngine":
510
- """Provide compatibility with API layer expecting language_registry"""
511
- return self
512
-
513
- def detect_language_from_file(self, file_path: Path) -> str | None:
514
- """
515
- Detect language from file path (compatibility method)
516
-
517
- Args:
518
- file_path: Path to the file
519
-
520
- Returns:
521
- Detected language name or None
522
- """
523
- try:
524
- return self.language_detector.detect_from_extension(str(file_path))
525
- except Exception as e:
526
- logger.error(f"Error detecting language for {file_path}: {e}")
527
- return None
528
-
529
- def get_extensions_for_language(self, language: str) -> list[str]:
530
- """
531
- Get file extensions for a language (compatibility method)
532
-
533
- Args:
534
- language: Programming language name
535
-
536
- Returns:
537
- List of file extensions
538
- """
539
- try:
540
- # Get extensions from language detector
541
- extensions = []
542
- for ext, lang in self.language_detector.EXTENSION_MAPPING.items():
543
- if lang == language:
544
- extensions.append(ext)
545
- return extensions
546
- except Exception as e:
547
- logger.error(f"Error getting extensions for {language}: {e}")
548
- return []
549
-
550
- def get_registry_info(self) -> dict:
551
- """
552
- Get registry information (compatibility method)
553
-
554
- Returns:
555
- Registry information dictionary
556
- """
557
- try:
558
- return {
559
- "supported_languages": self.get_supported_languages(),
560
- "total_languages": len(self.get_supported_languages()),
561
- "language_detector_available": self.language_detector is not None,
562
- "plugin_manager_available": self.plugin_manager is not None,
563
- }
564
- except Exception as e:
565
- logger.error(f"Error getting registry info: {e}")
566
- 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 packages first (needed for proper class package resolution)
305
+ if hasattr(extractor, "extract_packages"):
306
+ packages = extractor.extract_packages(
307
+ parse_result.tree, parse_result.source_code
308
+ )
309
+ elements.extend(packages)
310
+
311
+ # Extract functions/methods
312
+ if hasattr(extractor, "extract_functions"):
313
+ functions = extractor.extract_functions(
314
+ parse_result.tree, parse_result.source_code
315
+ )
316
+ elements.extend(functions)
317
+
318
+ # Extract classes
319
+ if hasattr(extractor, "extract_classes"):
320
+ classes = extractor.extract_classes(
321
+ parse_result.tree, parse_result.source_code
322
+ )
323
+ elements.extend(classes)
324
+
325
+ # Extract variables/fields
326
+ if hasattr(extractor, "extract_variables"):
327
+ variables = extractor.extract_variables(
328
+ parse_result.tree, parse_result.source_code
329
+ )
330
+ elements.extend(variables)
331
+
332
+ # Extract imports
333
+ if hasattr(extractor, "extract_imports"):
334
+ imports = extractor.extract_imports(
335
+ parse_result.tree, parse_result.source_code
336
+ )
337
+ elements.extend(imports)
338
+
339
+ return elements
340
+
341
+ # Fallback: create basic elements from query results
342
+ return self._create_basic_elements(parse_result)
343
+
344
+ except Exception as e:
345
+ logger.error(f"Error extracting elements: {e}")
346
+ return []
347
+
348
+ def _create_basic_elements(self, parse_result: ParseResult) -> list[CodeElement]:
349
+ """
350
+ Create basic code elements as fallback.
351
+
352
+ Args:
353
+ parse_result: Result from parsing operation
354
+
355
+ Returns:
356
+ List of basic code elements
357
+ """
358
+ # This is a basic fallback implementation
359
+ # Real implementation would extract meaningful elements
360
+ elements: list[Any] = []
361
+
362
+ try:
363
+ if parse_result.tree and parse_result.tree.root_node:
364
+ # Create a basic element representing the file
365
+
366
+ # Note: CodeElement is abstract, so we'd need a concrete implementation
367
+ # For now, return empty list until concrete element types are available
368
+ pass
369
+
370
+ except Exception as e:
371
+ logger.error(f"Error creating basic elements: {e}")
372
+
373
+ return elements
374
+
375
+ def _count_nodes(self, tree: Tree | None) -> int:
376
+ """
377
+ Count nodes in the AST tree.
378
+
379
+ Args:
380
+ tree: Tree-sitter tree
381
+
382
+ Returns:
383
+ Number of nodes in the tree
384
+ """
385
+ if tree is None or tree.root_node is None:
386
+ return 0
387
+
388
+ try:
389
+
390
+ def count_recursive(node: Any) -> int:
391
+ """Recursively count nodes."""
392
+ count = 1 # Count current node
393
+ if hasattr(node, "children"):
394
+ for child in node.children:
395
+ count += count_recursive(child)
396
+ return count
397
+
398
+ return count_recursive(tree.root_node)
399
+
400
+ except Exception as e:
401
+ logger.error(f"Error counting nodes: {e}")
402
+ return 0
403
+
404
+ def _get_language_object(self, tree: Tree) -> Any | None:
405
+ """
406
+ Get the language object associated with a tree.
407
+
408
+ Args:
409
+ tree: Tree-sitter tree
410
+
411
+ Returns:
412
+ Language object or None
413
+ """
414
+ try:
415
+ # Tree-sitter trees have a language property
416
+ if hasattr(tree, "language"):
417
+ return tree.language
418
+ return None
419
+ except Exception as e:
420
+ logger.error(f"Error getting language object: {e}")
421
+ return None
422
+
423
+ def _initialize_plugins(self) -> None:
424
+ """Initialize the plugin system."""
425
+ try:
426
+ if self.plugin_manager:
427
+ plugins = self.plugin_manager.load_plugins()
428
+ logger.info(f"Loaded {len(plugins)} plugins successfully")
429
+ else:
430
+ logger.warning("Plugin manager not available")
431
+ except Exception as e:
432
+ logger.error(f"Plugin initialization failed: {e}")
433
+ # Don't raise here to allow engine to work without plugins
434
+ logger.warning("Continuing without plugins")
435
+
436
+ def _create_empty_result(
437
+ self, file_path: str | None, language: str, error: str | None = None
438
+ ) -> AnalysisResult:
439
+ """
440
+ Create an empty analysis result.
441
+
442
+ Args:
443
+ file_path: File path
444
+ language: Programming language
445
+ error: Optional error message
446
+
447
+ Returns:
448
+ Empty AnalysisResult
449
+ """
450
+ return AnalysisResult(
451
+ file_path=file_path or "",
452
+ language=language,
453
+ line_count=0,
454
+ success=error is None,
455
+ elements=[],
456
+ node_count=0,
457
+ query_results={},
458
+ source_code="",
459
+ error_message=error,
460
+ analysis_time=0.0,
461
+ # Initialize empty collections
462
+ classes=[],
463
+ methods=[],
464
+ fields=[],
465
+ imports=[],
466
+ annotations=[],
467
+ package=None,
468
+ )
469
+
470
+ def get_supported_languages(self) -> list[str]:
471
+ """
472
+ Get list of supported programming languages.
473
+
474
+ Returns:
475
+ List of supported language names
476
+ """
477
+ try:
478
+ if self.plugin_manager:
479
+ return list(self.plugin_manager.get_supported_languages())
480
+ return []
481
+ except Exception as e:
482
+ logger.error(f"Error getting supported languages: {e}")
483
+ return []
484
+
485
+ def get_available_queries(self, language: str) -> list[str]:
486
+ """
487
+ Get available queries for a specific language.
488
+
489
+ Args:
490
+ language: Programming language name
491
+
492
+ Returns:
493
+ List of available query names
494
+ """
495
+ try:
496
+ plugin = self._get_language_plugin(language)
497
+ if plugin and hasattr(plugin, "get_supported_queries"):
498
+ queries = plugin.get_supported_queries()
499
+ return list(queries) if queries else []
500
+ else:
501
+ # Return default queries
502
+ return ["class", "method", "field"]
503
+ except Exception as e:
504
+ logger.error(f"Error getting available queries for {language}: {e}")
505
+ return []
506
+
507
+ # Add compatibility methods for API layer
508
+ @property
509
+ def language_registry(self) -> "AnalysisEngine":
510
+ """Provide compatibility with API layer expecting language_registry"""
511
+ return self
512
+
513
+ def detect_language_from_file(self, file_path: Path) -> str | None:
514
+ """
515
+ Detect language from file path (compatibility method)
516
+
517
+ Args:
518
+ file_path: Path to the file
519
+
520
+ Returns:
521
+ Detected language name or None
522
+ """
523
+ try:
524
+ return self.language_detector.detect_from_extension(str(file_path))
525
+ except Exception as e:
526
+ logger.error(f"Error detecting language for {file_path}: {e}")
527
+ return None
528
+
529
+ def get_extensions_for_language(self, language: str) -> list[str]:
530
+ """
531
+ Get file extensions for a language (compatibility method)
532
+
533
+ Args:
534
+ language: Programming language name
535
+
536
+ Returns:
537
+ List of file extensions
538
+ """
539
+ try:
540
+ # Get extensions from language detector
541
+ extensions = []
542
+ for ext, lang in self.language_detector.EXTENSION_MAPPING.items():
543
+ if lang == language:
544
+ extensions.append(ext)
545
+ return extensions
546
+ except Exception as e:
547
+ logger.error(f"Error getting extensions for {language}: {e}")
548
+ return []
549
+
550
+ def get_registry_info(self) -> dict:
551
+ """
552
+ Get registry information (compatibility method)
553
+
554
+ Returns:
555
+ Registry information dictionary
556
+ """
557
+ try:
558
+ return {
559
+ "supported_languages": self.get_supported_languages(),
560
+ "total_languages": len(self.get_supported_languages()),
561
+ "language_detector_available": self.language_detector is not None,
562
+ "plugin_manager_available": self.plugin_manager is not None,
563
+ }
564
+ except Exception as e:
565
+ logger.error(f"Error getting registry info: {e}")
566
+ return {}