diff --git a/CHANGELOG.md b/CHANGELOG.md index 010425d5d..834eacdbb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,9 @@ # **Upcoming release** +- #811 Preserve still-used imports when moving globals with filtered import cleanup - #850 Update and pin black version in pre-commit and Github Actions - #851 Bump supported python version to up to Python 3.14 -- #852 Implement patchedast handlers for TypeAlias +- #852 Implement patchedast handlers for TypeAlias - #853 Implement patchedast handlers TypeVar - #847 Avoid printing autoimport syntax errors (@yangfan-yf-yf) - #623, #819, #863 Support MatchOr, MatchSequence, MatchStar (@jheld, @lieryan) diff --git a/rope/refactor/importutils/actions.py b/rope/refactor/importutils/actions.py index 47e6e8c7f..37d345360 100644 --- a/rope/refactor/importutils/actions.py +++ b/rope/refactor/importutils/actions.py @@ -175,6 +175,11 @@ def visitFromImport(self, import_stmt, import_info): ): if import_info.is_star_import(): return True + if import_stmt.readonly: + return not self.import_info.is_star_import() and all( + pair in import_info.names_and_aliases + for pair in self.import_info.names_and_aliases + ) if self.import_info.is_star_import(): import_stmt.import_info = self.import_info return True diff --git a/ropetest/refactor/importutilstest.py b/ropetest/refactor/importutilstest.py index 845bebd5b..a9a482a86 100644 --- a/ropetest/refactor/importutilstest.py +++ b/ropetest/refactor/importutilstest.py @@ -1,8 +1,13 @@ import unittest from textwrap import dedent -from rope.base.prefs import get_preferred_import_style, ImportStyle, Prefs, ImportPrefs -from rope.base.prefs import DEFAULT_IMPORT_STYLE +from rope.base.prefs import ( + DEFAULT_IMPORT_STYLE, + ImportPrefs, + ImportStyle, + Prefs, + get_preferred_import_style, +) from rope.refactor.importutils import ImportTools, add_import, importinfo from ropetest import testutils @@ -1943,6 +1948,30 @@ def import_filter(import_stmt): return import_filter + def test_filtered_duplicate_removal_preserves_distinct_from_imports(self): + for imported in ("mod3", "mod3 as other", "*"): + with self.subTest(imported=imported): + source = "from pkg2 import mod2\nfrom pkg2 import " + imported + "\n" + self.mod.write(source) + pymod = self.project.get_pymodule(self.mod) + module_imports = self.import_tools.module_imports( + pymod, self._line_filter(2) + ) + module_imports.remove_duplicates() + self.assertEqual(source, module_imports.get_changed_source()) + + def test_filtered_duplicate_removal_still_removes_covered_imports(self): + for imported in ("mod2", "mod2, mod3", "*"): + with self.subTest(imported=imported): + expected = "from pkg2 import " + imported + "\n" + self.mod.write(expected + "from pkg2 import mod2\n") + pymod = self.project.get_pymodule(self.mod) + module_imports = self.import_tools.module_imports( + pymod, self._line_filter(2) + ) + module_imports.remove_duplicates() + self.assertEqual(expected, module_imports.get_changed_source()) + def test_filtered_expand_stars(self): self.pkg1.get_child("__init__.py").write("var1 = 1\n") self.pkg2.get_child("__init__.py").write("var2 = 1\n") diff --git a/ropetest/refactor/movetest.py b/ropetest/refactor/movetest.py index 1bf07655d..deb511ba3 100644 --- a/ropetest/refactor/movetest.py +++ b/ropetest/refactor/movetest.py @@ -8,9 +8,8 @@ from rope.refactor import move from ropetest import testutils - if TYPE_CHECKING: - from rope.base import resources, project + from rope.base import project, resources class MoveRefactoringTest(unittest.TestCase): project: project.Project @@ -74,6 +73,29 @@ def test_move_constant(self) -> None: self.assertEqual("", self.origin_module.read()) self.assertEqual("foo = 123\n", self.destination_module.read()) + def test_move_constant_preserves_used_module_alias(self) -> None: + self.project.prefs["prefer_module_from_imports"] = True + self.mod4.write("def keep():\n return 1\n\nvalue = 2\n") + self.mod5.write("def existing():\n return 3\n") + self.mod3.write( + "from pkg import mod5\n" + "from pkg import mod4 as utils_module\n" + "\n" + "def run():\n" + " return mod5.existing(), utils_module.keep(), utils_module.value\n" + ) + self._move(self.mod4, self.mod4.read().index("value"), self.mod5) + self.assertEqual( + "from pkg import mod5\n" + "from pkg import mod4 as utils_module\n" + "\n" + "def run():\n" + " return mod5.existing(), utils_module.keep(), mod5.value\n", + self.mod3.read(), + ) + self.assertEqual("def keep():\n return 1\n\n", self.mod4.read()) + self.assertIn("value = 2\n", self.mod5.read()) + def test_move_constant_2(self) -> None: self.origin_module.write("bar = 321\nfoo = 123\n") self._move(self.origin_module, self.origin_module.read().index("foo") + 1, self.destination_module) @@ -603,7 +625,7 @@ def test_moving_modules_and_removing_out_of_date_froms(self) -> None: dedent("""\ import mod4 print(mod4)""" - ), + ), self.origin_module.read(), ) @@ -1423,7 +1445,7 @@ def foo(): dedent("""\ def hello(func): return func - """), + """), self.origin_module.read(), ) self.assertEqual(