tree-sitter-analyzer 1.7.5__py3-none-any.whl → 1.8.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.

Files changed (47) hide show
  1. tree_sitter_analyzer/__init__.py +1 -1
  2. tree_sitter_analyzer/api.py +26 -32
  3. tree_sitter_analyzer/cli/argument_validator.py +77 -0
  4. tree_sitter_analyzer/cli/commands/table_command.py +7 -2
  5. tree_sitter_analyzer/cli_main.py +17 -3
  6. tree_sitter_analyzer/core/cache_service.py +15 -5
  7. tree_sitter_analyzer/core/query.py +33 -22
  8. tree_sitter_analyzer/core/query_service.py +179 -154
  9. tree_sitter_analyzer/exceptions.py +334 -0
  10. tree_sitter_analyzer/file_handler.py +16 -1
  11. tree_sitter_analyzer/formatters/formatter_registry.py +355 -0
  12. tree_sitter_analyzer/formatters/html_formatter.py +462 -0
  13. tree_sitter_analyzer/formatters/language_formatter_factory.py +3 -0
  14. tree_sitter_analyzer/formatters/markdown_formatter.py +1 -1
  15. tree_sitter_analyzer/interfaces/mcp_server.py +3 -1
  16. tree_sitter_analyzer/language_detector.py +91 -7
  17. tree_sitter_analyzer/languages/css_plugin.py +390 -0
  18. tree_sitter_analyzer/languages/html_plugin.py +395 -0
  19. tree_sitter_analyzer/languages/java_plugin.py +116 -0
  20. tree_sitter_analyzer/languages/javascript_plugin.py +113 -0
  21. tree_sitter_analyzer/languages/markdown_plugin.py +266 -46
  22. tree_sitter_analyzer/languages/python_plugin.py +176 -33
  23. tree_sitter_analyzer/languages/typescript_plugin.py +130 -1
  24. tree_sitter_analyzer/mcp/tools/analyze_scale_tool.py +68 -3
  25. tree_sitter_analyzer/mcp/tools/fd_rg_utils.py +32 -7
  26. tree_sitter_analyzer/mcp/tools/find_and_grep_tool.py +10 -0
  27. tree_sitter_analyzer/mcp/tools/list_files_tool.py +9 -0
  28. tree_sitter_analyzer/mcp/tools/query_tool.py +100 -52
  29. tree_sitter_analyzer/mcp/tools/read_partial_tool.py +98 -14
  30. tree_sitter_analyzer/mcp/tools/search_content_tool.py +9 -0
  31. tree_sitter_analyzer/mcp/tools/table_format_tool.py +37 -13
  32. tree_sitter_analyzer/models.py +53 -0
  33. tree_sitter_analyzer/output_manager.py +1 -1
  34. tree_sitter_analyzer/plugins/base.py +50 -0
  35. tree_sitter_analyzer/plugins/manager.py +5 -1
  36. tree_sitter_analyzer/queries/css.py +634 -0
  37. tree_sitter_analyzer/queries/html.py +556 -0
  38. tree_sitter_analyzer/queries/markdown.py +54 -164
  39. tree_sitter_analyzer/query_loader.py +16 -3
  40. tree_sitter_analyzer/security/validator.py +343 -46
  41. tree_sitter_analyzer/utils/__init__.py +113 -0
  42. tree_sitter_analyzer/utils/tree_sitter_compat.py +282 -0
  43. tree_sitter_analyzer/utils.py +62 -24
  44. {tree_sitter_analyzer-1.7.5.dist-info → tree_sitter_analyzer-1.8.2.dist-info}/METADATA +136 -14
  45. {tree_sitter_analyzer-1.7.5.dist-info → tree_sitter_analyzer-1.8.2.dist-info}/RECORD +47 -38
  46. {tree_sitter_analyzer-1.7.5.dist-info → tree_sitter_analyzer-1.8.2.dist-info}/entry_points.txt +2 -0
  47. {tree_sitter_analyzer-1.7.5.dist-info → tree_sitter_analyzer-1.8.2.dist-info}/WHEEL +0 -0
@@ -398,3 +398,337 @@ class RegexSecurityError(SecurityError):
398
398
  )
399
399
  self.pattern = pattern
400
400
  self.dangerous_construct = dangerous_construct
