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.
- tree_sitter_analyzer/__init__.py +132 -132
- tree_sitter_analyzer/__main__.py +11 -11
- tree_sitter_analyzer/api.py +533 -533
- tree_sitter_analyzer/cli/__init__.py +39 -39
- tree_sitter_analyzer/cli/__main__.py +12 -12
- tree_sitter_analyzer/cli/commands/__init__.py +26 -26
- tree_sitter_analyzer/cli/commands/advanced_command.py +88 -88
- tree_sitter_analyzer/cli/commands/base_command.py +182 -180
- tree_sitter_analyzer/cli/commands/structure_command.py +138 -138
- tree_sitter_analyzer/cli/commands/summary_command.py +101 -101
- tree_sitter_analyzer/core/__init__.py +15 -15
- tree_sitter_analyzer/core/analysis_engine.py +74 -78
- tree_sitter_analyzer/core/cache_service.py +320 -320
- tree_sitter_analyzer/core/engine.py +566 -566
- tree_sitter_analyzer/core/parser.py +293 -293
- tree_sitter_analyzer/encoding_utils.py +459 -459
- tree_sitter_analyzer/file_handler.py +210 -210
- tree_sitter_analyzer/formatters/__init__.py +1 -1
- tree_sitter_analyzer/formatters/base_formatter.py +167 -167
- tree_sitter_analyzer/formatters/formatter_factory.py +78 -78
- tree_sitter_analyzer/formatters/java_formatter.py +18 -18
- tree_sitter_analyzer/formatters/python_formatter.py +19 -19
- tree_sitter_analyzer/interfaces/__init__.py +9 -9
- tree_sitter_analyzer/interfaces/cli.py +528 -528
- tree_sitter_analyzer/interfaces/cli_adapter.py +344 -343
- tree_sitter_analyzer/interfaces/mcp_adapter.py +206 -206
- tree_sitter_analyzer/language_detector.py +53 -53
- tree_sitter_analyzer/languages/__init__.py +10 -10
- tree_sitter_analyzer/languages/java_plugin.py +1 -1
- tree_sitter_analyzer/languages/javascript_plugin.py +446 -446
- tree_sitter_analyzer/languages/python_plugin.py +755 -755
- tree_sitter_analyzer/mcp/__init__.py +34 -31
- tree_sitter_analyzer/mcp/resources/__init__.py +44 -44
- tree_sitter_analyzer/mcp/resources/code_file_resource.py +209 -209
- tree_sitter_analyzer/mcp/server.py +623 -436
- tree_sitter_analyzer/mcp/tools/__init__.py +30 -30
- tree_sitter_analyzer/mcp/tools/analyze_scale_tool.py +10 -6
- tree_sitter_analyzer/mcp/tools/analyze_scale_tool_cli_compatible.py +247 -242
- tree_sitter_analyzer/mcp/tools/base_tool.py +54 -54
- tree_sitter_analyzer/mcp/tools/read_partial_tool.py +310 -308
- tree_sitter_analyzer/mcp/tools/table_format_tool.py +386 -379
- tree_sitter_analyzer/mcp/tools/universal_analyze_tool.py +563 -559
- tree_sitter_analyzer/mcp/utils/__init__.py +107 -107
- tree_sitter_analyzer/models.py +10 -10
- tree_sitter_analyzer/output_manager.py +253 -253
- tree_sitter_analyzer/plugins/__init__.py +280 -280
- tree_sitter_analyzer/plugins/base.py +529 -529
- tree_sitter_analyzer/plugins/manager.py +379 -379
- tree_sitter_analyzer/queries/__init__.py +26 -26
- tree_sitter_analyzer/queries/java.py +391 -391
- tree_sitter_analyzer/queries/javascript.py +148 -148
- tree_sitter_analyzer/queries/python.py +285 -285
- tree_sitter_analyzer/queries/typescript.py +229 -229
- tree_sitter_analyzer/query_loader.py +257 -257
- tree_sitter_analyzer/security/boundary_manager.py +237 -279
- tree_sitter_analyzer/security/validator.py +60 -58
- tree_sitter_analyzer/utils.py +294 -277
- {tree_sitter_analyzer-0.8.3.dist-info → tree_sitter_analyzer-0.9.2.dist-info}/METADATA +28 -19
- tree_sitter_analyzer-0.9.2.dist-info/RECORD +77 -0
- {tree_sitter_analyzer-0.8.3.dist-info → tree_sitter_analyzer-0.9.2.dist-info}/entry_points.txt +1 -0
- tree_sitter_analyzer-0.8.3.dist-info/RECORD +0 -77
- {tree_sitter_analyzer-0.8.3.dist-info → tree_sitter_analyzer-0.9.2.dist-info}/WHEEL +0 -0
tree_sitter_analyzer/api.py
CHANGED
|
@@ -1,533 +1,533 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""
|
|
3
|
-
Tree-sitter Analyzer API
|
|
4
|
-
|
|
5
|
-
Public API facade that provides a stable, high-level interface to the
|
|
6
|
-
tree-sitter analyzer framework. This is the main entry point for both
|
|
7
|
-
CLI and MCP interfaces.
|
|
8
|
-
"""
|
|
9
|
-
|
|
10
|
-
import logging
|
|
11
|
-
from pathlib import Path
|
|
12
|
-
from typing import Any
|
|
13
|
-
|
|
14
|
-
from .core.engine import AnalysisEngine
|
|
15
|
-
from .utils import log_error
|
|
16
|
-
|
|
17
|
-
logger = logging.getLogger(__name__)
|
|
18
|
-
|
|
19
|
-
# Global engine instance (singleton pattern)
|
|
20
|
-
_engine: AnalysisEngine | None = None
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
def get_engine() -> AnalysisEngine:
|
|
24
|
-
"""
|
|
25
|
-
Get the global analysis engine instance.
|
|
26
|
-
|
|
27
|
-
Returns:
|
|
28
|
-
AnalysisEngine instance
|
|
29
|
-
"""
|
|
30
|
-
global _engine
|
|
31
|
-
if _engine is None:
|
|
32
|
-
_engine = AnalysisEngine()
|
|
33
|
-
return _engine
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
def analyze_file(
|
|
37
|
-
file_path: str | Path,
|
|
38
|
-
language: str | None = None,
|
|
39
|
-
queries: list[str] | None = None,
|
|
40
|
-
include_elements: bool = True,
|
|
41
|
-
include_details: bool = False, # Add for backward compatibility
|
|
42
|
-
include_queries: bool = True,
|
|
43
|
-
include_complexity: bool = False, # Add for backward compatibility
|
|
44
|
-
) -> dict[str, Any]:
|
|
45
|
-
"""
|
|
46
|
-
Analyze a source code file.
|
|
47
|
-
|
|
48
|
-
This is the main high-level function for file analysis. It handles
|
|
49
|
-
language detection, parsing, query execution, and element extraction.
|
|
50
|
-
|
|
51
|
-
Args:
|
|
52
|
-
file_path: Path to the source file to analyze
|
|
53
|
-
language: Programming language (auto-detected if not specified)
|
|
54
|
-
queries: List of query names to execute (all available if not specified)
|
|
55
|
-
include_elements: Whether to extract code elements
|
|
56
|
-
include_queries: Whether to execute queries
|
|
57
|
-
include_complexity: Whether to include complexity metrics (backward compatibility)
|
|
58
|
-
|
|
59
|
-
Returns:
|
|
60
|
-
Analysis results dictionary containing:
|
|
61
|
-
- success: Whether the analysis was successful
|
|
62
|
-
- file_info: Basic file information
|
|
63
|
-
- language_info: Detected/specified language information
|
|
64
|
-
- ast_info: Abstract syntax tree information
|
|
65
|
-
- query_results: Results from executed queries (if include_queries=True)
|
|
66
|
-
- elements: Extracted code elements (if include_elements=True)
|
|
67
|
-
- error: Error message (if success=False)
|
|
68
|
-
"""
|
|
69
|
-
try:
|
|
70
|
-
engine = get_engine()
|
|
71
|
-
|
|
72
|
-
# Perform the analysis
|
|
73
|
-
analysis_result = engine.analyze_file(file_path, language)
|
|
74
|
-
|
|
75
|
-
# Convert AnalysisResult to expected API format
|
|
76
|
-
result = {
|
|
77
|
-
"success": analysis_result.success,
|
|
78
|
-
"file_info": {
|
|
79
|
-
"path": str(file_path),
|
|
80
|
-
"exists": Path(file_path).exists(),
|
|
81
|
-
"size": (
|
|
82
|
-
Path(file_path).stat().st_size if Path(file_path).exists() else 0
|
|
83
|
-
),
|
|
84
|
-
},
|
|
85
|
-
"language_info": {
|
|
86
|
-
"language": analysis_result.language,
|
|
87
|
-
"detected": language is None, # True if language was auto-detected
|
|
88
|
-
},
|
|
89
|
-
"ast_info": {
|
|
90
|
-
"node_count": analysis_result.node_count,
|
|
91
|
-
"line_count": analysis_result.line_count,
|
|
92
|
-
},
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
# Add elements if requested and available
|
|
96
|
-
if include_elements and hasattr(analysis_result, "elements"):
|
|
97
|
-
result["elements"] = [
|
|
98
|
-
{
|
|
99
|
-
"name": elem.name,
|
|
100
|
-
"type": type(elem).__name__.lower(),
|
|
101
|
-
"start_line": elem.start_line,
|
|
102
|
-
"end_line": elem.end_line,
|
|
103
|
-
"raw_text": elem.raw_text,
|
|
104
|
-
"language": elem.language,
|
|
105
|
-
}
|
|
106
|
-
for elem in analysis_result.elements
|
|
107
|
-
]
|
|
108
|
-
|
|
109
|
-
# Add query results if requested and available
|
|
110
|
-
if include_queries and hasattr(analysis_result, "query_results"):
|
|
111
|
-
result["query_results"] = analysis_result.query_results
|
|
112
|
-
|
|
113
|
-
# Add error message if analysis failed
|
|
114
|
-
if not analysis_result.success and analysis_result.error_message:
|
|
115
|
-
result["error"] = analysis_result.error_message
|
|
116
|
-
|
|
117
|
-
# Filter results based on options
|
|
118
|
-
if not include_elements and "elements" in result:
|
|
119
|
-
del result["elements"]
|
|
120
|
-
|
|
121
|
-
if not include_queries and "query_results" in result:
|
|
122
|
-
del result["query_results"]
|
|
123
|
-
|
|
124
|
-
return result
|
|
125
|
-
|
|
126
|
-
except FileNotFoundError as e:
|
|
127
|
-
# Re-raise FileNotFoundError for tests that expect it
|
|
128
|
-
raise e
|
|
129
|
-
except Exception as e:
|
|
130
|
-
log_error(f"API analyze_file failed: {e}")
|
|
131
|
-
return {
|
|
132
|
-
"success": False,
|
|
133
|
-
"error": str(e),
|
|
134
|
-
"file_info": {"path": str(file_path), "exists": Path(file_path).exists()},
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
def analyze_code(
|
|
139
|
-
source_code: str,
|
|
140
|
-
language: str,
|
|
141
|
-
queries: list[str] | None = None,
|
|
142
|
-
include_elements: bool = True,
|
|
143
|
-
include_queries: bool = True,
|
|
144
|
-
) -> dict[str, Any]:
|
|
145
|
-
"""
|
|
146
|
-
Analyze source code directly (without file).
|
|
147
|
-
|
|
148
|
-
Args:
|
|
149
|
-
source_code: Source code string to analyze
|
|
150
|
-
language: Programming language
|
|
151
|
-
queries: List of query names to execute (all available if not specified)
|
|
152
|
-
include_elements: Whether to extract code elements
|
|
153
|
-
include_queries: Whether to execute queries
|
|
154
|
-
|
|
155
|
-
Returns:
|
|
156
|
-
Analysis results dictionary
|
|
157
|
-
"""
|
|
158
|
-
try:
|
|
159
|
-
engine = get_engine()
|
|
160
|
-
|
|
161
|
-
# Perform the analysis
|
|
162
|
-
analysis_result = engine.analyze_code(source_code, language)
|
|
163
|
-
|
|
164
|
-
# Convert AnalysisResult to expected API format
|
|
165
|
-
result = {
|
|
166
|
-
"success": analysis_result.success,
|
|
167
|
-
"language_info": {
|
|
168
|
-
"language": analysis_result.language,
|
|
169
|
-
"detected": False, # Language was explicitly provided
|
|
170
|
-
},
|
|
171
|
-
"ast_info": {
|
|
172
|
-
"node_count": analysis_result.node_count,
|
|
173
|
-
"line_count": analysis_result.line_count,
|
|
174
|
-
},
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
# Add elements if requested and available
|
|
178
|
-
if include_elements and hasattr(analysis_result, "elements"):
|
|
179
|
-
result["elements"] = [
|
|
180
|
-
{
|
|
181
|
-
"name": elem.name,
|
|
182
|
-
"type": type(elem).__name__.lower(),
|
|
183
|
-
"start_line": elem.start_line,
|
|
184
|
-
"end_line": elem.end_line,
|
|
185
|
-
"raw_text": elem.raw_text,
|
|
186
|
-
"language": elem.language,
|
|
187
|
-
}
|
|
188
|
-
for elem in analysis_result.elements
|
|
189
|
-
]
|
|
190
|
-
|
|
191
|
-
# Add query results if requested and available
|
|
192
|
-
if include_queries and hasattr(analysis_result, "query_results"):
|
|
193
|
-
result["query_results"] = analysis_result.query_results
|
|
194
|
-
|
|
195
|
-
# Add error message if analysis failed
|
|
196
|
-
if not analysis_result.success and analysis_result.error_message:
|
|
197
|
-
result["error"] = analysis_result.error_message
|
|
198
|
-
|
|
199
|
-
# Filter results based on options
|
|
200
|
-
if not include_elements and "elements" in result:
|
|
201
|
-
del result["elements"]
|
|
202
|
-
|
|
203
|
-
if not include_queries and "query_results" in result:
|
|
204
|
-
del result["query_results"]
|
|
205
|
-
|
|
206
|
-
return result
|
|
207
|
-
|
|
208
|
-
except Exception as e:
|
|
209
|
-
log_error(f"API analyze_code failed: {e}")
|
|
210
|
-
return {"success": False, "error": str(e)}
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
def get_supported_languages() -> list[str]:
|
|
214
|
-
"""
|
|
215
|
-
Get list of all supported programming languages.
|
|
216
|
-
|
|
217
|
-
Returns:
|
|
218
|
-
List of supported language names
|
|
219
|
-
"""
|
|
220
|
-
try:
|
|
221
|
-
engine = get_engine()
|
|
222
|
-
return engine.get_supported_languages()
|
|
223
|
-
except Exception as e:
|
|
224
|
-
log_error(f"Failed to get supported languages: {e}")
|
|
225
|
-
return []
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
def get_available_queries(language: str) -> list[str]:
|
|
229
|
-
"""
|
|
230
|
-
Get available queries for a specific language.
|
|
231
|
-
|
|
232
|
-
Args:
|
|
233
|
-
language: Programming language name
|
|
234
|
-
|
|
235
|
-
Returns:
|
|
236
|
-
List of available query names
|
|
237
|
-
"""
|
|
238
|
-
try:
|
|
239
|
-
engine = get_engine()
|
|
240
|
-
# Try to get plugin and its supported queries
|
|
241
|
-
plugin = engine._get_language_plugin(language)
|
|
242
|
-
if plugin and hasattr(plugin, "get_supported_queries"):
|
|
243
|
-
result = plugin.get_supported_queries()
|
|
244
|
-
return list(result) if result else []
|
|
245
|
-
else:
|
|
246
|
-
# Return default queries
|
|
247
|
-
return ["class", "method", "field"]
|
|
248
|
-
except Exception as e:
|
|
249
|
-
log_error(f"Failed to get available queries for {language}: {e}")
|
|
250
|
-
return []
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
def is_language_supported(language: str) -> bool:
|
|
254
|
-
"""
|
|
255
|
-
Check if a programming language is supported.
|
|
256
|
-
|
|
257
|
-
Args:
|
|
258
|
-
language: Programming language name
|
|
259
|
-
|
|
260
|
-
Returns:
|
|
261
|
-
True if the language is supported
|
|
262
|
-
"""
|
|
263
|
-
try:
|
|
264
|
-
supported_languages = get_supported_languages()
|
|
265
|
-
return language.lower() in [lang.lower() for lang in supported_languages]
|
|
266
|
-
except Exception as e:
|
|
267
|
-
log_error(f"Failed to check language support for {language}: {e}")
|
|
268
|
-
return False
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
def detect_language(file_path: str | Path) -> str | None:
|
|
272
|
-
"""
|
|
273
|
-
Detect programming language from file path.
|
|
274
|
-
|
|
275
|
-
Args:
|
|
276
|
-
file_path: Path to the file
|
|
277
|
-
|
|
278
|
-
Returns:
|
|
279
|
-
Detected language name or None
|
|
280
|
-
"""
|
|
281
|
-
try:
|
|
282
|
-
engine = get_engine()
|
|
283
|
-
# Use language_detector instead of language_registry
|
|
284
|
-
return engine.language_detector.detect_from_extension(str(file_path))
|
|
285
|
-
except Exception as e:
|
|
286
|
-
log_error(f"Failed to detect language for {file_path}: {e}")
|
|
287
|
-
return None
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
def get_file_extensions(language: str) -> list[str]:
|
|
291
|
-
"""
|
|
292
|
-
Get file extensions for a specific language.
|
|
293
|
-
|
|
294
|
-
Args:
|
|
295
|
-
language: Programming language name
|
|
296
|
-
|
|
297
|
-
Returns:
|
|
298
|
-
List of file extensions
|
|
299
|
-
"""
|
|
300
|
-
try:
|
|
301
|
-
engine = get_engine()
|
|
302
|
-
# Use language_detector to get extensions
|
|
303
|
-
if hasattr(engine.language_detector, "get_extensions_for_language"):
|
|
304
|
-
result = engine.language_detector.get_extensions_for_language(language)
|
|
305
|
-
return list(result) if result else []
|
|
306
|
-
else:
|
|
307
|
-
# Fallback: return common extensions
|
|
308
|
-
extension_map = {
|
|
309
|
-
"java": [".java"],
|
|
310
|
-
"python": [".py"],
|
|
311
|
-
"javascript": [".js"],
|
|
312
|
-
"typescript": [".ts"],
|
|
313
|
-
"c": [".c"],
|
|
314
|
-
"cpp": [".cpp", ".cxx", ".cc"],
|
|
315
|
-
"go": [".go"],
|
|
316
|
-
"rust": [".rs"],
|
|
317
|
-
}
|
|
318
|
-
return extension_map.get(language.lower(), [])
|
|
319
|
-
except Exception as e:
|
|
320
|
-
log_error(f"Failed to get extensions for {language}: {e}")
|
|
321
|
-
return []
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
def validate_file(file_path: str | Path) -> dict[str, Any]:
|
|
325
|
-
"""
|
|
326
|
-
Validate a source code file without full analysis.
|
|
327
|
-
|
|
328
|
-
Args:
|
|
329
|
-
file_path: Path to the file to validate
|
|
330
|
-
|
|
331
|
-
Returns:
|
|
332
|
-
Validation results dictionary
|
|
333
|
-
"""
|
|
334
|
-
file_path = Path(file_path)
|
|
335
|
-
|
|
336
|
-
result: dict[str, Any] = {
|
|
337
|
-
"valid": False,
|
|
338
|
-
"exists": file_path.exists(),
|
|
339
|
-
"readable": False,
|
|
340
|
-
"language": None,
|
|
341
|
-
"supported": False,
|
|
342
|
-
"size": 0,
|
|
343
|
-
"errors": [],
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
try:
|
|
347
|
-
# Check if file exists
|
|
348
|
-
if not file_path.exists():
|
|
349
|
-
result["errors"].append("File does not exist")
|
|
350
|
-
return result
|
|
351
|
-
|
|
352
|
-
# Check if file is readable
|
|
353
|
-
try:
|
|
354
|
-
with open(file_path, encoding="utf-8") as f:
|
|
355
|
-
f.read(100) # Read first 100 chars to test
|
|
356
|
-
result["readable"] = True
|
|
357
|
-
result["size"] = file_path.stat().st_size
|
|
358
|
-
except Exception as e:
|
|
359
|
-
result["errors"].append(f"File is not readable: {e}")
|
|
360
|
-
return result
|
|
361
|
-
|
|
362
|
-
# Detect language
|
|
363
|
-
language = detect_language(file_path)
|
|
364
|
-
result["language"] = language
|
|
365
|
-
|
|
366
|
-
if language:
|
|
367
|
-
result["supported"] = is_language_supported(language)
|
|
368
|
-
if not result["supported"]:
|
|
369
|
-
result["errors"].append(f"Language '{language}' is not supported")
|
|
370
|
-
else:
|
|
371
|
-
result["errors"].append("Could not detect programming language")
|
|
372
|
-
|
|
373
|
-
# If we got this far with no errors, the file is valid
|
|
374
|
-
result["valid"] = len(result["errors"]) == 0
|
|
375
|
-
|
|
376
|
-
except Exception as e:
|
|
377
|
-
result["errors"].append(f"Validation failed: {e}")
|
|
378
|
-
|
|
379
|
-
return result
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
def get_framework_info() -> dict[str, Any]:
|
|
383
|
-
"""
|
|
384
|
-
Get information about the framework and its capabilities.
|
|
385
|
-
|
|
386
|
-
Returns:
|
|
387
|
-
Framework information dictionary
|
|
388
|
-
"""
|
|
389
|
-
try:
|
|
390
|
-
engine = get_engine()
|
|
391
|
-
|
|
392
|
-
return {
|
|
393
|
-
"name": "tree-sitter-analyzer",
|
|
394
|
-
"version": "2.0.0", # New architecture version
|
|
395
|
-
"supported_languages": engine.get_supported_languages(),
|
|
396
|
-
"total_languages": len(engine.get_supported_languages()),
|
|
397
|
-
"plugin_info": {
|
|
398
|
-
"manager_available": engine.plugin_manager is not None,
|
|
399
|
-
"loaded_plugins": (
|
|
400
|
-
len(engine.plugin_manager.get_supported_languages())
|
|
401
|
-
if engine.plugin_manager
|
|
402
|
-
else 0
|
|
403
|
-
),
|
|
404
|
-
},
|
|
405
|
-
"core_components": [
|
|
406
|
-
"AnalysisEngine",
|
|
407
|
-
"Parser",
|
|
408
|
-
"QueryExecutor",
|
|
409
|
-
"PluginManager",
|
|
410
|
-
"LanguageDetector",
|
|
411
|
-
],
|
|
412
|
-
}
|
|
413
|
-
except Exception as e:
|
|
414
|
-
log_error(f"Failed to get framework info: {e}")
|
|
415
|
-
return {"name": "tree-sitter-analyzer", "version": "2.0.0", "error": str(e)}
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
def execute_query(
|
|
419
|
-
file_path: str | Path, query_name: str, language: str | None = None
|
|
420
|
-
) -> dict[str, Any]:
|
|
421
|
-
"""
|
|
422
|
-
Execute a specific query against a file.
|
|
423
|
-
|
|
424
|
-
Args:
|
|
425
|
-
file_path: Path to the source file
|
|
426
|
-
query_name: Name of the query to execute
|
|
427
|
-
language: Programming language (auto-detected if not specified)
|
|
428
|
-
|
|
429
|
-
Returns:
|
|
430
|
-
Query execution results
|
|
431
|
-
"""
|
|
432
|
-
try:
|
|
433
|
-
# Analyze with only the specified query
|
|
434
|
-
result = analyze_file(
|
|
435
|
-
file_path,
|
|
436
|
-
language=language,
|
|
437
|
-
queries=[query_name],
|
|
438
|
-
include_elements=False,
|
|
439
|
-
include_queries=True,
|
|
440
|
-
)
|
|
441
|
-
|
|
442
|
-
if result["success"] and "query_results" in result:
|
|
443
|
-
query_results = result["query_results"].get(query_name, [])
|
|
444
|
-
return {
|
|
445
|
-
"success": True,
|
|
446
|
-
"query_name": query_name,
|
|
447
|
-
"results": query_results,
|
|
448
|
-
"count": len(query_results),
|
|
449
|
-
"language": result.get("language_info", {}).get("language"),
|
|
450
|
-
"file_path": str(file_path),
|
|
451
|
-
}
|
|
452
|
-
else:
|
|
453
|
-
return {
|
|
454
|
-
"success": False,
|
|
455
|
-
"query_name": query_name,
|
|
456
|
-
"error": result.get("error", "Unknown error"),
|
|
457
|
-
"file_path": str(file_path),
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
except Exception as e:
|
|
461
|
-
log_error(f"Query execution failed: {e}")
|
|
462
|
-
return {
|
|
463
|
-
"success": False,
|
|
464
|
-
"query_name": query_name,
|
|
465
|
-
"error": str(e),
|
|
466
|
-
"file_path": str(file_path),
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
def extract_elements(
|
|
471
|
-
file_path: str | Path,
|
|
472
|
-
language: str | None = None,
|
|
473
|
-
element_types: list[str] | None = None,
|
|
474
|
-
) -> dict[str, Any]:
|
|
475
|
-
"""
|
|
476
|
-
Extract code elements from a file.
|
|
477
|
-
|
|
478
|
-
Args:
|
|
479
|
-
file_path: Path to the source file
|
|
480
|
-
language: Programming language (auto-detected if not specified)
|
|
481
|
-
element_types: Types of elements to extract (all if not specified)
|
|
482
|
-
|
|
483
|
-
Returns:
|
|
484
|
-
Element extraction results
|
|
485
|
-
"""
|
|
486
|
-
try:
|
|
487
|
-
# Analyze with only element extraction
|
|
488
|
-
result = analyze_file(
|
|
489
|
-
file_path, language=language, include_elements=True, include_queries=False
|
|
490
|
-
)
|
|
491
|
-
|
|
492
|
-
if result["success"] and "elements" in result:
|
|
493
|
-
elements = result["elements"]
|
|
494
|
-
|
|
495
|
-
# Filter by element types if specified
|
|
496
|
-
if element_types:
|
|
497
|
-
filtered_elements = []
|
|
498
|
-
for element in elements:
|
|
499
|
-
if any(
|
|
500
|
-
etype.lower() in element.get("type", "").lower()
|
|
501
|
-
for etype in element_types
|
|
502
|
-
):
|
|
503
|
-
filtered_elements.append(element)
|
|
504
|
-
elements = filtered_elements
|
|
505
|
-
|
|
506
|
-
return {
|
|
507
|
-
"success": True,
|
|
508
|
-
"elements": elements,
|
|
509
|
-
"count": len(elements),
|
|
510
|
-
"language": result.get("language_info", {}).get("language"),
|
|
511
|
-
"file_path": str(file_path),
|
|
512
|
-
}
|
|
513
|
-
else:
|
|
514
|
-
return {
|
|
515
|
-
"success": False,
|
|
516
|
-
"error": result.get("error", "Unknown error"),
|
|
517
|
-
"file_path": str(file_path),
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
except Exception as e:
|
|
521
|
-
log_error(f"Element extraction failed: {e}")
|
|
522
|
-
return {"success": False, "error": str(e), "file_path": str(file_path)}
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
# Convenience functions for backward compatibility
|
|
526
|
-
def analyze(file_path: str | Path, **kwargs: Any) -> dict[str, Any]:
|
|
527
|
-
"""Convenience function that aliases to analyze_file."""
|
|
528
|
-
return analyze_file(file_path, **kwargs)
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
def get_languages() -> list[str]:
|
|
532
|
-
"""Convenience function that aliases to get_supported_languages."""
|
|
533
|
-
return get_supported_languages()
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Tree-sitter Analyzer API
|
|
4
|
+
|
|
5
|
+
Public API facade that provides a stable, high-level interface to the
|
|
6
|
+
tree-sitter analyzer framework. This is the main entry point for both
|
|
7
|
+
CLI and MCP interfaces.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import logging
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
from .core.engine import AnalysisEngine
|
|
15
|
+
from .utils import log_error
|
|
16
|
+
|
|
17
|
+
logger = logging.getLogger(__name__)
|
|
18
|
+
|
|
19
|
+
# Global engine instance (singleton pattern)
|
|
20
|
+
_engine: AnalysisEngine | None = None
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def get_engine() -> AnalysisEngine:
|
|
24
|
+
"""
|
|
25
|
+
Get the global analysis engine instance.
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
AnalysisEngine instance
|
|
29
|
+
"""
|
|
30
|
+
global _engine
|
|
31
|
+
if _engine is None:
|
|
32
|
+
_engine = AnalysisEngine()
|
|
33
|
+
return _engine
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def analyze_file(
|
|
37
|
+
file_path: str | Path,
|
|
38
|
+
language: str | None = None,
|
|
39
|
+
queries: list[str] | None = None,
|
|
40
|
+
include_elements: bool = True,
|
|
41
|
+
include_details: bool = False, # Add for backward compatibility
|
|
42
|
+
include_queries: bool = True,
|
|
43
|
+
include_complexity: bool = False, # Add for backward compatibility
|
|
44
|
+
) -> dict[str, Any]:
|
|
45
|
+
"""
|
|
46
|
+
Analyze a source code file.
|
|
47
|
+
|
|
48
|
+
This is the main high-level function for file analysis. It handles
|
|
49
|
+
language detection, parsing, query execution, and element extraction.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
file_path: Path to the source file to analyze
|
|
53
|
+
language: Programming language (auto-detected if not specified)
|
|
54
|
+
queries: List of query names to execute (all available if not specified)
|
|
55
|
+
include_elements: Whether to extract code elements
|
|
56
|
+
include_queries: Whether to execute queries
|
|
57
|
+
include_complexity: Whether to include complexity metrics (backward compatibility)
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
Analysis results dictionary containing:
|
|
61
|
+
- success: Whether the analysis was successful
|
|
62
|
+
- file_info: Basic file information
|
|
63
|
+
- language_info: Detected/specified language information
|
|
64
|
+
- ast_info: Abstract syntax tree information
|
|
65
|
+
- query_results: Results from executed queries (if include_queries=True)
|
|
66
|
+
- elements: Extracted code elements (if include_elements=True)
|
|
67
|
+
- error: Error message (if success=False)
|
|
68
|
+
"""
|
|
69
|
+
try:
|
|
70
|
+
engine = get_engine()
|
|
71
|
+
|
|
72
|
+
# Perform the analysis
|
|
73
|
+
analysis_result = engine.analyze_file(file_path, language)
|
|
74
|
+
|
|
75
|
+
# Convert AnalysisResult to expected API format
|
|
76
|
+
result = {
|
|
77
|
+
"success": analysis_result.success,
|
|
78
|
+
"file_info": {
|
|
79
|
+
"path": str(file_path),
|
|
80
|
+
"exists": Path(file_path).exists(),
|
|
81
|
+
"size": (
|
|
82
|
+
Path(file_path).stat().st_size if Path(file_path).exists() else 0
|
|
83
|
+
),
|
|
84
|
+
},
|
|
85
|
+
"language_info": {
|
|
86
|
+
"language": analysis_result.language,
|
|
87
|
+
"detected": language is None, # True if language was auto-detected
|
|
88
|
+
},
|
|
89
|
+
"ast_info": {
|
|
90
|
+
"node_count": analysis_result.node_count,
|
|
91
|
+
"line_count": analysis_result.line_count,
|
|
92
|
+
},
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
# Add elements if requested and available
|
|
96
|
+
if include_elements and hasattr(analysis_result, "elements"):
|
|
97
|
+
result["elements"] = [
|
|
98
|
+
{
|
|
99
|
+
"name": elem.name,
|
|
100
|
+
"type": type(elem).__name__.lower(),
|
|
101
|
+
"start_line": elem.start_line,
|
|
102
|
+
"end_line": elem.end_line,
|
|
103
|
+
"raw_text": elem.raw_text,
|
|
104
|
+
"language": elem.language,
|
|
105
|
+
}
|
|
106
|
+
for elem in analysis_result.elements
|
|
107
|
+
]
|
|
108
|
+
|
|
109
|
+
# Add query results if requested and available
|
|
110
|
+
if include_queries and hasattr(analysis_result, "query_results"):
|
|
111
|
+
result["query_results"] = analysis_result.query_results
|
|
112
|
+
|
|
113
|
+
# Add error message if analysis failed
|
|
114
|
+
if not analysis_result.success and analysis_result.error_message:
|
|
115
|
+
result["error"] = analysis_result.error_message
|
|
116
|
+
|
|
117
|
+
# Filter results based on options
|
|
118
|
+
if not include_elements and "elements" in result:
|
|
119
|
+
del result["elements"]
|
|
120
|
+
|
|
121
|
+
if not include_queries and "query_results" in result:
|
|
122
|
+
del result["query_results"]
|
|
123
|
+
|
|
124
|
+
return result
|
|
125
|
+
|
|
126
|
+
except FileNotFoundError as e:
|
|
127
|
+
# Re-raise FileNotFoundError for tests that expect it
|
|
128
|
+
raise e
|
|
129
|
+
except Exception as e:
|
|
130
|
+
log_error(f"API analyze_file failed: {e}")
|
|
131
|
+
return {
|
|
132
|
+
"success": False,
|
|
133
|
+
"error": str(e),
|
|
134
|
+
"file_info": {"path": str(file_path), "exists": Path(file_path).exists()},
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def analyze_code(
|
|
139
|
+
source_code: str,
|
|
140
|
+
language: str,
|
|
141
|
+
queries: list[str] | None = None,
|
|
142
|
+
include_elements: bool = True,
|
|
143
|
+
include_queries: bool = True,
|
|
144
|
+
) -> dict[str, Any]:
|
|
145
|
+
"""
|
|
146
|
+
Analyze source code directly (without file).
|
|
147
|
+
|
|
148
|
+
Args:
|
|
149
|
+
source_code: Source code string to analyze
|
|
150
|
+
language: Programming language
|
|
151
|
+
queries: List of query names to execute (all available if not specified)
|
|
152
|
+
include_elements: Whether to extract code elements
|
|
153
|
+
include_queries: Whether to execute queries
|
|
154
|
+
|
|
155
|
+
Returns:
|
|
156
|
+
Analysis results dictionary
|
|
157
|
+
"""
|
|
158
|
+
try:
|
|
159
|
+
engine = get_engine()
|
|
160
|
+
|
|
161
|
+
# Perform the analysis
|
|
162
|
+
analysis_result = engine.analyze_code(source_code, language)
|
|
163
|
+
|
|
164
|
+
# Convert AnalysisResult to expected API format
|
|
165
|
+
result = {
|
|
166
|
+
"success": analysis_result.success,
|
|
167
|
+
"language_info": {
|
|
168
|
+
"language": analysis_result.language,
|
|
169
|
+
"detected": False, # Language was explicitly provided
|
|
170
|
+
},
|
|
171
|
+
"ast_info": {
|
|
172
|
+
"node_count": analysis_result.node_count,
|
|
173
|
+
"line_count": analysis_result.line_count,
|
|
174
|
+
},
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
# Add elements if requested and available
|
|
178
|
+
if include_elements and hasattr(analysis_result, "elements"):
|
|
179
|
+
result["elements"] = [
|
|
180
|
+
{
|
|
181
|
+
"name": elem.name,
|
|
182
|
+
"type": type(elem).__name__.lower(),
|
|
183
|
+
"start_line": elem.start_line,
|
|
184
|
+
"end_line": elem.end_line,
|
|
185
|
+
"raw_text": elem.raw_text,
|
|
186
|
+
"language": elem.language,
|
|
187
|
+
}
|
|
188
|
+
for elem in analysis_result.elements
|
|
189
|
+
]
|
|
190
|
+
|
|
191
|
+
# Add query results if requested and available
|
|
192
|
+
if include_queries and hasattr(analysis_result, "query_results"):
|
|
193
|
+
result["query_results"] = analysis_result.query_results
|
|
194
|
+
|
|
195
|
+
# Add error message if analysis failed
|
|
196
|
+
if not analysis_result.success and analysis_result.error_message:
|
|
197
|
+
result["error"] = analysis_result.error_message
|
|
198
|
+
|
|
199
|
+
# Filter results based on options
|
|
200
|
+
if not include_elements and "elements" in result:
|
|
201
|
+
del result["elements"]
|
|
202
|
+
|
|
203
|
+
if not include_queries and "query_results" in result:
|
|
204
|
+
del result["query_results"]
|
|
205
|
+
|
|
206
|
+
return result
|
|
207
|
+
|
|
208
|
+
except Exception as e:
|
|
209
|
+
log_error(f"API analyze_code failed: {e}")
|
|
210
|
+
return {"success": False, "error": str(e)}
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def get_supported_languages() -> list[str]:
|
|
214
|
+
"""
|
|
215
|
+
Get list of all supported programming languages.
|
|
216
|
+
|
|
217
|
+
Returns:
|
|
218
|
+
List of supported language names
|
|
219
|
+
"""
|
|
220
|
+
try:
|
|
221
|
+
engine = get_engine()
|
|
222
|
+
return engine.get_supported_languages()
|
|
223
|
+
except Exception as e:
|
|
224
|
+
log_error(f"Failed to get supported languages: {e}")
|
|
225
|
+
return []
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def get_available_queries(language: str) -> list[str]:
|
|
229
|
+
"""
|
|
230
|
+
Get available queries for a specific language.
|
|
231
|
+
|
|
232
|
+
Args:
|
|
233
|
+
language: Programming language name
|
|
234
|
+
|
|
235
|
+
Returns:
|
|
236
|
+
List of available query names
|
|
237
|
+
"""
|
|
238
|
+
try:
|
|
239
|
+
engine = get_engine()
|
|
240
|
+
# Try to get plugin and its supported queries
|
|
241
|
+
plugin = engine._get_language_plugin(language)
|
|
242
|
+
if plugin and hasattr(plugin, "get_supported_queries"):
|
|
243
|
+
result = plugin.get_supported_queries()
|
|
244
|
+
return list(result) if result else []
|
|
245
|
+
else:
|
|
246
|
+
# Return default queries
|
|
247
|
+
return ["class", "method", "field"]
|
|
248
|
+
except Exception as e:
|
|
249
|
+
log_error(f"Failed to get available queries for {language}: {e}")
|
|
250
|
+
return []
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def is_language_supported(language: str) -> bool:
|
|
254
|
+
"""
|
|
255
|
+
Check if a programming language is supported.
|
|
256
|
+
|
|
257
|
+
Args:
|
|
258
|
+
language: Programming language name
|
|
259
|
+
|
|
260
|
+
Returns:
|
|
261
|
+
True if the language is supported
|
|
262
|
+
"""
|
|
263
|
+
try:
|
|
264
|
+
supported_languages = get_supported_languages()
|
|
265
|
+
return language.lower() in [lang.lower() for lang in supported_languages]
|
|
266
|
+
except Exception as e:
|
|
267
|
+
log_error(f"Failed to check language support for {language}: {e}")
|
|
268
|
+
return False
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
def detect_language(file_path: str | Path) -> str | None:
|
|
272
|
+
"""
|
|
273
|
+
Detect programming language from file path.
|
|
274
|
+
|
|
275
|
+
Args:
|
|
276
|
+
file_path: Path to the file
|
|
277
|
+
|
|
278
|
+
Returns:
|
|
279
|
+
Detected language name or None
|
|
280
|
+
"""
|
|
281
|
+
try:
|
|
282
|
+
engine = get_engine()
|
|
283
|
+
# Use language_detector instead of language_registry
|
|
284
|
+
return engine.language_detector.detect_from_extension(str(file_path))
|
|
285
|
+
except Exception as e:
|
|
286
|
+
log_error(f"Failed to detect language for {file_path}: {e}")
|
|
287
|
+
return None
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
def get_file_extensions(language: str) -> list[str]:
|
|
291
|
+
"""
|
|
292
|
+
Get file extensions for a specific language.
|
|
293
|
+
|
|
294
|
+
Args:
|
|
295
|
+
language: Programming language name
|
|
296
|
+
|
|
297
|
+
Returns:
|
|
298
|
+
List of file extensions
|
|
299
|
+
"""
|
|
300
|
+
try:
|
|
301
|
+
engine = get_engine()
|
|
302
|
+
# Use language_detector to get extensions
|
|
303
|
+
if hasattr(engine.language_detector, "get_extensions_for_language"):
|
|
304
|
+
result = engine.language_detector.get_extensions_for_language(language)
|
|
305
|
+
return list(result) if result else []
|
|
306
|
+
else:
|
|
307
|
+
# Fallback: return common extensions
|
|
308
|
+
extension_map = {
|
|
309
|
+
"java": [".java"],
|
|
310
|
+
"python": [".py"],
|
|
311
|
+
"javascript": [".js"],
|
|
312
|
+
"typescript": [".ts"],
|
|
313
|
+
"c": [".c"],
|
|
314
|
+
"cpp": [".cpp", ".cxx", ".cc"],
|
|
315
|
+
"go": [".go"],
|
|
316
|
+
"rust": [".rs"],
|
|
317
|
+
}
|
|
318
|
+
return extension_map.get(language.lower(), [])
|
|
319
|
+
except Exception as e:
|
|
320
|
+
log_error(f"Failed to get extensions for {language}: {e}")
|
|
321
|
+
return []
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
def validate_file(file_path: str | Path) -> dict[str, Any]:
|
|
325
|
+
"""
|
|
326
|
+
Validate a source code file without full analysis.
|
|
327
|
+
|
|
328
|
+
Args:
|
|
329
|
+
file_path: Path to the file to validate
|
|
330
|
+
|
|
331
|
+
Returns:
|
|
332
|
+
Validation results dictionary
|
|
333
|
+
"""
|
|
334
|
+
file_path = Path(file_path)
|
|
335
|
+
|
|
336
|
+
result: dict[str, Any] = {
|
|
337
|
+
"valid": False,
|
|
338
|
+
"exists": file_path.exists(),
|
|
339
|
+
"readable": False,
|
|
340
|
+
"language": None,
|
|
341
|
+
"supported": False,
|
|
342
|
+
"size": 0,
|
|
343
|
+
"errors": [],
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
try:
|
|
347
|
+
# Check if file exists
|
|
348
|
+
if not file_path.exists():
|
|
349
|
+
result["errors"].append("File does not exist")
|
|
350
|
+
return result
|
|
351
|
+
|
|
352
|
+
# Check if file is readable
|
|
353
|
+
try:
|
|
354
|
+
with open(file_path, encoding="utf-8") as f:
|
|
355
|
+
f.read(100) # Read first 100 chars to test
|
|
356
|
+
result["readable"] = True
|
|
357
|
+
result["size"] = file_path.stat().st_size
|
|
358
|
+
except Exception as e:
|
|
359
|
+
result["errors"].append(f"File is not readable: {e}")
|
|
360
|
+
return result
|
|
361
|
+
|
|
362
|
+
# Detect language
|
|
363
|
+
language = detect_language(file_path)
|
|
364
|
+
result["language"] = language
|
|
365
|
+
|
|
366
|
+
if language:
|
|
367
|
+
result["supported"] = is_language_supported(language)
|
|
368
|
+
if not result["supported"]:
|
|
369
|
+
result["errors"].append(f"Language '{language}' is not supported")
|
|
370
|
+
else:
|
|
371
|
+
result["errors"].append("Could not detect programming language")
|
|
372
|
+
|
|
373
|
+
# If we got this far with no errors, the file is valid
|
|
374
|
+
result["valid"] = len(result["errors"]) == 0
|
|
375
|
+
|
|
376
|
+
except Exception as e:
|
|
377
|
+
result["errors"].append(f"Validation failed: {e}")
|
|
378
|
+
|
|
379
|
+
return result
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
def get_framework_info() -> dict[str, Any]:
|
|
383
|
+
"""
|
|
384
|
+
Get information about the framework and its capabilities.
|
|
385
|
+
|
|
386
|
+
Returns:
|
|
387
|
+
Framework information dictionary
|
|
388
|
+
"""
|
|
389
|
+
try:
|
|
390
|
+
engine = get_engine()
|
|
391
|
+
|
|
392
|
+
return {
|
|
393
|
+
"name": "tree-sitter-analyzer",
|
|
394
|
+
"version": "2.0.0", # New architecture version
|
|
395
|
+
"supported_languages": engine.get_supported_languages(),
|
|
396
|
+
"total_languages": len(engine.get_supported_languages()),
|
|
397
|
+
"plugin_info": {
|
|
398
|
+
"manager_available": engine.plugin_manager is not None,
|
|
399
|
+
"loaded_plugins": (
|
|
400
|
+
len(engine.plugin_manager.get_supported_languages())
|
|
401
|
+
if engine.plugin_manager
|
|
402
|
+
else 0
|
|
403
|
+
),
|
|
404
|
+
},
|
|
405
|
+
"core_components": [
|
|
406
|
+
"AnalysisEngine",
|
|
407
|
+
"Parser",
|
|
408
|
+
"QueryExecutor",
|
|
409
|
+
"PluginManager",
|
|
410
|
+
"LanguageDetector",
|
|
411
|
+
],
|
|
412
|
+
}
|
|
413
|
+
except Exception as e:
|
|
414
|
+
log_error(f"Failed to get framework info: {e}")
|
|
415
|
+
return {"name": "tree-sitter-analyzer", "version": "2.0.0", "error": str(e)}
|
|
416
|
+
|
|
417
|
+
|
|
418
|
+
def execute_query(
|
|
419
|
+
file_path: str | Path, query_name: str, language: str | None = None
|
|
420
|
+
) -> dict[str, Any]:
|
|
421
|
+
"""
|
|
422
|
+
Execute a specific query against a file.
|
|
423
|
+
|
|
424
|
+
Args:
|
|
425
|
+
file_path: Path to the source file
|
|
426
|
+
query_name: Name of the query to execute
|
|
427
|
+
language: Programming language (auto-detected if not specified)
|
|
428
|
+
|
|
429
|
+
Returns:
|
|
430
|
+
Query execution results
|
|
431
|
+
"""
|
|
432
|
+
try:
|
|
433
|
+
# Analyze with only the specified query
|
|
434
|
+
result = analyze_file(
|
|
435
|
+
file_path,
|
|
436
|
+
language=language,
|
|
437
|
+
queries=[query_name],
|
|
438
|
+
include_elements=False,
|
|
439
|
+
include_queries=True,
|
|
440
|
+
)
|
|
441
|
+
|
|
442
|
+
if result["success"] and "query_results" in result:
|
|
443
|
+
query_results = result["query_results"].get(query_name, [])
|
|
444
|
+
return {
|
|
445
|
+
"success": True,
|
|
446
|
+
"query_name": query_name,
|
|
447
|
+
"results": query_results,
|
|
448
|
+
"count": len(query_results),
|
|
449
|
+
"language": result.get("language_info", {}).get("language"),
|
|
450
|
+
"file_path": str(file_path),
|
|
451
|
+
}
|
|
452
|
+
else:
|
|
453
|
+
return {
|
|
454
|
+
"success": False,
|
|
455
|
+
"query_name": query_name,
|
|
456
|
+
"error": result.get("error", "Unknown error"),
|
|
457
|
+
"file_path": str(file_path),
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
except Exception as e:
|
|
461
|
+
log_error(f"Query execution failed: {e}")
|
|
462
|
+
return {
|
|
463
|
+
"success": False,
|
|
464
|
+
"query_name": query_name,
|
|
465
|
+
"error": str(e),
|
|
466
|
+
"file_path": str(file_path),
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
|
|
470
|
+
def extract_elements(
|
|
471
|
+
file_path: str | Path,
|
|
472
|
+
language: str | None = None,
|
|
473
|
+
element_types: list[str] | None = None,
|
|
474
|
+
) -> dict[str, Any]:
|
|
475
|
+
"""
|
|
476
|
+
Extract code elements from a file.
|
|
477
|
+
|
|
478
|
+
Args:
|
|
479
|
+
file_path: Path to the source file
|
|
480
|
+
language: Programming language (auto-detected if not specified)
|
|
481
|
+
element_types: Types of elements to extract (all if not specified)
|
|
482
|
+
|
|
483
|
+
Returns:
|
|
484
|
+
Element extraction results
|
|
485
|
+
"""
|
|
486
|
+
try:
|
|
487
|
+
# Analyze with only element extraction
|
|
488
|
+
result = analyze_file(
|
|
489
|
+
file_path, language=language, include_elements=True, include_queries=False
|
|
490
|
+
)
|
|
491
|
+
|
|
492
|
+
if result["success"] and "elements" in result:
|
|
493
|
+
elements = result["elements"]
|
|
494
|
+
|
|
495
|
+
# Filter by element types if specified
|
|
496
|
+
if element_types:
|
|
497
|
+
filtered_elements = []
|
|
498
|
+
for element in elements:
|
|
499
|
+
if any(
|
|
500
|
+
etype.lower() in element.get("type", "").lower()
|
|
501
|
+
for etype in element_types
|
|
502
|
+
):
|
|
503
|
+
filtered_elements.append(element)
|
|
504
|
+
elements = filtered_elements
|
|
505
|
+
|
|
506
|
+
return {
|
|
507
|
+
"success": True,
|
|
508
|
+
"elements": elements,
|
|
509
|
+
"count": len(elements),
|
|
510
|
+
"language": result.get("language_info", {}).get("language"),
|
|
511
|
+
"file_path": str(file_path),
|
|
512
|
+
}
|
|
513
|
+
else:
|
|
514
|
+
return {
|
|
515
|
+
"success": False,
|
|
516
|
+
"error": result.get("error", "Unknown error"),
|
|
517
|
+
"file_path": str(file_path),
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
except Exception as e:
|
|
521
|
+
log_error(f"Element extraction failed: {e}")
|
|
522
|
+
return {"success": False, "error": str(e), "file_path": str(file_path)}
|
|
523
|
+
|
|
524
|
+
|
|
525
|
+
# Convenience functions for backward compatibility
|
|
526
|
+
def analyze(file_path: str | Path, **kwargs: Any) -> dict[str, Any]:
|
|
527
|
+
"""Convenience function that aliases to analyze_file."""
|
|
528
|
+
return analyze_file(file_path, **kwargs)
|
|
529
|
+
|
|
530
|
+
|
|
531
|
+
def get_languages() -> list[str]:
|
|
532
|
+
"""Convenience function that aliases to get_supported_languages."""
|
|
533
|
+
return get_supported_languages()
|