Skip to content

Commit

Permalink
Added exception-without-raise codemod
Browse files Browse the repository at this point in the history
  • Loading branch information
andrecsilva committed Dec 15, 2023
1 parent 906c6ad commit cef2e83
Show file tree
Hide file tree
Showing 9 changed files with 149 additions and 5 deletions.
33 changes: 33 additions & 0 deletions integration_tests/test_exception_without_raise.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
from core_codemods.exception_without_raise import ExceptionWithoutRaise
from integration_tests.base_test import (
BaseIntegrationTest,
original_and_expected_from_code_path,
)


class TestExceptionWithoutRaise(BaseIntegrationTest):
codemod = ExceptionWithoutRaise
code_path = "tests/samples/exception_without_raise.py"
original_code, expected_new_code = original_and_expected_from_code_path(
code_path,
[
(1, """ raise ValueError\n"""),
],
)

# fmt: off
expected_diff =(
"""--- \n"""
"""+++ \n"""
"""@@ -1,4 +1,4 @@\n"""
""" try:\n"""
"""- ValueError\n"""
"""+ raise ValueError\n"""
""" except:\n"""
""" pass\n"""
)
# fmt: on

expected_line_change = "2"
change_description = ExceptionWithoutRaise.CHANGE_DESCRIPTION
num_changed_files = 1
4 changes: 4 additions & 0 deletions src/codemodder/scripts/generate_docs.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,10 @@ class DocMetadata:
importance="Medium",
guidance_explained="This change will only restrict the response type and will not alter the response data itself. Thus we deem it safe.",
),
"exception-without-raise": DocMetadata(
importance="Low",
guidance_explained="An statement with an exception has no effect. Raising the exception is most likely the intended effect and thus we deem it safe.",
),
}


Expand Down
8 changes: 8 additions & 0 deletions src/codemodder/utils/utils.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
import libcst as cst


def list_subclasses(kls) -> set[str]:
l = set()
for subkls in kls.__subclasses__():
l.add(subkls.__name__)
l = l | list_subclasses(subkls)
return l


def clean_simplestring(node: cst.SimpleString | str) -> str:
if isinstance(node, str):
return node.strip('"')
Expand Down
6 changes: 4 additions & 2 deletions src/core_codemods/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
from codemodder.registry import CodemodCollection
from core_codemods.numpy_nan_equality import NumpyNanEquality
from core_codemods.sql_parameterization import SQLQueryParameterization

from .django_debug_flag_on import DjangoDebugFlagOn
from .django_session_cookie_secure_off import DjangoSessionCookieSecureOff
Expand Down Expand Up @@ -34,6 +32,9 @@
from .django_receiver_on_top import DjangoReceiverOnTop
from .django_json_response_type import DjangoJsonResponseType
from .flask_json_response_type import FlaskJsonResponseType
from .numpy_nan_equality import NumpyNanEquality
from .sql_parameterization import SQLQueryParameterization
from .exception_without_raise import ExceptionWithoutRaise

registry = CodemodCollection(
origin="pixee",
Expand Down Expand Up @@ -74,5 +75,6 @@
NumpyNanEquality,
DjangoJsonResponseType,
FlaskJsonResponseType,
ExceptionWithoutRaise,
],
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
An statement with an exception without raising it is most likely an error. Our changes look something like this:

```diff
try:
- ValueError
+ raise ValueError
except:
pass
```
41 changes: 41 additions & 0 deletions src/core_codemods/exception_without_raise.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
from typing import Union
import libcst as cst
from codemodder.codemods.api import BaseCodemod
from codemodder.codemods.base_codemod import ReviewGuidance

from codemodder.codemods.utils_mixin import NameResolutionMixin
from codemodder.utils.utils import list_subclasses


class ExceptionWithoutRaise(BaseCodemod, NameResolutionMixin):
NAME = "exception-without-raise"
SUMMARY = "Added raise statement to exception creation"
REVIEW_GUIDANCE = ReviewGuidance.MERGE_WITHOUT_REVIEW
DESCRIPTION = SUMMARY
REFERENCES = [
{
"url": "https://docs.python.org/3/tutorial/errors.html#raising-exceptions",
"description": "",
},
]
CHANGE_DESCRIPTION = "Wrapped exception in a raise statement"

def leave_SimpleStatementLine(
self,
original_node: cst.SimpleStatementLine,
updated_node: cst.SimpleStatementLine,
) -> Union[
cst.BaseStatement, cst.FlattenSentinel[cst.BaseStatement], cst.RemovalSentinel
]:
match original_node:
case cst.SimpleStatementLine(
body=[cst.Expr(cst.Name() | cst.Attribute() as name)]
):
true_name = self.find_base_name(name)
if true_name:
true_name = true_name.split(".")[-1]
all_exceptions = list_subclasses(BaseException)
if true_name in all_exceptions:
self.report_change(original_node)
return updated_node.with_changes(body=[cst.Raise(exc=name)])
return updated_node
3 changes: 0 additions & 3 deletions src/core_codemods/numpy_nan_equality.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
from collections import namedtuple
import libcst as cst
from libcst import UnaryOperation
from codemodder.codemods.api import BaseCodemod
from codemodder.codemods.base_codemod import ReviewGuidance

from codemodder.codemods.utils_mixin import NameResolutionMixin

NodeWithTrueName = namedtuple("NodeWithTrueName", ["node", "name"])


class NumpyNanEquality(BaseCodemod, NameResolutionMixin):
NAME = "numpy-nan-equality"
Expand Down
46 changes: 46 additions & 0 deletions tests/codemods/test_exception_without_raise.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
from tests.codemods.base_codemod_test import BaseCodemodTest
from core_codemods.exception_without_raise import ExceptionWithoutRaise
from textwrap import dedent


class TestExceptionWithoutRaise(BaseCodemodTest):
codemod = ExceptionWithoutRaise

def test_name(self):
assert self.codemod.name() == "exception-without-raise"

def test_simple(self, tmpdir):
input_code = """\
ValueError
"""
expected = """\
raise ValueError
"""
self.run_and_assert(tmpdir, dedent(input_code), dedent(expected))
assert len(self.file_context.codemod_changes) == 1

def test_alias(self, tmpdir):
input_code = """\
from libcst import CSTValidationError as error
error
"""
expected = """\
from libcst import CSTValidationError as error
raise error
"""
self.run_and_assert(tmpdir, dedent(input_code), dedent(expected))
assert len(self.file_context.codemod_changes) == 1

def test_unknown_exception(self, tmpdir):
input_code = """\
Something
"""
self.run_and_assert(tmpdir, dedent(input_code), dedent(input_code))
assert len(self.file_context.codemod_changes) == 0

def test_raised_exception(self, tmpdir):
input_code = """\
raise ValueError
"""
self.run_and_assert(tmpdir, dedent(input_code), dedent(input_code))
assert len(self.file_context.codemod_changes) == 0
4 changes: 4 additions & 0 deletions tests/samples/exception_without_raise.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
try:
ValueError
except:
pass

0 comments on commit cef2e83

Please sign in to comment.