diff --git a/integration_tests/test_fix_deprecated_pkg_resources.py b/integration_tests/test_fix_deprecated_pkg_resources.py new file mode 100644 index 00000000..15d371e6 --- /dev/null +++ b/integration_tests/test_fix_deprecated_pkg_resources.py @@ -0,0 +1,20 @@ +from codemodder.codemods.test import BaseIntegrationTest +from core_codemods.fix_deprecated_pkg_resources import FixDeprecatedPkgResources + + +class TestFixDeprecatedPkgResources(BaseIntegrationTest): + codemod = FixDeprecatedPkgResources + original_code = """ + import pkg_resources + + dist = pkg_resources.get_distribution("Django") + dist.location + version = dist.version + """ + replacement_lines = [ + (1, "from importlib.metadata import distribution\n"), + (3, 'dist = distribution("Django")\n'), + ] + expected_diff = '--- \n+++ \n@@ -1,4 +1,4 @@\n import tempfile\n \n-tempfile.mktemp()\n+tempfile.mkstemp()\n var = "hello"\n' + expected_line_change = "3" + change_description = FixDeprecatedPkgResources.change_description diff --git a/src/core_codemods/__init__.py b/src/core_codemods/__init__.py index d260df3e..0b9b44d4 100644 --- a/src/core_codemods/__init__.py +++ b/src/core_codemods/__init__.py @@ -19,6 +19,7 @@ from .fix_dataclass_defaults import FixDataclassDefaults from .fix_deprecated_abstractproperty import FixDeprecatedAbstractproperty from .fix_deprecated_logging_warn import FixDeprecatedLoggingWarn +from .fix_deprecated_pkg_resources import FixDeprecatedPkgResources from .fix_empty_sequence_comparison import FixEmptySequenceComparison from .fix_float_equality import FixFloatEquality from .fix_hasattr_call import TransformFixHasattrCall @@ -141,6 +142,7 @@ TransformFixHasattrCall, FixDataclassDefaults, FixMissingSelfOrCls, + FixDeprecatedPkgResources, ], ) diff --git a/src/core_codemods/django_debug_flag_on.py b/src/core_codemods/django_debug_flag_on.py index c9192ee6..9b753bd8 100644 --- a/src/core_codemods/django_debug_flag_on.py +++ b/src/core_codemods/django_debug_flag_on.py @@ -19,9 +19,9 @@ class DjangoDebugFlagOn(SimpleCodemod): ], ) change_description = "Flip `Django` debug flag to off." - detector_pattern = """ + detector_pattern = f""" rules: - - id: django-debug-flag-on + - id: {metadata.name} pattern: DEBUG = True paths: include: diff --git a/src/core_codemods/django_session_cookie_secure_off.py b/src/core_codemods/django_session_cookie_secure_off.py index 34f086dd..11703d98 100644 --- a/src/core_codemods/django_session_cookie_secure_off.py +++ b/src/core_codemods/django_session_cookie_secure_off.py @@ -19,9 +19,9 @@ class DjangoSessionCookieSecureOff(SimpleCodemod): ], ) change_description = "Sets Django's `SESSION_COOKIE_SECURE` flag if off or missing." - detector_pattern = """ + detector_pattern = f""" rules: - - id: django-session-cookie-secure-off + - id: {metadata.name} # This pattern creates one finding with no text for settings.py file. pattern-regex: ^ paths: diff --git a/src/core_codemods/docs/pixee_python_fix-deprecated-pkg-resources.md b/src/core_codemods/docs/pixee_python_fix-deprecated-pkg-resources.md new file mode 100644 index 00000000..e69de29b diff --git a/src/core_codemods/fix_deprecated_pkg_resources.py b/src/core_codemods/fix_deprecated_pkg_resources.py new file mode 100644 index 00000000..fa34617f --- /dev/null +++ b/src/core_codemods/fix_deprecated_pkg_resources.py @@ -0,0 +1,31 @@ +import libcst as cst + +from codemodder.codemods.utils_mixin import NameResolutionMixin +from core_codemods.api import Metadata, Reference, ReviewGuidance, SimpleCodemod + + +class FixDeprecatedPkgResources(SimpleCodemod, NameResolutionMixin): + metadata = Metadata( + name="fix-deprecated-pkg-resources", + summary="Replace Deprecated Use of `pkg_resources` Module`", + review_guidance=ReviewGuidance.MERGE_WITHOUT_REVIEW, + references=[ + Reference( + url="TODOOOOhttps://docs.python.org/3/library/logging.html#logging.Logger.warning" + ), + ], + ) + change_description = "Replace deprecated `logging.warn` with `logging.warning`" + detector_pattern = f""" + rules: + - id: {metadata.name} + pattern: pkg_resources.get_distribution(...) + pattern-inside: | + import pkg_resources + ... + """ + + def on_result_found(self, original_node, updated_node): + self.remove_unused_import(original_node) + self.add_needed_import("importlib.metadata", "distribution") + return updated_node.with_changes(func=cst.Name(value="distribution")) diff --git a/src/core_codemods/limit_readline.py b/src/core_codemods/limit_readline.py index 81c888f2..a131697f 100644 --- a/src/core_codemods/limit_readline.py +++ b/src/core_codemods/limit_readline.py @@ -15,9 +15,9 @@ class LimitReadline(SimpleCodemod): ], ) change_description = "Adds a size limit argument to readline() calls." - detector_pattern = """ + detector_pattern = f""" rules: - - id: limit-readline + - id: {metadata.name} mode: taint pattern-sources: - pattern-either: diff --git a/src/core_codemods/secure_flask_cookie.py b/src/core_codemods/secure_flask_cookie.py index 53bfe956..bb2ab666 100644 --- a/src/core_codemods/secure_flask_cookie.py +++ b/src/core_codemods/secure_flask_cookie.py @@ -17,9 +17,9 @@ class SecureFlaskCookie(SimpleCodemod, SecureCookieMixin): ], ) change_description = "Flask response `set_cookie` call should be called with `secure=True`, `httponly=True`, and `samesite='Lax'`." - detector_pattern = """ + detector_pattern = f""" rules: - - id: secure-flask-cookie + - id: {metadata.name} mode: taint pattern-sources: - pattern-either: diff --git a/tests/codemods/test_fix_deprecated_pkg_resources.py b/tests/codemods/test_fix_deprecated_pkg_resources.py new file mode 100644 index 00000000..57ff9f23 --- /dev/null +++ b/tests/codemods/test_fix_deprecated_pkg_resources.py @@ -0,0 +1,114 @@ +import pytest + +from codemodder.codemods.test import BaseSemgrepCodemodTest +from core_codemods.fix_deprecated_pkg_resources import FixDeprecatedPkgResources + + +class TestFixDeprecatedPkgResources(BaseSemgrepCodemodTest): + codemod = FixDeprecatedPkgResources + + @pytest.mark.parametrize( + "input_code,expected_output", + [ + ( + """ + import pkg_resources + dist = pkg_resources.get_distribution("package_name") + """, + """ + from importlib.metadata import distribution + + dist = distribution("package_name") + """, + ), + ( + """ + import pkg_resources + version = pkg_resources.get_distribution("package_name").version + """, + """ + from importlib.metadata import distribution + + version = distribution("package_name").version + """, + ), + ], + ) + def test_import(self, tmpdir, input_code, expected_output): + self.run_and_assert(tmpdir, input_code, expected_output) + + @pytest.mark.parametrize( + "input_code,expected_output", + [ + ( + """ + from pkg_resources import get_distribution + dist = get_distribution("package_name") + """, + """ + from importlib.metadata import distribution + + dist = distribution("package_name") + """, + ), + ( + """ + from pkg_resources import get_distribution + version = get_distribution("package_name").version + """, + """ + from importlib.metadata import distribution + + version = distribution("package_name").version + """, + ), + ], + ) + def test_from_import(self, tmpdir, input_code, expected_output): + self.run_and_assert(tmpdir, input_code, expected_output) + + @pytest.mark.parametrize( + "input_code,expected_output", + [ + ( + """ + from pkg_resources import get_distribution as get + dist = get("package_name") + """, + """ + from importlib.metadata import distribution + + dist = distribution("package_name") + """, + ), + ( + """ + from pkg_resources import get_distribution as get + version = get("package_name").version + """, + """ + from importlib.metadata import distribution + + version = distribution("package_name").version + """, + ), + ], + ) + def test_import_alias(self, tmpdir, input_code, expected_output): + self.run_and_assert(tmpdir, input_code, expected_output) + + def test_multiple(self, tmpdir): + original_code = """ + import pkg_resources + dist = pkg_resources.get_distribution("Django") + dist.location + version = dist.version + """ + expected_output = """ + from importlib.metadata import distribution + + dist = distribution("Django") + dist.location + version = dist.version + """ + self.run_and_assert(tmpdir, original_code, expected_output)