tree-sitter-analyzer 0.1.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 +121 -0
  2. tree_sitter_analyzer/__main__.py +12 -0
  3. tree_sitter_analyzer/api.py +539 -0
  4. tree_sitter_analyzer/cli/__init__.py +39 -0
  5. tree_sitter_analyzer/cli/__main__.py +13 -0
  6. tree_sitter_analyzer/cli/commands/__init__.py +27 -0
  7. tree_sitter_analyzer/cli/commands/advanced_command.py +88 -0
  8. tree_sitter_analyzer/cli/commands/base_command.py +155 -0
  9. tree_sitter_analyzer/cli/commands/default_command.py +19 -0
  10. tree_sitter_analyzer/cli/commands/partial_read_command.py +133 -0
  11. tree_sitter_analyzer/cli/commands/query_command.py +82 -0
  12. tree_sitter_analyzer/cli/commands/structure_command.py +121 -0
  13. tree_sitter_analyzer/cli/commands/summary_command.py +93 -0
  14. tree_sitter_analyzer/cli/commands/table_command.py +233 -0
  15. tree_sitter_analyzer/cli/info_commands.py +121 -0
  16. tree_sitter_analyzer/cli_main.py +276 -0
  17. tree_sitter_analyzer/core/__init__.py +20 -0
  18. tree_sitter_analyzer/core/analysis_engine.py +574 -0
  19. tree_sitter_analyzer/core/cache_service.py +330 -0
  20. tree_sitter_analyzer/core/engine.py +560 -0
  21. tree_sitter_analyzer/core/parser.py +288 -0
  22. tree_sitter_analyzer/core/query.py +502 -0
  23. tree_sitter_analyzer/encoding_utils.py +460 -0
  24. tree_sitter_analyzer/exceptions.py +340 -0
  25. tree_sitter_analyzer/file_handler.py +222 -0
  26. tree_sitter_analyzer/formatters/__init__.py +1 -0
  27. tree_sitter_analyzer/formatters/base_formatter.py +168 -0
  28. tree_sitter_analyzer/formatters/formatter_factory.py +74 -0
  29. tree_sitter_analyzer/formatters/java_formatter.py +270 -0
  30. tree_sitter_analyzer/formatters/python_formatter.py +235 -0
  31. tree_sitter_analyzer/interfaces/__init__.py +10 -0
  32. tree_sitter_analyzer/interfaces/cli.py +557 -0
  33. tree_sitter_analyzer/interfaces/cli_adapter.py +319 -0
  34. tree_sitter_analyzer/interfaces/mcp_adapter.py +170 -0
  35. tree_sitter_analyzer/interfaces/mcp_server.py +416 -0
  36. tree_sitter_analyzer/java_analyzer.py +219 -0
  37. tree_sitter_analyzer/language_detector.py +400 -0
  38. tree_sitter_analyzer/language_loader.py +228 -0
  39. tree_sitter_analyzer/languages/__init__.py +11 -0
  40. tree_sitter_analyzer/languages/java_plugin.py +1113 -0
  41. tree_sitter_analyzer/languages/python_plugin.py +712 -0
  42. tree_sitter_analyzer/mcp/__init__.py +32 -0
  43. tree_sitter_analyzer/mcp/resources/__init__.py +47 -0
  44. tree_sitter_analyzer/mcp/resources/code_file_resource.py +213 -0
  45. tree_sitter_analyzer/mcp/resources/project_stats_resource.py +550 -0
  46. tree_sitter_analyzer/mcp/server.py +319 -0
  47. tree_sitter_analyzer/mcp/tools/__init__.py +36 -0
  48. tree_sitter_analyzer/mcp/tools/analyze_scale_tool.py +558 -0
  49. tree_sitter_analyzer/mcp/tools/analyze_scale_tool_cli_compatible.py +245 -0
  50. tree_sitter_analyzer/mcp/tools/base_tool.py +55 -0
  51. tree_sitter_analyzer/mcp/tools/get_positions_tool.py +448 -0
  52. tree_sitter_analyzer/mcp/tools/read_partial_tool.py +302 -0
  53. tree_sitter_analyzer/mcp/tools/table_format_tool.py +359 -0
  54. tree_sitter_analyzer/mcp/tools/universal_analyze_tool.py +476 -0
  55. tree_sitter_analyzer/mcp/utils/__init__.py +106 -0
  56. tree_sitter_analyzer/mcp/utils/error_handler.py +549 -0
  57. tree_sitter_analyzer/models.py +481 -0
  58. tree_sitter_analyzer/output_manager.py +264 -0
  59. tree_sitter_analyzer/plugins/__init__.py +334 -0
  60. tree_sitter_analyzer/plugins/base.py +446 -0
  61. tree_sitter_analyzer/plugins/java_plugin.py +625 -0
  62. tree_sitter_analyzer/plugins/javascript_plugin.py +439 -0
  63. tree_sitter_analyzer/plugins/manager.py +355 -0
  64. tree_sitter_analyzer/plugins/plugin_loader.py +83 -0
  65. tree_sitter_analyzer/plugins/python_plugin.py +598 -0
  66. tree_sitter_analyzer/plugins/registry.py +366 -0
  67. tree_sitter_analyzer/queries/__init__.py +27 -0
  68. tree_sitter_analyzer/queries/java.py +394 -0
  69. tree_sitter_analyzer/queries/javascript.py +149 -0
  70. tree_sitter_analyzer/queries/python.py +286 -0
  71. tree_sitter_analyzer/queries/typescript.py +230 -0
  72. tree_sitter_analyzer/query_loader.py +260 -0
  73. tree_sitter_analyzer/table_formatter.py +448 -0
  74. tree_sitter_analyzer/utils.py +201 -0
  75. tree_sitter_analyzer-0.1.0.dist-info/METADATA +581 -0
  76. tree_sitter_analyzer-0.1.0.dist-info/RECORD +78 -0
  77. tree_sitter_analyzer-0.1.0.dist-info/WHEEL +4 -0
  78. tree_sitter_analyzer-0.1.0.dist-info/entry_points.txt +8 -0
@@ -0,0 +1,560 @@
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 []