diff --git a/src/tests/fixers_test.py b/src/tests/fixers_test.py index a5a6695..a1a25d4 100644 --- a/src/tests/fixers_test.py +++ b/src/tests/fixers_test.py @@ -4,11 +4,18 @@ from unittest.mock import MagicMock from tryceratops.fixers import RaiseWithoutCauseFixer, VerboseReraiseFixer +from tryceratops.fixers.exception_block import LoggerErrorFixer from tryceratops.violations import RaiseWithoutCauseViolation, VerboseReraiseViolation, codes +from tryceratops.violations.violations import Violation from .analyzer_helpers import read_sample_lines +def create_violation(code: Tuple[str, str], line: int): + node_mock = MagicMock(spec=ast.Raise, lineno=line) + return Violation(code[0], line, 0, code[1], "filename", node_mock) + + def create_verbose_reraise_violation(code: Tuple[str, str], line: int): node_mock = MagicMock(spec=ast.Raise, lineno=line) return VerboseReraiseViolation(code[0], line, 0, "", "", node_mock, "ex") @@ -43,6 +50,22 @@ def test_verbose_fixer(): assert results[expected_modified_offset].endswith("raise # This is verbose\n") +def test_logger_error_fixer(): + fixer = LoggerErrorFixer() + lines = read_sample_lines("log_error") + expected_modified_line = 15 + expected_modified_offset = expected_modified_line - 1 + violation = create_violation(codes.USE_LOGGING_EXCEPTION, expected_modified_line) + + results = fixer.perform_fix(lines, violation) + + assert_ast_is_valid(results) + assert_unmodified_lines(lines, results, expected_modified_offset) + assert results[expected_modified_offset].endswith( + "logger.exception(\"I'm using 'error', but should be using 'exception'\")\n" + ) + + class TestReraiseWithoutCauseFixer: @pytest.fixture(autouse=True) def setup(self): diff --git a/src/tryceratops/fixers/__init__.py b/src/tryceratops/fixers/__init__.py index 80e69d7..65cd8d5 100644 --- a/src/tryceratops/fixers/__init__.py +++ b/src/tryceratops/fixers/__init__.py @@ -3,12 +3,16 @@ from typing import TYPE_CHECKING, Set, Type from .base import BaseFixer -from .exception_block import RaiseWithoutCauseFixer, VerboseReraiseFixer +from .exception_block import LoggerErrorFixer, RaiseWithoutCauseFixer, VerboseReraiseFixer if TYPE_CHECKING: from tryceratops.filters import GlobalFilter -FIXER_CLASSES: Set[Type[BaseFixer]] = {RaiseWithoutCauseFixer, VerboseReraiseFixer} +FIXER_CLASSES: Set[Type[BaseFixer]] = { + RaiseWithoutCauseFixer, + VerboseReraiseFixer, + LoggerErrorFixer, +} def get_fixers_chain(global_filter: GlobalFilter) -> Set[BaseFixer]: diff --git a/src/tryceratops/fixers/exception_block.py b/src/tryceratops/fixers/exception_block.py index c2de657..64ae433 100644 --- a/src/tryceratops/fixers/exception_block.py +++ b/src/tryceratops/fixers/exception_block.py @@ -3,7 +3,11 @@ from typing import List from tryceratops.violations import codes -from tryceratops.violations.violations import RaiseWithoutCauseViolation, VerboseReraiseViolation +from tryceratops.violations.violations import ( + RaiseWithoutCauseViolation, + VerboseReraiseViolation, + Violation, +) from .base import BaseFixer @@ -63,3 +67,16 @@ def perform_fix(self, lines: List[str], violation: RaiseWithoutCauseViolation) - self._fix_raise_no_cause(all_lines, violation, exception_name) return all_lines + + +class LoggerErrorFixer(BaseFixer[Violation]): + violation_code = codes.USE_LOGGING_EXCEPTION + + def perform_fix(self, lines: List[str], violation: Violation) -> List[str]: + all_lines = lines[:] + + guilty_line = all_lines[violation.line - 1] + new_line = guilty_line.replace(".error(", ".exception(") + all_lines[violation.line - 1] = new_line + + return all_lines