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