tree-sitter-analyzer 1.8.4__py3-none-any.whl → 1.9.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.
Potentially problematic release.
This version of tree-sitter-analyzer might be problematic. Click here for more details.
- tree_sitter_analyzer/__init__.py +1 -1
- tree_sitter_analyzer/api.py +4 -4
- tree_sitter_analyzer/cli/argument_validator.py +29 -17
- tree_sitter_analyzer/cli/commands/advanced_command.py +7 -5
- tree_sitter_analyzer/cli/commands/structure_command.py +7 -5
- tree_sitter_analyzer/cli/commands/summary_command.py +10 -6
- tree_sitter_analyzer/cli/commands/table_command.py +8 -7
- tree_sitter_analyzer/cli/info_commands.py +1 -1
- tree_sitter_analyzer/cli_main.py +3 -2
- tree_sitter_analyzer/core/analysis_engine.py +5 -5
- tree_sitter_analyzer/core/cache_service.py +3 -1
- tree_sitter_analyzer/core/query.py +17 -5
- tree_sitter_analyzer/core/query_service.py +1 -1
- tree_sitter_analyzer/encoding_utils.py +3 -3
- tree_sitter_analyzer/exceptions.py +61 -50
- tree_sitter_analyzer/file_handler.py +3 -0
- tree_sitter_analyzer/formatters/base_formatter.py +10 -5
- tree_sitter_analyzer/formatters/formatter_registry.py +83 -68
- tree_sitter_analyzer/formatters/html_formatter.py +90 -64
- tree_sitter_analyzer/formatters/javascript_formatter.py +21 -16
- tree_sitter_analyzer/formatters/language_formatter_factory.py +7 -6
- tree_sitter_analyzer/formatters/markdown_formatter.py +247 -124
- tree_sitter_analyzer/formatters/python_formatter.py +61 -38
- tree_sitter_analyzer/formatters/typescript_formatter.py +113 -45
- tree_sitter_analyzer/interfaces/mcp_server.py +2 -2
- tree_sitter_analyzer/language_detector.py +6 -6
- tree_sitter_analyzer/language_loader.py +3 -1
- tree_sitter_analyzer/languages/css_plugin.py +120 -61
- tree_sitter_analyzer/languages/html_plugin.py +159 -62
- tree_sitter_analyzer/languages/java_plugin.py +42 -34
- tree_sitter_analyzer/languages/javascript_plugin.py +59 -30
- tree_sitter_analyzer/languages/markdown_plugin.py +402 -368
- tree_sitter_analyzer/languages/python_plugin.py +111 -64
- tree_sitter_analyzer/languages/typescript_plugin.py +241 -132
- tree_sitter_analyzer/mcp/server.py +22 -18
- tree_sitter_analyzer/mcp/tools/analyze_scale_tool.py +13 -8
- tree_sitter_analyzer/mcp/tools/base_tool.py +2 -2
- tree_sitter_analyzer/mcp/tools/fd_rg_utils.py +232 -26
- tree_sitter_analyzer/mcp/tools/find_and_grep_tool.py +31 -23
- tree_sitter_analyzer/mcp/tools/list_files_tool.py +21 -19
- tree_sitter_analyzer/mcp/tools/query_tool.py +17 -18
- tree_sitter_analyzer/mcp/tools/read_partial_tool.py +30 -31
- tree_sitter_analyzer/mcp/tools/search_content_tool.py +131 -77
- tree_sitter_analyzer/mcp/tools/table_format_tool.py +29 -16
- tree_sitter_analyzer/mcp/utils/file_output_factory.py +64 -51
- tree_sitter_analyzer/mcp/utils/file_output_manager.py +34 -24
- tree_sitter_analyzer/mcp/utils/gitignore_detector.py +8 -4
- tree_sitter_analyzer/models.py +7 -5
- tree_sitter_analyzer/plugins/base.py +9 -7
- tree_sitter_analyzer/plugins/manager.py +1 -0
- tree_sitter_analyzer/queries/css.py +2 -21
- tree_sitter_analyzer/queries/html.py +2 -15
- tree_sitter_analyzer/queries/markdown.py +30 -41
- tree_sitter_analyzer/queries/python.py +20 -5
- tree_sitter_analyzer/query_loader.py +5 -5
- tree_sitter_analyzer/security/validator.py +114 -86
- tree_sitter_analyzer/utils/__init__.py +58 -28
- tree_sitter_analyzer/utils/tree_sitter_compat.py +72 -65
- tree_sitter_analyzer/utils.py +26 -15
- {tree_sitter_analyzer-1.8.4.dist-info → tree_sitter_analyzer-1.9.1.dist-info}/METADATA +23 -6
- tree_sitter_analyzer-1.9.1.dist-info/RECORD +109 -0
- tree_sitter_analyzer-1.8.4.dist-info/RECORD +0 -109
- {tree_sitter_analyzer-1.8.4.dist-info → tree_sitter_analyzer-1.9.1.dist-info}/WHEEL +0 -0
- {tree_sitter_analyzer-1.8.4.dist-info → tree_sitter_analyzer-1.9.1.dist-info}/entry_points.txt +0 -0
|
@@ -403,7 +403,7 @@ class RegexSecurityError(SecurityError):
|
|
|
403
403
|
# MCP-specific exceptions for enhanced error handling
|
|
404
404
|
class MCPToolError(MCPError):
|
|
405
405
|
"""Raised when MCP tool execution fails."""
|
|
406
|
-
|
|
406
|
+
|
|
407
407
|
def __init__(
|
|
408
408
|
self,
|
|
409
409
|
message: str,
|
|
@@ -419,17 +419,17 @@ class MCPToolError(MCPError):
|
|
|
419
419
|
context["input_params"] = sanitized_params
|
|
420
420
|
if execution_stage:
|
|
421
421
|
context["execution_stage"] = execution_stage
|
|
422
|
-
|
|
422
|
+
|
|
423
423
|
super().__init__(message, tool_name=tool_name, context=context, **kwargs)
|
|
424
424
|
self.input_params = input_params
|
|
425
425
|
self.execution_stage = execution_stage
|
|
426
|
-
|
|
426
|
+
|
|
427
427
|
@staticmethod
|
|
428
428
|
def _sanitize_params(params: dict[str, Any]) -> dict[str, Any]:
|
|
429
429
|
"""Sanitize sensitive information from parameters."""
|
|
430
430
|
sanitized = {}
|
|
431
431
|
sensitive_keys = {"password", "token", "key", "secret", "auth", "credential"}
|
|
432
|
-
|
|
432
|
+
|
|
433
433
|
for key, value in params.items():
|
|
434
434
|
if any(sensitive in key.lower() for sensitive in sensitive_keys):
|
|
435
435
|
sanitized[key] = "***REDACTED***"
|
|
@@ -437,13 +437,13 @@ class MCPToolError(MCPError):
|
|
|
437
437
|
sanitized[key] = value[:100] + "...[TRUNCATED]"
|
|
438
438
|
else:
|
|
439
439
|
sanitized[key] = value
|
|
440
|
-
|
|
440
|
+
|
|
441
441
|
return sanitized
|
|
442
442
|
|
|
443
443
|
|
|
444
444
|
class MCPResourceError(MCPError):
|
|
445
445
|
"""Raised when MCP resource access fails."""
|
|
446
|
-
|
|
446
|
+
|
|
447
447
|
def __init__(
|
|
448
448
|
self,
|
|
449
449
|
message: str,
|
|
@@ -457,7 +457,7 @@ class MCPResourceError(MCPError):
|
|
|
457
457
|
context["resource_type"] = resource_type
|
|
458
458
|
if access_mode:
|
|
459
459
|
context["access_mode"] = access_mode
|
|
460
|
-
|
|
460
|
+
|
|
461
461
|
super().__init__(message, resource_uri=resource_uri, context=context, **kwargs)
|
|
462
462
|
self.resource_type = resource_type
|
|
463
463
|
self.access_mode = access_mode
|
|
@@ -465,7 +465,7 @@ class MCPResourceError(MCPError):
|
|
|
465
465
|
|
|
466
466
|
class MCPTimeoutError(MCPError):
|
|
467
467
|
"""Raised when MCP operation times out."""
|
|
468
|
-
|
|
468
|
+
|
|
469
469
|
def __init__(
|
|
470
470
|
self,
|
|
471
471
|
message: str,
|
|
@@ -478,7 +478,7 @@ class MCPTimeoutError(MCPError):
|
|
|
478
478
|
context["timeout_seconds"] = timeout_seconds
|
|
479
479
|
if operation_type:
|
|
480
480
|
context["operation_type"] = operation_type
|
|
481
|
-
|
|
481
|
+
|
|
482
482
|
super().__init__(message, context=context, **kwargs)
|
|
483
483
|
self.timeout_seconds = timeout_seconds
|
|
484
484
|
self.operation_type = operation_type
|
|
@@ -486,7 +486,7 @@ class MCPTimeoutError(MCPError):
|
|
|
486
486
|
|
|
487
487
|
class MCPValidationError(ValidationError):
|
|
488
488
|
"""Raised when MCP input validation fails."""
|
|
489
|
-
|
|
489
|
+
|
|
490
490
|
def __init__(
|
|
491
491
|
self,
|
|
492
492
|
message: str,
|
|
@@ -503,15 +503,17 @@ class MCPValidationError(ValidationError):
|
|
|
503
503
|
context["parameter_name"] = parameter_name
|
|
504
504
|
if validation_rule:
|
|
505
505
|
context["validation_rule"] = validation_rule
|
|
506
|
-
|
|
506
|
+
|
|
507
507
|
# Sanitize parameter value for logging
|
|
508
508
|
if parameter_value is not None:
|
|
509
509
|
if isinstance(parameter_value, str) and len(parameter_value) > 200:
|
|
510
510
|
context["parameter_value"] = parameter_value[:200] + "...[TRUNCATED]"
|
|
511
511
|
else:
|
|
512
512
|
context["parameter_value"] = parameter_value
|
|
513
|
-
|
|
514
|
-
super().__init__(
|
|
513
|
+
|
|
514
|
+
super().__init__(
|
|
515
|
+
message, validation_type="mcp_parameter", context=context, **kwargs
|
|
516
|
+
)
|
|
515
517
|
self.tool_name = tool_name
|
|
516
518
|
self.parameter_name = parameter_name
|
|
517
519
|
self.validation_rule = validation_rule
|
|
@@ -519,7 +521,7 @@ class MCPValidationError(ValidationError):
|
|
|
519
521
|
|
|
520
522
|
class FileRestrictionError(SecurityError):
|
|
521
523
|
"""Raised when file access is restricted by mode or security policy."""
|
|
522
|
-
|
|
524
|
+
|
|
523
525
|
def __init__(
|
|
524
526
|
self,
|
|
525
527
|
message: str,
|
|
@@ -533,13 +535,13 @@ class FileRestrictionError(SecurityError):
|
|
|
533
535
|
context["current_mode"] = current_mode
|
|
534
536
|
if allowed_patterns:
|
|
535
537
|
context["allowed_patterns"] = allowed_patterns
|
|
536
|
-
|
|
538
|
+
|
|
537
539
|
super().__init__(
|
|
538
540
|
message,
|
|
539
541
|
security_type="file_restriction",
|
|
540
542
|
file_path=file_path,
|
|
541
543
|
context=context,
|
|
542
|
-
**kwargs
|
|
544
|
+
**kwargs,
|
|
543
545
|
)
|
|
544
546
|
self.current_mode = current_mode
|
|
545
547
|
self.allowed_patterns = allowed_patterns
|
|
@@ -554,52 +556,52 @@ def create_mcp_error_response(
|
|
|
554
556
|
) -> dict[str, Any]:
|
|
555
557
|
"""
|
|
556
558
|
Create standardized MCP error response dictionary.
|
|
557
|
-
|
|
559
|
+
|
|
558
560
|
Args:
|
|
559
561
|
exception: The exception to convert
|
|
560
562
|
tool_name: Name of the MCP tool that failed
|
|
561
563
|
include_debug_info: Whether to include debug information
|
|
562
564
|
sanitize_sensitive: Whether to sanitize sensitive information
|
|
563
|
-
|
|
565
|
+
|
|
564
566
|
Returns:
|
|
565
567
|
MCP-compliant error response dictionary
|
|
566
568
|
"""
|
|
567
569
|
import traceback
|
|
568
|
-
|
|
570
|
+
|
|
569
571
|
response: dict[str, Any] = {
|
|
570
572
|
"success": False,
|
|
571
573
|
"error": {
|
|
572
574
|
"type": exception.__class__.__name__,
|
|
573
575
|
"message": str(exception),
|
|
574
|
-
"timestamp": __import__("datetime").datetime.utcnow().isoformat() + "Z"
|
|
575
|
-
}
|
|
576
|
+
"timestamp": __import__("datetime").datetime.utcnow().isoformat() + "Z",
|
|
577
|
+
},
|
|
576
578
|
}
|
|
577
|
-
|
|
579
|
+
|
|
578
580
|
# Add tool name if provided
|
|
579
581
|
if tool_name:
|
|
580
582
|
response["error"]["tool"] = tool_name
|
|
581
|
-
|
|
583
|
+
|
|
582
584
|
# Add context if available
|
|
583
585
|
if hasattr(exception, "context") and exception.context:
|
|
584
586
|
context = exception.context.copy()
|
|
585
|
-
|
|
587
|
+
|
|
586
588
|
# Sanitize sensitive information if requested
|
|
587
589
|
if sanitize_sensitive:
|
|
588
590
|
context = _sanitize_error_context(context)
|
|
589
|
-
|
|
591
|
+
|
|
590
592
|
response["error"]["context"] = context
|
|
591
|
-
|
|
593
|
+
|
|
592
594
|
# Add error code if available
|
|
593
595
|
if hasattr(exception, "error_code"):
|
|
594
596
|
response["error"]["code"] = exception.error_code
|
|
595
|
-
|
|
597
|
+
|
|
596
598
|
# Add debug information if requested
|
|
597
599
|
if include_debug_info:
|
|
598
600
|
response["error"]["debug"] = {
|
|
599
601
|
"traceback": traceback.format_exc(),
|
|
600
|
-
"exception_args": list(exception.args) if exception.args else []
|
|
602
|
+
"exception_args": list(exception.args) if exception.args else [],
|
|
601
603
|
}
|
|
602
|
-
|
|
604
|
+
|
|
603
605
|
# Add specific error details for known exception types
|
|
604
606
|
if isinstance(exception, MCPToolError):
|
|
605
607
|
response["error"]["execution_stage"] = exception.execution_stage
|
|
@@ -608,7 +610,7 @@ def create_mcp_error_response(
|
|
|
608
610
|
elif isinstance(exception, FileRestrictionError):
|
|
609
611
|
response["error"]["current_mode"] = exception.current_mode
|
|
610
612
|
response["error"]["allowed_patterns"] = exception.allowed_patterns
|
|
611
|
-
|
|
613
|
+
|
|
612
614
|
return response
|
|
613
615
|
|
|
614
616
|
|
|
@@ -616,10 +618,18 @@ def _sanitize_error_context(context: dict[str, Any]) -> dict[str, Any]:
|
|
|
616
618
|
"""Sanitize sensitive information from error context."""
|
|
617
619
|
sanitized = {}
|
|
618
620
|
sensitive_keys = {
|
|
619
|
-
"password",
|
|
620
|
-
"
|
|
621
|
+
"password",
|
|
622
|
+
"token",
|
|
623
|
+
"key",
|
|
624
|
+
"secret",
|
|
625
|
+
"auth",
|
|
626
|
+
"credential",
|
|
627
|
+
"api_key",
|
|
628
|
+
"access_token",
|
|
629
|
+
"private_key",
|
|
630
|
+
"session_id",
|
|
621
631
|
}
|
|
622
|
-
|
|
632
|
+
|
|
623
633
|
for key, value in context.items():
|
|
624
634
|
if any(sensitive in key.lower() for sensitive in sensitive_keys):
|
|
625
635
|
sanitized[key] = "***REDACTED***"
|
|
@@ -634,7 +644,7 @@ def _sanitize_error_context(context: dict[str, Any]) -> dict[str, Any]:
|
|
|
634
644
|
sanitized[key]["__truncated__"] = True
|
|
635
645
|
else:
|
|
636
646
|
sanitized[key] = value
|
|
637
|
-
|
|
647
|
+
|
|
638
648
|
return sanitized
|
|
639
649
|
|
|
640
650
|
|
|
@@ -648,14 +658,14 @@ async def safe_execute_async(
|
|
|
648
658
|
) -> Any:
|
|
649
659
|
"""
|
|
650
660
|
Safely execute an async function with exception handling.
|
|
651
|
-
|
|
661
|
+
|
|
652
662
|
Args:
|
|
653
663
|
coro: Coroutine to execute
|
|
654
664
|
default_return: Value to return on exception
|
|
655
665
|
exception_types: Exception types to catch
|
|
656
666
|
log_errors: Whether to log errors
|
|
657
667
|
tool_name: Name of the tool for error context
|
|
658
|
-
|
|
668
|
+
|
|
659
669
|
Returns:
|
|
660
670
|
Coroutine result or default_return on exception
|
|
661
671
|
"""
|
|
@@ -664,10 +674,10 @@ async def safe_execute_async(
|
|
|
664
674
|
except exception_types as e:
|
|
665
675
|
if log_errors:
|
|
666
676
|
from .utils import log_error
|
|
667
|
-
|
|
677
|
+
|
|
668
678
|
error_context = {"tool_name": tool_name} if tool_name else {}
|
|
669
679
|
log_error(f"Async execution failed: {e}", extra=error_context)
|
|
670
|
-
|
|
680
|
+
|
|
671
681
|
return default_return
|
|
672
682
|
|
|
673
683
|
|
|
@@ -678,57 +688,58 @@ def mcp_exception_handler(
|
|
|
678
688
|
) -> Any:
|
|
679
689
|
"""
|
|
680
690
|
Decorator for MCP tool exception handling.
|
|
681
|
-
|
|
691
|
+
|
|
682
692
|
Args:
|
|
683
693
|
tool_name: Name of the MCP tool
|
|
684
694
|
include_debug: Whether to include debug information
|
|
685
695
|
sanitize_sensitive: Whether to sanitize sensitive information
|
|
686
696
|
"""
|
|
697
|
+
|
|
687
698
|
def decorator(func: Any) -> Any:
|
|
688
699
|
async def async_wrapper(*args: Any, **kwargs: Any) -> Any:
|
|
689
700
|
try:
|
|
690
701
|
return await func(*args, **kwargs)
|
|
691
702
|
except Exception as e:
|
|
692
703
|
from .utils import log_error
|
|
693
|
-
|
|
704
|
+
|
|
694
705
|
# Log the error with tool context
|
|
695
706
|
log_error(
|
|
696
707
|
f"MCP tool '{tool_name}' failed: {e}",
|
|
697
|
-
extra={"tool_name": tool_name, "exception_type": type(e).__name__}
|
|
708
|
+
extra={"tool_name": tool_name, "exception_type": type(e).__name__},
|
|
698
709
|
)
|
|
699
|
-
|
|
710
|
+
|
|
700
711
|
# Return standardized error response
|
|
701
712
|
return create_mcp_error_response(
|
|
702
713
|
e,
|
|
703
714
|
tool_name=tool_name,
|
|
704
715
|
include_debug_info=include_debug,
|
|
705
|
-
sanitize_sensitive=sanitize_sensitive
|
|
716
|
+
sanitize_sensitive=sanitize_sensitive,
|
|
706
717
|
)
|
|
707
|
-
|
|
718
|
+
|
|
708
719
|
def sync_wrapper(*args: Any, **kwargs: Any) -> Any:
|
|
709
720
|
try:
|
|
710
721
|
return func(*args, **kwargs)
|
|
711
722
|
except Exception as e:
|
|
712
723
|
from .utils import log_error
|
|
713
|
-
|
|
724
|
+
|
|
714
725
|
# Log the error with tool context
|
|
715
726
|
log_error(
|
|
716
727
|
f"MCP tool '{tool_name}' failed: {e}",
|
|
717
|
-
extra={"tool_name": tool_name, "exception_type": type(e).__name__}
|
|
728
|
+
extra={"tool_name": tool_name, "exception_type": type(e).__name__},
|
|
718
729
|
)
|
|
719
|
-
|
|
730
|
+
|
|
720
731
|
# Return standardized error response
|
|
721
732
|
return create_mcp_error_response(
|
|
722
733
|
e,
|
|
723
734
|
tool_name=tool_name,
|
|
724
735
|
include_debug_info=include_debug,
|
|
725
|
-
sanitize_sensitive=sanitize_sensitive
|
|
736
|
+
sanitize_sensitive=sanitize_sensitive,
|
|
726
737
|
)
|
|
727
|
-
|
|
738
|
+
|
|
728
739
|
# Return appropriate wrapper based on function type
|
|
729
740
|
if __import__("asyncio").iscoroutinefunction(func):
|
|
730
741
|
return async_wrapper
|
|
731
742
|
else:
|
|
732
743
|
return sync_wrapper
|
|
733
|
-
|
|
744
|
+
|
|
734
745
|
return decorator
|
|
@@ -13,14 +13,17 @@ from .utils import setup_logger
|
|
|
13
13
|
# Set up logger for this module
|
|
14
14
|
logger = setup_logger(__name__)
|
|
15
15
|
|
|
16
|
+
|
|
16
17
|
def log_error(message: str, *args, **kwargs) -> None:
|
|
17
18
|
"""Log error message"""
|
|
18
19
|
logger.error(message, *args, **kwargs)
|
|
19
20
|
|
|
21
|
+
|
|
20
22
|
def log_info(message: str, *args, **kwargs) -> None:
|
|
21
23
|
"""Log info message"""
|
|
22
24
|
logger.info(message, *args, **kwargs)
|
|
23
25
|
|
|
26
|
+
|
|
24
27
|
def log_warning(message: str, *args, **kwargs) -> None:
|
|
25
28
|
"""Log warning message"""
|
|
26
29
|
logger.warning(message, *args, **kwargs)
|
|
@@ -6,32 +6,37 @@ Base formatter for language-specific formatting.
|
|
|
6
6
|
import csv
|
|
7
7
|
import io
|
|
8
8
|
from abc import ABC, abstractmethod
|
|
9
|
-
from typing import Any
|
|
9
|
+
from typing import Any
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
class BaseFormatter(ABC):
|
|
13
13
|
"""Base class for language-specific formatters"""
|
|
14
14
|
|
|
15
|
+
@abstractmethod
|
|
15
16
|
def __init__(self):
|
|
16
17
|
pass
|
|
17
18
|
|
|
18
19
|
@abstractmethod
|
|
19
|
-
def format_summary(self, analysis_result:
|
|
20
|
+
def format_summary(self, analysis_result: dict[str, Any]) -> str:
|
|
20
21
|
"""Format summary output"""
|
|
21
22
|
pass
|
|
22
23
|
|
|
23
24
|
@abstractmethod
|
|
24
|
-
def format_structure(self, analysis_result:
|
|
25
|
+
def format_structure(self, analysis_result: dict[str, Any]) -> str:
|
|
25
26
|
"""Format structure analysis output"""
|
|
26
27
|
pass
|
|
27
28
|
|
|
28
29
|
@abstractmethod
|
|
29
|
-
def format_advanced(
|
|
30
|
+
def format_advanced(
|
|
31
|
+
self, analysis_result: dict[str, Any], output_format: str = "json"
|
|
32
|
+
) -> str:
|
|
30
33
|
"""Format advanced analysis output"""
|
|
31
34
|
pass
|
|
32
35
|
|
|
33
36
|
@abstractmethod
|
|
34
|
-
def format_table(
|
|
37
|
+
def format_table(
|
|
38
|
+
self, analysis_result: dict[str, Any], table_type: str = "full"
|
|
39
|
+
) -> str:
|
|
35
40
|
"""Format table output"""
|
|
36
41
|
pass
|
|
37
42
|
|