401
+
402
+
403
+ # MCP-specific exceptions for enhanced error handling
404
+ class MCPToolError(MCPError):
405
+ """Raised when MCP tool execution fails."""
406
+
407
+ def __init__(
408
+ self,
409
+ message: str,
410
+ tool_name: str | None = None,
411
+ input_params: dict[str, Any] | None = None,
412
+ execution_stage: str | None = None,
413
+ **kwargs: Any,
414
+ ) -> None:
415
+ context = kwargs.get("context", {})
416
+ if input_params:
417
+ # Sanitize sensitive information from input params
418
+ sanitized_params = self._sanitize_params(input_params)
419
+ context["input_params"] = sanitized_params
420
+ if execution_stage:
421
+ context["execution_stage"] = execution_stage
422
+
423
+ super().__init__(message, tool_name=tool_name, context=context, **kwargs)
424
+ self.input_params = input_params
425
+ self.execution_stage = execution_stage
426
+
427
+ @staticmethod
428
+ def _sanitize_params(params: dict[str, Any]) -> dict[str, Any]:
429
+ """Sanitize sensitive information from parameters."""
430
+ sanitized = {}
431
+ sensitive_keys = {"password", "token", "key", "secret", "auth", "credential"}
432
+
433
+ for key, value in params.items():
434
+ if any(sensitive in key.lower() for sensitive in sensitive_keys):
435
+ sanitized[key] = "***REDACTED***"
436
+ elif isinstance(value, str) and len(value) > 100:
437
+ sanitized[key] = value[:100] + "...[TRUNCATED]"
438
+ else:
439
+ sanitized[key] = value
440
+
441
+ return sanitized
442
+
443
+
444
+ class MCPResourceError(MCPError):
445
+ """Raised when MCP resource access fails."""
446
+
447
+ def __init__(
448
+ self,
449
+ message: str,
450
+ resource_uri: str | None = None,
451
+ resource_type: str | None = None,
452
+ access_mode: str | None = None,
453
+ **kwargs: Any,
454
+ ) -> None:
455
+ context = kwargs.get("context", {})
456
+ if resource_type:
457
+ context["resource_type"] = resource_type
458
+ if access_mode:
459
+ context["access_mode"] = access_mode
460
+
461
+ super().__init__(message, resource_uri=resource_uri, context=context, **kwargs)
462
+ self.resource_type = resource_type
463
+ self.access_mode = access_mode
464
+
465
+
466
+ class MCPTimeoutError(MCPError):
467
+ """Raised when MCP operation times out."""
468
+
469
+ def __init__(
470
+ self,
471
+ message: str,
472
+ timeout_seconds: float | None = None,
473
+ operation_type: str | None = None,
474
+ **kwargs: Any,
475
+ ) -> None:
476
+ context = kwargs.get("context", {})
477
+ if timeout_seconds:
478
+ context["timeout_seconds"] = timeout_seconds
479
+ if operation_type:
480
+ context["operation_type"] = operation_type
481
+
482
+ super().__init__(message, context=context, **kwargs)
483
+ self.timeout_seconds = timeout_seconds
484
+ self.operation_type = operation_type
485
+
486
+
487
+ class MCPValidationError(ValidationError):
488
+ """Raised when MCP input validation fails."""
489
+
490
+ def __init__(
491
+ self,
492
+ message: str,
493
+ tool_name: str | None = None,
494
+ parameter_name: str | None = None,
495
+ parameter_value: Any | None = None,
496
+ validation_rule: str | None = None,
497
+ **kwargs: Any,
498
+ ) -> None:
499
+ context = kwargs.get("context", {})
500
+ if tool_name:
501
+ context["tool_name"] = tool_name
502
+ if parameter_name:
503
+ context["parameter_name"] = parameter_name
504
+ if validation_rule:
505
+ context["validation_rule"] = validation_rule
506
+
507
+ # Sanitize parameter value for logging
508
+ if parameter_value is not None:
509
+ if isinstance(parameter_value, str) and len(parameter_value) > 200:
510
+ context["parameter_value"] = parameter_value[:200] + "...[TRUNCATED]"
511
+ else:
512
+ context["parameter_value"] = parameter_value
513
+
514
+ super().__init__(message, validation_type="mcp_parameter", context=context, **kwargs)
515
+ self.tool_name = tool_name
516
+ self.parameter_name = parameter_name
517
+ self.validation_rule = validation_rule
518
+
519
+
520
+ class FileRestrictionError(SecurityError):
521
+ """Raised when file access is restricted by mode or security policy."""
522
+
523
+ def __init__(
524
+ self,
525
+ message: str,
526
+ file_path: str | Path | None = None,
527
+ current_mode: str | None = None,
528
+ allowed_patterns: list[str] | None = None,
529
+ **kwargs: Any,
530
+ ) -> None:
531
+ context = kwargs.get("context", {})
532
+ if current_mode:
533
+ context["current_mode"] = current_mode
534
+ if allowed_patterns:
535
+ context["allowed_patterns"] = allowed_patterns
536
+
537
+ super().__init__(
538
+ message,
539
+ security_type="file_restriction",
540
+ file_path=file_path,
541
+ context=context,
542
+ **kwargs
543
+ )
544
+ self.current_mode = current_mode
545
+ self.allowed_patterns = allowed_patterns
546
+
547
+
548
+ # Enhanced error response utilities for MCP
549
+ def create_mcp_error_response(
550
+ exception: Exception,
551
+ tool_name: str | None = None,
552
+ include_debug_info: bool = False,
553
+ sanitize_sensitive: bool = True,
554
+ ) -> dict[str, Any]:
555
+ """
556
+ Create standardized MCP error response dictionary.
557
+
558
+ Args:
559
+ exception: The exception to convert
560
+ tool_name: Name of the MCP tool that failed
561
+ include_debug_info: Whether to include debug information
562
+ sanitize_sensitive: Whether to sanitize sensitive information
563
+
564
+ Returns:
565
+ MCP-compliant error response dictionary
566
+ """
567
+ import traceback
568
+
569
+ response: dict[str, Any] = {
570
+ "success": False,
571
+ "error": {
572
+ "type": exception.__class__.__name__,
573
+ "message": str(exception),
574
+ "timestamp": __import__("datetime").datetime.utcnow().isoformat() + "Z"
575
+ }
576
+ }
577
+
578
+ # Add tool name if provided
579
+ if tool_name:
580
+ response["error"]["tool"] = tool_name
581
+
582
+ # Add context if available
583
+ if hasattr(exception, "context") and exception.context:
584
+ context = exception.context.copy()
585
+
586
+ # Sanitize sensitive information if requested
587
+ if sanitize_sensitive:
588
+ context = _sanitize_error_context(context)
589
+
590
+ response["error"]["context"] = context
591
+
592
+ # Add error code if available
593
+ if hasattr(exception, "error_code"):
594
+ response["error"]["code"] = exception.error_code
595
+
596
+ # Add debug information if requested
597
+ if include_debug_info:
598
+ response["error"]["debug"] = {
599
+ "traceback": traceback.format_exc(),
600
+ "exception_args": list(exception.args) if exception.args else []
601
+ }
602
+
603
+ # Add specific error details for known exception types
604
+ if isinstance(exception, MCPToolError):
605
+ response["error"]["execution_stage"] = exception.execution_stage
606
+ elif isinstance(exception, MCPTimeoutError):
607
+ response["error"]["timeout_seconds"] = exception.timeout_seconds
608
+ elif isinstance(exception, FileRestrictionError):
609
+ response["error"]["current_mode"] = exception.current_mode
610
+ response["error"]["allowed_patterns"] = exception.allowed_patterns
611
+
612
+ return response
613
+
614
+
615
+ def _sanitize_error_context(context: dict[str, Any]) -> dict[str, Any]:
616
+ """Sanitize sensitive information from error context."""
617
+ sanitized = {}
618
+ sensitive_keys = {
619
+ "password", "token", "key", "secret", "auth", "credential",
620
+ "api_key", "access_token", "private_key", "session_id"
621
+ }
622
+
623
+ for key, value in context.items():
624
+ if any(sensitive in key.lower() for sensitive in sensitive_keys):
625
+ sanitized[key] = "***REDACTED***"
626
+ elif isinstance(value, str) and len(value) > 500:
627
+ sanitized[key] = value[:500] + "...[TRUNCATED]"
628
+ elif isinstance(value, (list, tuple)) and len(value) > 10:
629
+ sanitized[key] = list(value[:10]) + ["...[TRUNCATED]"]
630
+ elif isinstance(value, dict) and len(value) > 20:
631
+ # Recursively sanitize nested dictionaries
632
+ truncated_dict = dict(list(value.items())[:20])
633
+ sanitized[key] = _sanitize_error_context(truncated_dict)
634
+ sanitized[key]["__truncated__"] = True
635
+ else:
636
+ sanitized[key] = value
637
+
638
+ return sanitized
639
+
640
+
641
+ # Async exception handling utilities for MCP tools
642
+ async def safe_execute_async(
643
+ coro: Any,
644
+ default_return: Any = None,
645
+ exception_types: tuple[type[Exception], ...] = (Exception,),
646
+ log_errors: bool = True,
647
+ tool_name: str | None = None,
648
+ ) -> Any:
649
+ """
650
+ Safely execute an async function with exception handling.
651
+
652
+ Args:
653
+ coro: Coroutine to execute
654
+ default_return: Value to return on exception
655
+ exception_types: Exception types to catch
656
+ log_errors: Whether to log errors
657
+ tool_name: Name of the tool for error context
658
+
659
+ Returns:
660
+ Coroutine result or default_return on exception
661
+ """
662
+ try:
663
+ return await coro
664
+ except exception_types as e:
665
+ if log_errors:
666
+ from .utils import log_error
667
+
668
+ error_context = {"tool_name": tool_name} if tool_name else {}
669
+ log_error(f"Async execution failed: {e}", extra=error_context)
670
+
671
+ return default_return
672
+
673
+
674
+ def mcp_exception_handler(
675
+ tool_name: str,
676
+ include_debug: bool = False,
677
+ sanitize_sensitive: bool = True,
678
+ ) -> Any:
679
+ """
680
+ Decorator for MCP tool exception handling.
681
+
682
+ Args:
683
+ tool_name: Name of the MCP tool
684
+ include_debug: Whether to include debug information
685
+ sanitize_sensitive: Whether to sanitize sensitive information
686
+ """
687
+ def decorator(func: Any) -> Any:
688
+ async def async_wrapper(*args: Any, **kwargs: Any) -> Any:
689
+ try:
690
+ return await func(*args, **kwargs)
691
+ except Exception as e:
692
+ from .utils import log_error
693
+
694
+ # Log the error with tool context
695
+ log_error(
696
+ f"MCP tool '{tool_name}' failed: {e}",
697
+ extra={"tool_name": tool_name, "exception_type": type(e).__name__}
698
+ )
699
+
700
+ # Return standardized error response
701
+ return create_mcp_error_response(
702
+ e,
703
+ tool_name=tool_name,
704
+ include_debug_info=include_debug,
705
+ sanitize_sensitive=sanitize_sensitive
706
+ )
707
+
708
+ def sync_wrapper(*args: Any, **kwargs: Any) -> Any:
709
+ try:
710
+ return func(*args, **kwargs)
711
+ except Exception as e:
712
+ from .utils import log_error
713
+
714
+ # Log the error with tool context
715
+ log_error(
716
+ f"MCP tool '{tool_name}' failed: {e}",
717
+ extra={"tool_name": tool_name, "exception_type": type(e).__name__}
718
+ )
719
+
720
+ # Return standardized error response
721
+ return create_mcp_error_response(
722
+ e,
723
+ tool_name=tool_name,
724
+ include_debug_info=include_debug,
725
+ sanitize_sensitive=sanitize_sensitive
726
+ )
727
+
728
+ # Return appropriate wrapper based on function type
729
+ if __import__("asyncio").iscoroutinefunction(func):
730
+ return async_wrapper
731
+ else:
732
+ return sync_wrapper
733
+
734
+ return decorator
@@ -8,7 +8,22 @@ This module provides file reading functionality with encoding detection and fall
8
8
  from pathlib import Path
9
9
 
10
10
  from .encoding_utils import read_file_safe
11
- from .utils import log_error, log_info, log_warning
11
+ from .utils import setup_logger
12
+
13
+ # Set up logger for this module
14
+ logger = setup_logger(__name__)
15
+
16
+ def log_error(message: str, *args, **kwargs) -> None:
17
+ """Log error message"""
18
+ logger.error(message, *args, **kwargs)
19
+
20
+ def log_info(message: str, *args, **kwargs) -> None:
21
+ """Log info message"""
22
+ logger.info(message, *args, **kwargs)
23
+
24
+ def log_warning(message: str, *args, **kwargs) -> None:
25
+ """Log warning message"""
26
+ logger.warning(message, *args, **kwargs)
12
27
 
13
28
 
14
29
  def detect_language_from_extension(file_path: str) -> str: