From 16d85c7f2777bde315402a976fae5b2cb80c9454 Mon Sep 17 00:00:00 2001 From: clavedeluna Date: Wed, 27 Dec 2023 11:11:10 -0300 Subject: [PATCH] add remove global at mod level codemod --- .../test_remove_module_global.py | 19 ++++++ src/codemodder/scripts/generate_docs.py | 4 ++ src/core_codemods/__init__.py | 2 + .../docs/pixee_python_remove-module-global.md | 10 +++ src/core_codemods/remove_module_global.py | 22 +++++++ tests/codemods/test_remove_module_global.py | 63 +++++++++++++++++++ tests/samples/module_global.py | 4 ++ 7 files changed, 124 insertions(+) create mode 100644 integration_tests/test_remove_module_global.py create mode 100644 src/core_codemods/docs/pixee_python_remove-module-global.md create mode 100644 src/core_codemods/remove_module_global.py create mode 100644 tests/codemods/test_remove_module_global.py create mode 100644 tests/samples/module_global.py diff --git a/integration_tests/test_remove_module_global.py b/integration_tests/test_remove_module_global.py new file mode 100644 index 000000000..5daf5fca5 --- /dev/null +++ b/integration_tests/test_remove_module_global.py @@ -0,0 +1,19 @@ +from core_codemods.remove_module_global import RemoveModuleGlobal +from integration_tests.base_test import ( + BaseIntegrationTest, + original_and_expected_from_code_path, +) + + +class TestRemoveModuleGlobal(BaseIntegrationTest): + codemod = RemoveModuleGlobal + code_path = "tests/samples/module_global.py" + original_code, _ = original_and_expected_from_code_path(code_path, []) + expected_new_code = """ +price = 25 +print("hello") +price = 30 +""".lstrip() + expected_diff = '--- \n+++ \n@@ -1,4 +1,3 @@\n price = 25\n print("hello")\n-global price\n price = 30\n' + expected_line_change = "3" + change_description = RemoveModuleGlobal.CHANGE_DESCRIPTION diff --git a/src/codemodder/scripts/generate_docs.py b/src/codemodder/scripts/generate_docs.py index e2e77fb63..2aa311afd 100644 --- a/src/codemodder/scripts/generate_docs.py +++ b/src/codemodder/scripts/generate_docs.py @@ -178,6 +178,10 @@ class DocMetadata: importance="Low", guidance_explained="Since literals and new objects have their own identities, comparisons against them using `is` operators are most likely a bug and thus we deem the change safe.", ), + "remove-module-global": DocMetadata( + importance="Low", + guidance_explained="Since the `global` keyword is intended for use in non-module scopes, using it at the module scope is unnecessary.", + ), } diff --git a/src/core_codemods/__init__.py b/src/core_codemods/__init__.py index a4e1cdba0..77895ebac 100644 --- a/src/core_codemods/__init__.py +++ b/src/core_codemods/__init__.py @@ -38,6 +38,7 @@ from .sql_parameterization import SQLQueryParameterization from .exception_without_raise import ExceptionWithoutRaise from .literal_or_new_object_identity import LiteralOrNewObjectIdentity +from .remove_module_global import RemoveModuleGlobal registry = CodemodCollection( origin="pixee", @@ -82,5 +83,6 @@ FlaskJsonResponseType, ExceptionWithoutRaise, LiteralOrNewObjectIdentity, + RemoveModuleGlobal, ], ) diff --git a/src/core_codemods/docs/pixee_python_remove-module-global.md b/src/core_codemods/docs/pixee_python_remove-module-global.md new file mode 100644 index 000000000..409161eea --- /dev/null +++ b/src/core_codemods/docs/pixee_python_remove-module-global.md @@ -0,0 +1,10 @@ +Using the `global` keyword is necessary only when you intend to modify a module-level (aka global) variable within a non-global scope, such as within a class or function. It is unnecessary to call `global` at the module-level. + +Our changes look something like this: + +```diff + price = 25 + print("hello") +- global price + price = 30z +``` diff --git a/src/core_codemods/remove_module_global.py b/src/core_codemods/remove_module_global.py new file mode 100644 index 000000000..e320c4369 --- /dev/null +++ b/src/core_codemods/remove_module_global.py @@ -0,0 +1,22 @@ +import libcst as cst +from libcst.metadata import GlobalScope, ScopeProvider +from typing import Union +from codemodder.codemods.api import BaseCodemod, ReviewGuidance +from codemodder.codemods.utils_mixin import NameResolutionMixin + + +class RemoveModuleGlobal(BaseCodemod, NameResolutionMixin): + NAME = "remove-module-global" + SUMMARY = "Remove Module-level Global Call" + REVIEW_GUIDANCE = ReviewGuidance.MERGE_WITHOUT_REVIEW + DESCRIPTION = "Remove Lines with `global` keyword at Module Level" + REFERENCES: list = [] + + def leave_Global( + self, original_node: cst.Global, _ + ) -> Union[cst.Global, cst.RemovalSentinel,]: + scope = self.get_metadata(ScopeProvider, original_node) + if isinstance(scope, GlobalScope): + self.report_change(original_node) + return cst.RemovalSentinel.REMOVE + return original_node diff --git a/tests/codemods/test_remove_module_global.py b/tests/codemods/test_remove_module_global.py new file mode 100644 index 000000000..12e95f794 --- /dev/null +++ b/tests/codemods/test_remove_module_global.py @@ -0,0 +1,63 @@ +from textwrap import dedent +from core_codemods.remove_module_global import RemoveModuleGlobal +from tests.codemods.base_codemod_test import BaseCodemodTest + + +class TestJwtDecodeVerify(BaseCodemodTest): + codemod = RemoveModuleGlobal + + def test_name(self): + assert self.codemod.name() == "remove-module-global" + + def test_simple(self, tmpdir): + input_code = """\ + price = 25 + global price + """ + expected = """\ + price = 25 + """ + self.run_and_assert(tmpdir, dedent(input_code), dedent(expected)) + assert len(self.file_context.codemod_changes) == 1 + + def test_reassigned(self, tmpdir): + input_code = """\ + price = 25 + print("hello") + global price + price = 30 + """ + expected = """\ + price = 25 + print("hello") + price = 30 + """ + self.run_and_assert(tmpdir, dedent(input_code), dedent(expected)) + assert len(self.file_context.codemod_changes) == 1 + + def test_attr_call(self, tmpdir): + input_code = """\ + class Price: + pass + PRICE = Price() + global PRICE + PRICE.__repr__ + """ + expected = """\ + class Price: + pass + PRICE = Price() + PRICE.__repr__ + """ + self.run_and_assert(tmpdir, dedent(input_code), dedent(expected)) + assert len(self.file_context.codemod_changes) == 1 + + def test_correct_scope(self, tmpdir): + input_code = """\ + price = 25 + def change_price(): + global price + price = 30 + """ + self.run_and_assert(tmpdir, dedent(input_code), dedent(input_code)) + assert len(self.file_context.codemod_changes) == 0 diff --git a/tests/samples/module_global.py b/tests/samples/module_global.py new file mode 100644 index 000000000..ff1d3eb24 --- /dev/null +++ b/tests/samples/module_global.py @@ -0,0 +1,4 @@ +price = 25 +print("hello") +global price +price = 30