tree-sitter-analyzer 1.0.0__py3-none-any.whl → 1.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 (29) hide show
  1. tree_sitter_analyzer/__init__.py +132 -132
  2. tree_sitter_analyzer/api.py +542 -542
  3. tree_sitter_analyzer/cli/commands/base_command.py +181 -181
  4. tree_sitter_analyzer/cli/commands/partial_read_command.py +139 -139
  5. tree_sitter_analyzer/cli/info_commands.py +124 -124
  6. tree_sitter_analyzer/cli_main.py +327 -327
  7. tree_sitter_analyzer/core/analysis_engine.py +584 -584
  8. tree_sitter_analyzer/core/query_service.py +162 -162
  9. tree_sitter_analyzer/file_handler.py +212 -212
  10. tree_sitter_analyzer/formatters/base_formatter.py +169 -169
  11. tree_sitter_analyzer/interfaces/cli.py +535 -535
  12. tree_sitter_analyzer/mcp/__init__.py +1 -1
  13. tree_sitter_analyzer/mcp/resources/__init__.py +1 -1
  14. tree_sitter_analyzer/mcp/resources/project_stats_resource.py +16 -5
  15. tree_sitter_analyzer/mcp/server.py +655 -655
  16. tree_sitter_analyzer/mcp/tools/__init__.py +30 -30
  17. tree_sitter_analyzer/mcp/utils/__init__.py +2 -2
  18. tree_sitter_analyzer/mcp/utils/error_handler.py +569 -569
  19. tree_sitter_analyzer/mcp/utils/path_resolver.py +414 -414
  20. tree_sitter_analyzer/output_manager.py +257 -257
  21. tree_sitter_analyzer/project_detector.py +330 -330
  22. tree_sitter_analyzer/security/boundary_manager.py +260 -260
  23. tree_sitter_analyzer/security/validator.py +257 -257
  24. tree_sitter_analyzer/table_formatter.py +710 -710
  25. tree_sitter_analyzer/utils.py +335 -335
  26. {tree_sitter_analyzer-1.0.0.dist-info → tree_sitter_analyzer-1.1.0.dist-info}/METADATA +11 -11
  27. {tree_sitter_analyzer-1.0.0.dist-info → tree_sitter_analyzer-1.1.0.dist-info}/RECORD +29 -29
  28. {tree_sitter_analyzer-1.0.0.dist-info → tree_sitter_analyzer-1.1.0.dist-info}/WHEEL +0 -0
  29. {tree_sitter_analyzer-1.0.0.dist-info → tree_sitter_analyzer-1.1.0.dist-info}/entry_points.txt +0 -0
