facebookincubator / Bowler

Safe code refactoring for modern Python.
https://pybowler.io/
MIT License
1.53k stars 91 forks source link

Templates for common refactorings #109

Open tsoernes opened 4 years ago

tsoernes commented 4 years ago

For example, changing from foo import baz to from bar import baz, a move refactoring seems quite difficult to execute with Bowler, as `select_module('baz¨) also returns usages of . It would be nice to have templates, i.e. functions returning queries, for common refactorings.

mbanders commented 4 years ago

This would be great. Just a handful of common examples would be very helpful to start.

Levitanus commented 4 years ago

Also, a bit confused at the very beginning: for example, here is the diff of two files:

from bowler import Query, Filename

query = Query(
    '.',
    # [lambda name: True if name in ('test.py', 'test2.py') else False],
)
selector = query.select_root('.').select_module('test').select_class('MyClass')
fixer = selector.rename('NewMyClass')
fixer.diff()
--- ./test.py
+++ ./test.py
@@ -1,4 +1,4 @@
-class MyClass:
+class NewMyClass:

     def method(self, arg: str) -> int:
         return int(arg)
--- ./test2.py
+++ ./test2.py
@@ -1,7 +1,7 @@
 from test import MyClass as GoodClass

-class MyClass(GoodClass):
+class NewMyClass(GoodClass):
     pass

--- ./test2.py
+++ ./test2.py
@@ -11,4 +11,4 @@

 cls_ = GoodClass()
 val = cls_.method('43')
-mcls = MyClass()
+mcls = NewMyClass()
[Finished in 0.5s]

I'm confused how to match the class (in the example) across modules, while one-file selection leads to renaming only inside it.

thatch commented 4 years ago

@jreese I think this is a support question for you...

amyreese commented 4 years ago

Unfortunately, you can't chain selectors like that; you either add filters/modifiers after each selector, or the next selector will just override the previous one. This is a limitation of how they are built on top of lib2to3's matching syntax.

Dealing with classes/functions/variables with shared names across multiple modules is where lib2to3 does not make this easy, and Bowler isn't (yet) smart enough to track the origin of each name to know the difference. The most correct way is to use Bowler, and interactively keep or discard individual hunks of the diff based on whether it's referring to the class you want to rename or not. Bowler will at least be sure find all the references to that name for you though.