@@ -1,542 +1,542 @@
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 (ValueError, TypeError, OSError) as e:
130
- # Handle specific expected errors
131
- log_error(f"API analyze_file failed with {type(e).__name__}: {e}")
132
- return {
133
- "success": False,
134
- "error": f"{type(e).__name__}: {str(e)}",
135
- "file_info": {"path": str(file_path), "exists": Path(file_path).exists()},
136
- }
137
- except Exception as e:
138
- # Handle unexpected errors
139
- log_error(f"API analyze_file failed with unexpected error: {e}")
140
- return {
141
- "success": False,
142
- "error": f"Unexpected error: {str(e)}",
143
- "file_info": {"path": str(file_path), "exists": Path(file_path).exists()},
144
- }
145
-
146
-
147
- def analyze_code(
148
- source_code: str,
149
- language: str,
150
- queries: list[str] | None = None,
151
- include_elements: bool = True,
152
- include_queries: bool = True,
153
- ) -> dict[str, Any]:
154
- """
155
- Analyze source code directly (without file).
156
-
157
- Args:
158
- source_code: Source code string to analyze
159
- language: Programming language
160
- queries: List of query names to execute (all available if not specified)
161
- include_elements: Whether to extract code elements
162
- include_queries: Whether to execute queries
163
-
164
- Returns:
165
- Analysis results dictionary
166
- """
167
- try:
168
- engine = get_engine()
169
-
170
- # Perform the analysis
171
- analysis_result = engine.analyze_code(source_code, language)
172
-
173
- # Convert AnalysisResult to expected API format
174
- result = {
175
- "success": analysis_result.success,
176
- "language_info": {
177
- "language": analysis_result.language,
178
- "detected": False, # Language was explicitly provided
179
- },
180
- "ast_info": {
181
- "node_count": analysis_result.node_count,
182
- "line_count": analysis_result.line_count,
183
- },
184
- }
185
-
186
- # Add elements if requested and available
187
- if include_elements and hasattr(analysis_result, "elements"):
188
- result["elements"] = [
189
- {
190
- "name": elem.name,
191
- "type": type(elem).__name__.lower(),
192
- "start_line": elem.start_line,
193
- "end_line": elem.end_line,
194
- "raw_text": elem.raw_text,
195
- "language": elem.language,
196
- }
197
- for elem in analysis_result.elements
198
- ]
199
-
200
- # Add query results if requested and available
201
- if include_queries and hasattr(analysis_result, "query_results"):
202
- result["query_results"] = analysis_result.query_results
203
-
204
- # Add error message if analysis failed
205
- if not analysis_result.success and analysis_result.error_message:
206
- result["error"] = analysis_result.error_message
207
-
208
- # Filter results based on options
209
- if not include_elements and "elements" in result:
210
- del result["elements"]
211
-
212
- if not include_queries and "query_results" in result:
213
- del result["query_results"]
214
-
215
- return result
216
-
217
- except Exception as e:
218
- log_error(f"API analyze_code failed: {e}")
219
- return {"success": False, "error": str(e)}
220
-
221
-
222
- def get_supported_languages() -> list[str]:
223
- """
224
- Get list of all supported programming languages.
225
-
226
- Returns:
227
- List of supported language names
228
- """
229
- try:
230
- engine = get_engine()
231
- return engine.get_supported_languages()
232
- except Exception as e:
233
- log_error(f"Failed to get supported languages: {e}")
234
- return []
235
-
236
-
237
- def get_available_queries(language: str) -> list[str]:
238
- """
239
- Get available queries for a specific language.
240
-
241
- Args:
242
- language: Programming language name
243
-
244
- Returns:
245
- List of available query names
246
- """
247
- try:
248
- engine = get_engine()
249
- # Try to get plugin and its supported queries
250
- plugin = engine._get_language_plugin(language)
251
- if plugin and hasattr(plugin, "get_supported_queries"):
252
- result = plugin.get_supported_queries()
253
- return list(result) if result else []
254
- else:
255
- # Return default queries
256
- return ["class", "method", "field"]
257
- except Exception as e:
258
- log_error(f"Failed to get available queries for {language}: {e}")
259
- return []
260
-
261
-
262
- def is_language_supported(language: str) -> bool:
263
- """
264
- Check if a programming language is supported.
265
-
266
- Args:
267
- language: Programming language name
268
-
269
- Returns:
270
- True if the language is supported
271
- """
272
- try:
273
- supported_languages = get_supported_languages()
274
- return language.lower() in [lang.lower() for lang in supported_languages]
275
- except Exception as e:
276
- log_error(f"Failed to check language support for {language}: {e}")
277
- return False
278
-
279
-
280
- def detect_language(file_path: str | Path) -> str | None:
281
- """
282
- Detect programming language from file path.
283
-
284
- Args:
285
- file_path: Path to the file
286
-
287
- Returns:
288
- Detected language name or None
289
- """
290
- try:
291
- engine = get_engine()
292
- # Use language_detector instead of language_registry
293
- return engine.language_detector.detect_from_extension(str(file_path))
294
- except Exception as e:
295
- log_error(f"Failed to detect language for {file_path}: {e}")
296
- return None
297
-
298
-
299
- def get_file_extensions(language: str) -> list[str]:
300
- """
301
- Get file extensions for a specific language.
302
-
303
- Args:
304
- language: Programming language name
305
-
306
- Returns:
307
- List of file extensions
308
- """
309
- try:
310
- engine = get_engine()
311
- # Use language_detector to get extensions
312
- if hasattr(engine.language_detector, "get_extensions_for_language"):
313
- result = engine.language_detector.get_extensions_for_language(language)
314
- return list(result) if result else []
315
- else:
316
- # Fallback: return common extensions
317
- extension_map = {
318
- "java": [".java"],
319
- "python": [".py"],
320
- "javascript": [".js"],
321
- "typescript": [".ts"],
322
- "c": [".c"],
323
- "cpp": [".cpp", ".cxx", ".cc"],
324
- "go": [".go"],
325
- "rust": [".rs"],
326
- }
327
- return extension_map.get(language.lower(), [])
328
- except Exception as e:
329
- log_error(f"Failed to get extensions for {language}: {e}")
330
- return []
331
-
332
-
333
- def validate_file(file_path: str | Path) -> dict[str, Any]:
334
- """
335
- Validate a source code file without full analysis.
336
-
337
- Args:
338
- file_path: Path to the file to validate
339
-
340
- Returns:
341
- Validation results dictionary
342
- """
343
- file_path = Path(file_path)
344
-
345
- result: dict[str, Any] = {
346
- "valid": False,
347
- "exists": file_path.exists(),
348
- "readable": False,
349
- "language": None,
350
- "supported": False,
351
- "size": 0,
352
- "errors": [],
353
- }
354
-
355
- try:
356
- # Check if file exists
357
- if not file_path.exists():
358
- result["errors"].append("File does not exist")
359
- return result
360
-
361
- # Check if file is readable
362
- try:
363
- with open(file_path, encoding="utf-8") as f:
364
- f.read(100) # Read first 100 chars to test
365
- result["readable"] = True
366
- result["size"] = file_path.stat().st_size
367
- except Exception as e:
368
- result["errors"].append(f"File is not readable: {e}")
369
- return result
370
-
371
- # Detect language
372
- language = detect_language(file_path)
373
- result["language"] = language
374
-
375
- if language:
376
- result["supported"] = is_language_supported(language)
377
- if not result["supported"]:
378
- result["errors"].append(f"Language '{language}' is not supported")
379
- else:
380
- result["errors"].append("Could not detect programming language")
381
-
382
- # If we got this far with no errors, the file is valid
383
- result["valid"] = len(result["errors"]) == 0
384
-
385
- except Exception as e:
386
- result["errors"].append(f"Validation failed: {e}")
387
-
388
- return result
389
-
390
-
391
- def get_framework_info() -> dict[str, Any]:
392
- """
393
- Get information about the framework and its capabilities.
394
-
395
- Returns:
396
- Framework information dictionary
397
- """
398
- try:
399
- engine = get_engine()
400
-
401
- return {
402
- "name": "tree-sitter-analyzer",
403
- "version": "2.0.0", # New architecture version
404
- "supported_languages": engine.get_supported_languages(),
405
- "total_languages": len(engine.get_supported_languages()),
406
- "plugin_info": {
407
- "manager_available": engine.plugin_manager is not None,
408
- "loaded_plugins": (
409
- len(engine.plugin_manager.get_supported_languages())
410
- if engine.plugin_manager
411
- else 0
412
- ),
413
- },
414
- "core_components": [
415
- "AnalysisEngine",
416
- "Parser",
417
- "QueryExecutor",
418
- "PluginManager",
419
- "LanguageDetector",
420
- ],
421
- }
422
- except Exception as e:
423
- log_error(f"Failed to get framework info: {e}")
424
- return {"name": "tree-sitter-analyzer", "version": "2.0.0", "error": str(e)}
425
-
426
-
427
- def execute_query(
428
- file_path: str | Path, query_name: str, language: str | None = None
429
- ) -> dict[str, Any]:
430
- """
431
- Execute a specific query against a file.
432
-
433
- Args:
434
- file_path: Path to the source file
435
- query_name: Name of the query to execute
436
- language: Programming language (auto-detected if not specified)
437
-
438
- Returns:
439
- Query execution results
440
- """
441
- try:
442
- # Analyze with only the specified query
443
- result = analyze_file(
444
- file_path,
445
- language=language,
446
- queries=[query_name],
447
- include_elements=False,
448
- include_queries=True,
449
- )
450
-
451
- if result["success"] and "query_results" in result:
452
- query_results = result["query_results"].get(query_name, [])
453
- return {
454
- "success": True,
455
- "query_name": query_name,
456
- "results": query_results,
457
- "count": len(query_results),
458
- "language": result.get("language_info", {}).get("language"),
459
- "file_path": str(file_path),
460
- }
461
- else:
462
- return {
463
- "success": False,
464
- "query_name": query_name,
465
- "error": result.get("error", "Unknown error"),
466
- "file_path": str(file_path),
467
- }
468
-
469
- except Exception as e:
470
- log_error(f"Query execution failed: {e}")
471
- return {
472
- "success": False,
473
- "query_name": query_name,
474
- "error": str(e),
475
- "file_path": str(file_path),
476
- }
477
-
478
-
479
- def extract_elements(
480
- file_path: str | Path,
481
- language: str | None = None,
482
- element_types: list[str] | None = None,
483
- ) -> dict[str, Any]:
484
- """
485
- Extract code elements from a file.
486
-
487
- Args:
488
- file_path: Path to the source file
489
- language: Programming language (auto-detected if not specified)
490
- element_types: Types of elements to extract (all if not specified)
491
-
492
- Returns:
493
- Element extraction results
494
- """
495
- try:
496
- # Analyze with only element extraction
497
- result = analyze_file(
498
- file_path, language=language, include_elements=True, include_queries=False
499
- )
500
-
501
- if result["success"] and "elements" in result:
502
- elements = result["elements"]
503
-
504
- # Filter by element types if specified
505
- if element_types:
506
- filtered_elements = []
507
- for element in elements:
508
- if any(
509
- etype.lower() in element.get("type", "").lower()
510
- for etype in element_types
511
- ):
512
- filtered_elements.append(element)
513
- elements = filtered_elements
514
-
515
- return {
516
- "success": True,
517
- "elements": elements,
518
- "count": len(elements),
519
- "language": result.get("language_info", {}).get("language"),
520
- "file_path": str(file_path),
521
- }
522
- else:
523
- return {
524
- "success": False,
525
- "error": result.get("error", "Unknown error"),
526
- "file_path": str(file_path),
527
- }
528
-
529
- except Exception as e:
530
- log_error(f"Element extraction failed: {e}")
531
- return {"success": False, "error": str(e), "file_path": str(file_path)}
532
-
533
-
534
- # Convenience functions for backward compatibility
535
- def analyze(file_path: str | Path, **kwargs: Any) -> dict[str, Any]:
536
- """Convenience function that aliases to analyze_file."""
537
- return analyze_file(file_path, **kwargs)
538
-
539
-
540
- def get_languages() -> list[str]:
541
- """Convenience function that aliases to get_supported_languages."""
542
- 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 (ValueError, TypeError, OSError) as e:
130
+ # Handle specific expected errors
131
+ log_error(f"API analyze_file failed with {type(e).__name__}: {e}")
132
+ return {
133
+ "success": False,
134
+ "error": f"{type(e).__name__}: {str(e)}",
135
+ "file_info": {"path": str(file_path), "exists": Path(file_path).exists()},
136
+ }
137
+ except Exception as e:
138
+ # Handle unexpected errors
139
+ log_error(f"API analyze_file failed with unexpected error: {e}")
140
+ return {
141
+ "success": False,
142
+ "error": f"Unexpected error: {str(e)}",
143
+ "file_info": {"path": str(file_path), "exists": Path(file_path).exists()},
144
+ }
145
+
146
+
147
+ def analyze_code(
148
+ source_code: str,
149
+ language: str,
150
+ queries: list[str] | None = None,
151
+ include_elements: bool = True,
152
+ include_queries: bool = True,
153
+ ) -> dict[str, Any]:
154
+ """
155
+ Analyze source code directly (without file).
156
+
157
+ Args:
158
+ source_code: Source code string to analyze
159
+ language: Programming language
160
+ queries: List of query names to execute (all available if not specified)
161
+ include_elements: Whether to extract code elements
162
+ include_queries: Whether to execute queries
163
+
164
+ Returns:
165
+ Analysis results dictionary
166
+ """
167
+ try:
168
+ engine = get_engine()
169
+
170
+ # Perform the analysis
171
+ analysis_result = engine.analyze_code(source_code, language)
172
+
173
+ # Convert AnalysisResult to expected API format
174
+ result = {
175
+ "success": analysis_result.success,
176
+ "language_info": {
177
+ "language": analysis_result.language,
178
+ "detected": False, # Language was explicitly provided
179
+ },
180
+ "ast_info": {
181
+ "node_count": analysis_result.node_count,
182
+ "line_count": analysis_result.line_count,
183
+ },
184
+ }
185
+
186
+ # Add elements if requested and available
187
+ if include_elements and hasattr(analysis_result, "elements"):
188
+ result["elements"] = [
189
+ {
190
+ "name": elem.name,
191
+ "type": type(elem).__name__.lower(),
192
+ "start_line": elem.start_line,
193
+ "end_line": elem.end_line,
194
+ "raw_text": elem.raw_text,
195
+ "language": elem.language,
196
+ }
197
+ for elem in analysis_result.elements
198
+ ]
199
+
200
+ # Add query results if requested and available
201
+ if include_queries and hasattr(analysis_result, "query_results"):
202
+ result["query_results"] = analysis_result.query_results
203
+
204
+ # Add error message if analysis failed
205
+ if not analysis_result.success and analysis_result.error_message:
206
+ result["error"] = analysis_result.error_message
207
+
208
+ # Filter results based on options
209
+ if not include_elements and "elements" in result:
210
+ del result["elements"]
211
+
212
+ if not include_queries and "query_results" in result:
213
+ del result["query_results"]
214
+
215
+ return result
216
+
217
+ except Exception as e:
218
+ log_error(f"API analyze_code failed: {e}")
219
+ return {"success": False, "error": str(e)}
220
+
221
+
222
+ def get_supported_languages() -> list[str]:
223
+ """
224
+ Get list of all supported programming languages.
225
+
226
+ Returns:
227
+ List of supported language names
228
+ """
229
+ try:
230
+ engine = get_engine()
231
+ return engine.get_supported_languages()
232
+ except Exception as e:
233
+ log_error(f"Failed to get supported languages: {e}")
234
+ return []
235
+
236
+
237
+ def get_available_queries(language: str) -> list[str]:
238
+ """
239
+ Get available queries for a specific language.
240
+
241
+ Args:
242
+ language: Programming language name
243
+
244
+ Returns:
245
+ List of available query names
246
+ """
247
+ try:
248
+ engine = get_engine()
249
+ # Try to get plugin and its supported queries
250
+ plugin = engine._get_language_plugin(language)
251
+ if plugin and hasattr(plugin, "get_supported_queries"):
252
+ result = plugin.get_supported_queries()
253
+ return list(result) if result else []
254
+ else:
255
+ # Return default queries
256
+ return ["class", "method", "field"]
257
+ except Exception as e:
258
+ log_error(f"Failed to get available queries for {language}: {e}")
259
+ return []
260
+
261
+
262
+ def is_language_supported(language: str) -> bool:
263
+ """
264
+ Check if a programming language is supported.
265
+
266
+ Args:
267
+ language: Programming language name
268
+
269
+ Returns:
270
+ True if the language is supported
271
+ """
272
+ try:
273
+ supported_languages = get_supported_languages()
274
+ return language.lower() in [lang.lower() for lang in supported_languages]
275
+ except Exception as e:
276
+ log_error(f"Failed to check language support for {language}: {e}")
277
+ return False
278
+
279
+
280
+ def detect_language(file_path: str | Path) -> str | None:
281
+ """
282
+ Detect programming language from file path.
283
+
284
+ Args:
285
+ file_path: Path to the file
286
+
287
+ Returns:
288
+ Detected language name or None
289
+ """
290
+ try:
291
+ engine = get_engine()
292
+ # Use language_detector instead of language_registry
293
+ return engine.language_detector.detect_from_extension(str(file_path))
294
+ except Exception as e:
295
+ log_error(f"Failed to detect language for {file_path}: {e}")
296
+ return None
297
+
298
+
299
+ def get_file_extensions(language: str) -> list[str]:
300
+ """
301
+ Get file extensions for a specific language.
302
+
303
+ Args:
304
+ language: Programming language name
305
+
306
+ Returns:
307
+ List of file extensions
308
+ """
309
+ try:
310
+ engine = get_engine()
311
+ # Use language_detector to get extensions
312
+ if hasattr(engine.language_detector, "get_extensions_for_language"):
313
+ result = engine.language_detector.get_extensions_for_language(language)
314
+ return list(result) if result else []
315
+ else:
316
+ # Fallback: return common extensions
317
+ extension_map = {
318
+ "java": [".java"],
319
+ "python": [".py"],
320
+ "javascript": [".js"],
321
+ "typescript": [".ts"],
322
+ "c": [".c"],
323
+ "cpp": [".cpp", ".cxx", ".cc"],
324
+ "go": [".go"],
325
+ "rust": [".rs"],
326
+ }
327
+ return extension_map.get(language.lower(), [])
328
+ except Exception as e:
329
+ log_error(f"Failed to get extensions for {language}: {e}")
330
+ return []
331
+
332
+
333
+ def validate_file(file_path: str | Path) -> dict[str, Any]:
334
+ """
335
+ Validate a source code file without full analysis.
336
+
337
+ Args:
338
+ file_path: Path to the file to validate
339
+
340
+ Returns:
341
+ Validation results dictionary
342
+ """
343
+ file_path = Path(file_path)
344
+
345
+ result: dict[str, Any] = {
346
+ "valid": False,
347
+ "exists": file_path.exists(),
348
+ "readable": False,
349
+ "language": None,
350
+ "supported": False,
351
+ "size": 0,
352
+ "errors": [],
353
+ }
354
+
355
+ try:
356
+ # Check if file exists
357
+ if not file_path.exists():
358
+ result["errors"].append("File does not exist")
359
+ return result
360
+
361
+ # Check if file is readable
362
+ try:
363
+ with open(file_path, encoding="utf-8") as f:
364
+ f.read(100) # Read first 100 chars to test
365
+ result["readable"] = True
366
+ result["size"] = file_path.stat().st_size
367
+ except Exception as e:
368
+ result["errors"].append(f"File is not readable: {e}")
369
+ return result
370
+
371
+ # Detect language
372
+ language = detect_language(file_path)
373
+ result["language"] = language
374
+
375
+ if language:
376
+ result["supported"] = is_language_supported(language)
377
+ if not result["supported"]:
378
+ result["errors"].append(f"Language '{language}' is not supported")
379
+ else:
380
+ result["errors"].append("Could not detect programming language")
381
+
382
+ # If we got this far with no errors, the file is valid
383
+ result["valid"] = len(result["errors"]) == 0
384
+
385
+ except Exception as e:
386
+ result["errors"].append(f"Validation failed: {e}")
387
+
388
+ return result
389
+
390
+
391
+ def get_framework_info() -> dict[str, Any]:
392
+ """
393
+ Get information about the framework and its capabilities.
394
+
395
+ Returns:
396
+ Framework information dictionary
397
+ """
398
+ try:
399
+ engine = get_engine()
400
+
401
+ return {
402
+ "name": "tree-sitter-analyzer",
403
+ "version": "2.0.0", # New architecture version
404
+ "supported_languages": engine.get_supported_languages(),
405
+ "total_languages": len(engine.get_supported_languages()),
406
+ "plugin_info": {
407
+ "manager_available": engine.plugin_manager is not None,
408
+ "loaded_plugins": (
409
+ len(engine.plugin_manager.get_supported_languages())
410
+ if engine.plugin_manager
411
+ else 0
412
+ ),
413
+ },
414
+ "core_components": [
415
+ "AnalysisEngine",
416
+ "Parser",
417
+ "QueryExecutor",
418
+ "PluginManager",
419
+ "LanguageDetector",
420
+ ],
421
+ }
422
+ except Exception as e:
423
+ log_error(f"Failed to get framework info: {e}")
424
+ return {"name": "tree-sitter-analyzer", "version": "2.0.0", "error": str(e)}
425
+
426
+
427
+ def execute_query(
428
+ file_path: str | Path, query_name: str, language: str | None = None
429
+ ) -> dict[str, Any]:
430
+ """
431
+ Execute a specific query against a file.
432
+
433
+ Args:
434
+ file_path: Path to the source file
435
+ query_name: Name of the query to execute
436
+ language: Programming language (auto-detected if not specified)
437
+
438
+ Returns:
439
+ Query execution results
440
+ """
441
+ try:
442
+ # Analyze with only the specified query
443
+ result = analyze_file(
444
+ file_path,
445
+ language=language,
446
+ queries=[query_name],
447
+ include_elements=False,
448
+ include_queries=True,
449
+ )
450
+
451
+ if result["success"] and "query_results" in result:
452
+ query_results = result["query_results"].get(query_name, [])
453
+ return {
454
+ "success": True,
455
+ "query_name": query_name,
456
+ "results": query_results,
457
+ "count": len(query_results),
458
+ "language": result.get("language_info", {}).get("language"),
459
+ "file_path": str(file_path),
460
+ }
461
+ else:
462
+ return {
463
+ "success": False,
464
+ "query_name": query_name,
465
+ "error": result.get("error", "Unknown error"),
466
+ "file_path": str(file_path),
467
+ }
468
+
469
+ except Exception as e:
470
+ log_error(f"Query execution failed: {e}")
471
+ return {
472
+ "success": False,
473
+ "query_name": query_name,
474
+ "error": str(e),
475
+ "file_path": str(file_path),
476
+ }
477
+
478
+
479
+ def extract_elements(
480
+ file_path: str | Path,
481
+ language: str | None = None,
482
+ element_types: list[str] | None = None,
483
+ ) -> dict[str, Any]:
484
+ """
485
+ Extract code elements from a file.
486
+
487
+ Args:
488
+ file_path: Path to the source file
489
+ language: Programming language (auto-detected if not specified)
490
+ element_types: Types of elements to extract (all if not specified)
491
+
492
+ Returns:
493
+ Element extraction results
494
+ """
495
+ try:
496
+ # Analyze with only element extraction
497
+ result = analyze_file(
498
+ file_path, language=language, include_elements=True, include_queries=False
499
+ )
500
+
501
+ if result["success"] and "elements" in result:
502
+ elements = result["elements"]
503
+
504
+ # Filter by element types if specified
505
+ if element_types:
506
+ filtered_elements = []
507
+ for element in elements:
508
+ if any(
509
+ etype.lower() in element.get("type", "").lower()
510
+ for etype in element_types
511
+ ):
512
+ filtered_elements.append(element)
513
+ elements = filtered_elements
514
+
515
+ return {
516
+ "success": True,
517
+ "elements": elements,
518
+ "count": len(elements),
519
+ "language": result.get("language_info", {}).get("language"),
520
+ "file_path": str(file_path),
521
+ }
522
+ else:
523
+ return {
524
+ "success": False,
525
+ "error": result.get("error", "Unknown error"),
526
+ "file_path": str(file_path),
527
+ }
528
+
529
+ except Exception as e:
530
+ log_error(f"Element extraction failed: {e}")
531
+ return {"success": False, "error": str(e), "file_path": str(file_path)}
532
+
533
+
534
+ # Convenience functions for backward compatibility
535
+ def analyze(file_path: str | Path, **kwargs: Any) -> dict[str, Any]:
536
+ """Convenience function that aliases to analyze_file."""
537
+ return analyze_file(file_path, **kwargs)
538
+
539
+
540
+ def get_languages() -> list[str]:
541
+ """Convenience function that aliases to get_supported_languages."""
542
+ return get_supported_languages()