Anonymous avatar Anonymous committed fa422f6

Added move method refactoring

Comments (0)

Files changed (16)

 * Using as a library: `docs/dev/library.txt`_
 * Contributing: `docs/dev/contributing.txt`_
 
+If you don't like rope's default emacs-like keybinding, edit the
+default ``~/.rope`` file (created the first time you start rope) and
+change `i_like_emacs` variable to `False`.
+
 
 Project Road Map
 ================

docs/dev/done.txt

 ===========
 
 
+- Moving methods : February 21, 2007
+
+
 > Public Release 0.5m1 : February 18, 2007
 
 

docs/dev/issues.txt

 Hot Topics
 ==========
 
-* `Extended rename`_
+* `Change occurrences`_
 
 
 To Be Discussed
 * `Allowing non-existent resources`_
 * `Python's implicit interfaces`_
 * `Having virtual PyModules`_
-* `Redefining Function/Class Definitions`_
+* `Redefining function/class definitions`_
 * `Better status bar`_
 * Indexing source files for faster occurrence finding
 * Faster module running
 * Finding available refactorings
 
 
-Extended Rename
-===============
+Change Occurrences
+==================
 
 We want to customize these parameters when performing rename
 refactoring:
 
-* The region or files to apply rename
+* The region rename
 * Should we replace primaries
 * Performing only on calls
-* Change imports
-* Change definition
-* Only reads or writes
+* It is completely side-effect free
+* Only reads or writes?
+* Change imports?
+* Change definitions?
 
-After adding it we should be able to remove local rename.
-Refactorings that can be done using extended rename:
+Refactorings that can be done:
 
 * Introduce parameter
-* Move field
 * Local variable to field
+* Handle long imports
+* Change references to a name to another name
+* Changing imported module
+* Encapsulate field
+* Introduce factory
 * ...
 
 Issues:
 
+* Removing local rename?
 * How specify a region in a file
 
+  * Always use the current scope
   * Using physical scopes; The mark should be on a defined object
   * Store the last place of mark for each editor;  the region
     between the two last marks is the region
+  * ``C-u # C-r r o`` where # is the number of scopes outside
+    current scope
 
 * The defaults should be local rename?
-* The keybinding; ``C-c r e`` or ``C-u C-c r r``
 * Do we rename modules and packages, too
 * What about defined objects
 * Using another name
   * ``Switch Occurrences``; ``C-c r w``
 
 
-Checking Varible And Function Usages In Their Defining Scopes
-=============================================================
+Checking Variable And Function Usages In Their Defining Scopes
+==============================================================
 
 ::
 
 
 Maybe we can use one of these solutions:
 
-* Checking pyname equallity only once for each scope
+* Checking pyname equality only once for each scope
 
   This way we check each name only once for each scope.  But this is
   not applicable for attributes.  What's more `PyName`\s seem to

docs/dev/stories.txt

 * Extracting methods from pieces with only one return/yield statement
 
 
-* Inlining a single occurrence
-
-
 * Asking whether to encapsulate field in the class itself
 
 
 * Analyzing function decorators
 
 
-* Performing import actions only on one import statement
-
-
 * Extract class
 
 
 > Public Release 0.5m2 : March 4, 2007
 
 
-* Extended rename
+* Inlining a single occurrence
 
 
-* Moving methods
+* Performing import actions only on one import statement
+
+
+* Change occurrences

docs/dev/workingon.txt

-Moving Methods
-==============
+Small Stories
+=============
 
-- `sourceutils.get_body()`
-- Add a `create_move()`
-- Passing parameters
-- Passing the main object whenever needed
-- Arg with default parameter
-- Passing args and keywords args
-- Getting new method
-- Getting old method
-- Making the change
-- If both classes are in the same file
+- Moving Methods
 
-* Adding to the UI
-* Adding methods to a function with ``pass`` body
-* Destination attribute
-* Exceptional conditions
-* Refactor similarities between method_object and move for
-  renaming ``self``\s and method signature
-* What to do about pydocs?
-* Separate `MoveModule` and `MoveGlobal`
+  - Adding to the UI
+  - Separating `MoveModule` and `MoveGlobal`
+  - Nonexistent attribute
+  - Unknown attribute type
+  - Adding imports? Only when moving to another module
+  - Adding methods to a class with ``pass`` body
 
-* Inlining the last method of a class
+- Default name for new method
+- Inlining the last method of a class
+- In the ``README.txt`` mention how to change the keybinding
+
+* A completing text widget for dialogs; `Completing(name, list, handle)`
 * Do `get_body()` and `get_body_region()` belong to `PyFunction`?
 * Does `get_definition_info()` belong to `PyFunction`?
 * Moving static and class methods
-* Moving methods with decorators
+* Inlining methods with decorators
 * Decorators and method refactorings
-* Changing `rope.refactor` to use `sourceutils.add_methods` wherever possible
 * Unifying ``x.get_type() == get_base_type('y')``, ``isinstance(x, PyY)``
-* ``M-a`` and ``M-e``
 * Finding unused variables
+* Document how to move fields
+* Adding codeassist templates in ``~/.rope``?
 
 
 Remaining Small Stories
 =======================
 
-Main:
+Base:
 
-* Separating UI and functional tests from unit tests
-* Goto definition for ``"# comment.\na_var"``
-* Showing properties in quick outline
 * Docs for builtin functions and properties
 * Lambdas as functions; consider their parameters
-* Changing ``C-a C-a`` to move to the first character in the line
-* Changing open project dialog
-* Saving a file that doesn't exist; Allowing nonexistent resources
-
-Others:
-
-* Comments should be indented
-* In the ``README.txt`` mention how to change the keybinding
 * Sorting names in from import list
 * When running `inlinetest` modules we get
   ``Exception exceptions.SystemError: 'error return without exception set'
     in <generator object at 0xb7173aec> ignored``
 * Only saving diffs in `ChangeContents`
 * Undoing `RemoveResource`; It's not used by refactorings
-* Decide when to use `difflib` in `Editor.set_text` based on the
-  number of changes
 * Caching calculated parameters and returned object in `PyFunction`\s
 * Allowing running code to call rope functions while rope is running?
 * Importing star and removing self imports; stack overflow
 * Extract constant
 * `PyClass.superclasses` should be concluded data
+* Handling `AssList` for inline variable and encapsulate field
+* Import addition when adding a relative to an absolute import
+
+UI and IDE:
+
+* Goto definition for ``"# comment.\na_var"``
+* Showing properties in quick outline
+* Changing ``C-a C-a`` to move to the first character in the line
+* ``M-a`` and ``M-e``
+* Changing open project dialog
+* Comments should be indented
+* Decide when to use `difflib` in `Editor.set_text` based on the
+  number of changes
+* Better move dialog; complete modules names; use `editor._CompletionListHandle`
+
+Others:
+
+* Separating UI and functional tests from unit tests
 * Better `ropetest` package structure
-* Handling `AssList` for inline variable and encapsulate field
-* Better move dialog; complete modules names; use `editor._CompletionListHandle`
-* Import addition when adding a relative to an absolute import
   * Rename class/function/module/package/method/variable/keyword arg
   * Extract method
   * Extract variable
-  * Move class/function/module/package
+  * Move class/function/module/package/method
   * Inline method/local variable
   * Change method signature
   * Introduce factory method

docs/user/overview.txt

 rename in file              C-c r e
 convert local to field
 inline argument default
-module to package
-rename current module
-move current package
+module to package           C-c r 1 p
+rename current module       C-c r 1 r
+move current package        C-c r 1 v
 --------------------------  -------------  -------------
 organize imports            C-c i o        C-O
 expand star imports         C-c i x

rope/ide/codeassist.py

 
 import rope.base.codeanalyze
 import rope.base.pyobjects
-from rope.base.exceptions import RopeError
 from rope.base.codeanalyze import (StatementRangeFinder, ArrayLinesAdapter,
                                    WordRangeFinder, ScopeNameFinder,
                                    SourceLinesAdapter)
+from rope.base.exceptions import RopeError
 from rope.refactor import occurrences, functionutils
 
 
     def _get_default_templates(self):
         result = []
         result.append(TemplateProposal('main', Template("if __name__ == '__main__':\n    ${cursor}\n")))
-        test_case_template = "import unittest\n\n"+ \
-                             "class ${class}(unittest.TestCase):\n\n" + \
-                             "    def setUp(self):\n        super(${class}, self).setUp()\n\n" + \
-                             "    def tearDown(self):\n        super(${class}, self).tearDown()\n\n" + \
-                             "    def test_${aspect1}(self):\n        pass${cursor}\n\n\n" + \
-                             "if __name__ == '__main__':\n" + \
-                             "    unittest.main()\n"
+        test_case_template = \
+            ("import unittest\n\n"
+             "class ${class}(unittest.TestCase):\n\n"
+             "    def setUp(self):\n        super(${class}, self).setUp()\n\n"
+             "    def tearDown(self):\n        super(${class}, self).tearDown()\n\n"
+             "    def test_${aspect1}(self):\n        pass${cursor}\n\n\n"
+             "if __name__ == '__main__':\n"
+             "    unittest.main()\n")
         result.append(TemplateProposal('testcase', Template(test_case_template)))
         result.append(TemplateProposal('hash', Template('\n    def __hash__(self):\n' + \
                                                         '        return 1${cursor}\n')))

rope/refactor/inline.py

         result = _InlineFunctionCallsForModule(
             self.occurrence_finder, self.resource,
             self.definition_generator,
-            start_offset, end_offset).get_changed_module()
+            start_offset, end_offset,
+            self._get_method_replacement()).get_changed_module()
         changes.add_change(ChangeContents(self.resource, result))
 
+    def _get_method_replacement(self):
+        if self._is_the_last_method_of_a_class():
+            indents = sourceutils.get_indents(
+                self.pymodule.lines, self.pyfunction.get_scope().get_start())
+            return ' ' * indents + 'pass\n'
+        return ''
+
+    def _is_the_last_method_of_a_class(self):
+        pyclass = self.pyfunction.parent
+        if not isinstance(pyclass, rope.base.pyobjects.PyClass):
+            return False
+        class_start, class_end = sourceutils.get_body_region(pyclass)
+        source = self.pymodule.source_code
+        lines = self.pymodule.lines
+        scope = self.pyfunction.get_scope()
+        func_start = lines.get_line_start(scope.get_start())
+        func_end = lines.get_line_end(scope.get_end())
+        if source[class_start:func_start].strip() == '' and \
+           source[func_end:class_end].strip() == '':
+            return True
+        return False
+
     def _change_other_files(self, changes):
         for file in self.pycore.get_python_files():
             if file == self.resource:
 class _InlineFunctionCallsForModule(object):
 
     def __init__(self, occurrence_finder, resource, definition_generator,
-                 skip_start=0, skip_end=0):
+                 skip_start=0, skip_end=0, replacement=''):
         self.pycore = occurrence_finder.pycore
         self.occurrence_finder = occurrence_finder
         self.generator = definition_generator
         self._source = None
         self.skip_start = skip_start
         self.skip_end = skip_end
+        self.replacement = replacement
 
     def get_changed_module(self):
         change_collector = sourceutils.ChangeCollector(self.source)
-        change_collector.add_change(self.skip_start, self.skip_end, '')
+        change_collector.add_change(self.skip_start, self.skip_end,
+                                    self.replacement)
         for occurrence in self.occurrence_finder.find_occurrences(self.resource):
             start, end = occurrence.get_primary_range()
             if self.skip_start <= start < self.skip_end:
                 line_start, end, sourceutils.fix_indentation(definition, indents))
             if returns:
                 name = self.generator.get_returned()
+                if name is None:
+                    name = 'None'
                 change_collector.add_change(
                     line_end, end, self.source[line_start:start] + name +
                     self.source[end_parens:end])

rope/refactor/method_object.py

     def _get_body(self):
         body = sourceutils.get_body(self.pyfunction)
         for param in self._get_parameter_names():
-            body = param + ' = 1\n' + body
+            body = param + ' = None\n' + body
             pymod = self.pycore.get_string_module(body, self.resource)
             pyname = pymod.get_attribute(param)
             finder = occurrences.FilteredOccurrenceFinder(
             body = result[result.index('\n') + 1:]
         return body
 
-
     def _get_init(self):
         params = self._get_parameter_names()
         if not params:

rope/refactor/move.py

-import rope.base.exceptions
-from rope.base import pyobjects, codeanalyze
+from rope.base import pyobjects, codeanalyze, exceptions, pynames
 from rope.base.change import ChangeSet, ChangeContents, MoveResource
 from rope.refactor import (importutils, rename, occurrences,
                            sourceutils, functionutils)
 
 
 def create_move(project, resource, offset=None):
-    """A factory for creating Move objects"""
+    """A factory for creating Move objects
+
+    Based on `resource` and `offset`, return one of `MoveModule`,
+    `MoveGlobal` or `MoveMethod` for performing move refactoring.
+
+    """
     if offset is None:
-        return Move(project, resource)
+        return MoveModule(project, resource)
     pyname = codeanalyze.get_pyname_at(project.pycore, resource, offset)
     if pyname is None:
-        raise rope.base.exceptions.RefactoringError(
+        raise exceptions.RefactoringError(
             'Move only works on classes, functions, modules and methods.')
     pyobject = pyname.get_object()
     if isinstance(pyobject, pyobjects.PyModule) or \
        isinstance(pyobject, pyobjects.PyPackage):
-        return Move(project, pyobject.get_resource())
+        return MoveModule(project, pyobject.get_resource())
     if isinstance(pyobject, pyobjects.PyFunction) and \
        isinstance(pyobject.parent, pyobjects.PyClass):
         return MoveMethod(project, resource, offset)
-    return Move(project, resource, offset)
+    if isinstance(pyobject, pyobjects.PyDefinedObject) and \
+       isinstance(pyobject.parent, pyobjects.PyModule):
+        return MoveGlobal(project, resource, offset)
+    raise exceptions.RefactoringError(
+        'Move only works on global classes/functions, modules and methods.')
 
 
 class MoveMethod(object):
+    """For moving methods
+
+    It makes a new method in the destination class and changes
+    the body of the old method to call the new method.  You can
+    inline the old method to change all of its occurrences.
+
+    """
 
     def __init__(self, project, resource, offset):
         self.pycore = project.pycore
         self.pyfunction = pyname.get_object()
 
     def get_changes(self, dest_attr, new_name):
+        """Return the changes needed for this refactoring
+
+        :parameters:
+            - `dest_attr`: the name of the destination attribute
+            - `new_name`: the name of the new method
+
+        """
         changes = ChangeSet('Moving method <%s>' % self.method_name)
         resource1, start1, end1, new_content1 = \
             self._get_changes_made_by_old_class(dest_attr, new_name)
         else:
             collector2 = sourceutils.ChangeCollector(resource2.read())
             collector2.add_change(start2, end2, new_content2)
-            changes.add_change(ChangeContents(resource2,
-                                              collector2.get_changed()))
+            result = collector2.get_changed()
+            import_tools = importutils.ImportTools(self.pycore)
+            new_imports = self._get_used_imports(import_tools)
+            if new_imports:
+                goal_pymodule = self.pycore.get_string_module(result, resource2)
+                result = _add_imports_to_module(import_tools, goal_pymodule,
+                                                new_imports)
+            changes.add_change(ChangeContents(resource2, result))
+                
         changes.add_change(ChangeContents(resource1,
                                           collector1.get_changed()))
         return changes
 
+    def get_method_name(self):
+        return self.method_name
+
+    def _get_used_imports(self, import_tools):
+        module_imports = import_tools.get_module_imports(
+            self.pyfunction.get_module())
+        return module_imports.get_used_imports(self.pyfunction)
+
     def _get_changes_made_by_old_class(self, dest_attr, new_name):
         pymodule = self.pyfunction.get_module()
         indents = sourceutils.get_indents(
 
     def _get_changes_made_by_new_class(self, dest_attr, new_name):
         old_pyclass = self.pyfunction.parent
+        if dest_attr not in old_pyclass.get_attributes():
+            raise exceptions.RefactoringError(
+                'Destination attribute <%s> not found' % dest_attr)
         pyclass = old_pyclass.get_attribute(dest_attr).get_object().get_type()
+        if not isinstance(pyclass, pyobjects.PyClass):
+            raise exceptions.RefactoringError(
+                'Unknown class type for attribute <%s>' % dest_attr)
         pymodule = pyclass.get_module()
         resource = pyclass.get_module().get_resource()
-        insertion_point = min(pymodule.lines.get_line_end(
-                              pyclass.get_scope().get_end()) + 1,
-                              len(pymodule.source_code))
+        start, end = sourceutils.get_body_region(pyclass)
+        pre_blanks = '\n'
+        if pymodule.source_code[start:end].strip() != 'pass':
+            pre_blanks = '\n\n'
+            start = end
         indents = sourceutils.get_indents(pymodule.lines,
                                           pyclass.get_scope().get_start())
-        body = '\n\n' + sourceutils.fix_indentation(self.get_new_method(new_name),
-                                                    indents + 4)
-        return resource, insertion_point, insertion_point, body
+        body = pre_blanks + sourceutils.fix_indentation(
+            self.get_new_method(new_name), indents + 4)
+        return resource, start, end, body
 
     def get_new_method(self, name):
-        return '%s\n    %s' % (self._get_new_header(name), self._get_body())
+        return '%s\n%s' % (
+            self._get_new_header(name),
+            sourceutils.fix_indentation(self._get_body(), 4))
 
     def _get_unchanged_body(self):
-        body = sourceutils.get_body(self.pyfunction)
-        indented_body = sourceutils.fix_indentation(body, 4)
-        return body
+        return sourceutils.get_body(self.pyfunction)
 
     def _get_body(self, host='host'):
         self_name = self._get_self_name()
         return self._get_body('__old_self') != self._get_unchanged_body()
 
 
-class Move(object):
-    """A class for moving modules, packages, global functions and classes."""
-
-    def __init__(self, project, resource, offset=None):
-        self.pycore = project.pycore
-        if offset is not None:
-            self.pyname = codeanalyze.get_pyname_at(
-                self.pycore, resource, offset)
-            if self.pyname is None:
-                raise rope.base.exceptions.RefactoringError(
-                    'Move works on classes,functions or modules.')
-        else:
-            if not resource.is_folder() and resource.name == '__init__.py':
-                resource = resource.parent
-            dummy_pymodule = self.pycore.get_string_module('')
-            self.pyname = rope.base.pynames.ImportedModule(
-                dummy_pymodule, resource=resource)
-
-    def get_changes(self, dest_resource):
-        moving_object = self.pyname.get_object()
-        if moving_object.get_type() == pyobjects.get_base_type('Module'):
-            mover = MoveModule(self.pycore, self.pyname, dest_resource)
-        else:
-            mover = MoveGlobal(self.pycore, self.pyname, dest_resource)
-        return mover.get_changes()
-
-
 class _Mover(object):
 
-    def __init__(self, pycore, source, destination,
-                 pyname, old_name, new_name):
+    def __init__(self, pycore, source, pyname, old_name):
         self.pycore = pycore
         self.source = source
-        self.destination = destination
         self.old_pyname = pyname
         self.old_name = old_name
-        self.new_name = new_name
         self.import_tools = importutils.ImportTools(self.pycore)
         self._check_exceptional_conditions()
 
     def _check_exceptional_conditions(self):
         pass
 
-    def _add_imports_to_module(self, pymodule, new_imports):
-        module_with_imports = self.import_tools.get_module_imports(pymodule)
-        for new_import in new_imports:
-            module_with_imports.add_import(new_import)
-        return module_with_imports.get_changed_source()
-
-    def _add_imports_to_module2(self, pymodule, new_imports):
-        source = self._add_imports_to_module(pymodule, new_imports)
-        if source is None:
-            return pymodule, False
-        else:
-            return self.pycore.get_string_module(source, pymodule.get_resource()), True
-
     def _remove_old_pyname_imports(self, pymodule):
         old_source = pymodule.source_code
         module_with_imports = self.import_tools.get_module_imports(pymodule)
                        self.old_pyname.get_object():
                         self.changed = True
                         return False
-                except rope.base.exceptions.AttributeNotFoundError:
+                except exceptions.AttributeNotFoundError:
                     pass
                 return True
         can_select = CanSelect()
         else:
             return self.pycore.get_string_module(source, pymodule.get_resource()), True
 
+    def get_changes(self, dest):
+        raise NotImplementedError()
+
 
 class MoveGlobal(_Mover):
     """For moving global function and classes"""
 
-    def __init__(self, pycore, pyname, destination):
+    def __init__(self, project, resource, offset):
+        pycore = project.pycore
+        pyname = codeanalyze.get_pyname_at(pycore, resource, offset)
         old_name = pyname.get_object()._get_ast().name
         pymodule = pyname.get_object().get_module()
         source = pymodule.get_resource()
-        new_name = importutils.get_module_name(
-            pycore, destination) + '.' + old_name
-        if destination.is_folder() and destination.has_child('__init__.py'):
-            destination = destination.get_child('__init__.py')
 
-        super(MoveGlobal, self).__init__(pycore, source, destination,
-                                           pyname, old_name, new_name)
-        self.new_import = self.import_tools.get_import_for_module(
-            self.pycore.resource_to_pyobject(self.destination))
+        super(MoveGlobal, self).__init__(pycore, source, pyname, old_name)
         scope = pyname.get_object().get_scope()
 
     def _check_exceptional_conditions(self):
         if self.old_pyname is None or \
            not isinstance(self.old_pyname.get_object(), pyobjects.PyDefinedObject):
-            raise rope.base.exceptions.RefactoringError(
+            raise exceptions.RefactoringError(
                 'Move refactoring should be performed on a class/function.')
         moving_pyobject = self.old_pyname.get_object()
         if not self._is_global(moving_pyobject):
-            raise rope.base.exceptions.RefactoringError(
+            raise exceptions.RefactoringError(
                 'Move refactoring should be performed on a global class/function.')
-        if self.destination.is_folder():
-            raise rope.base.exceptions.RefactoringError(
-                'Move destination for non-modules should not be folders.')
 
     def _is_global(self, pyobject):
         return pyobject.get_scope().parent == pyobject.get_module().get_scope()
 
-    def get_changes(self):
+    def get_changes(self, dest):
+        if dest.is_folder() and dest.has_child('__init__.py'):
+            dest = dest.get_child('__init__.py')
+        if dest.is_folder():
+            raise exceptions.RefactoringError(
+                'Move destination for non-modules should not be folders.')
         changes = ChangeSet('Moving global <%s>' % self.old_name)
-        self._change_destination_module(changes)
-        self._change_source_module(changes)
-        self._change_other_modules(changes)
+        self._change_destination_module(changes, dest)
+        self._change_source_module(changes, dest)
+        self._change_other_modules(changes, dest)
         return changes
 
-    def _change_source_module(self, changes):
+    def _new_name(self, dest):
+        return importutils.get_module_name(self.pycore, dest) + '.' + self.old_name
+
+    def _new_import(self, dest):
+        return self.import_tools.get_import_for_module(
+            self.pycore.resource_to_pyobject(dest))
+
+    def _change_source_module(self, changes, dest):
         uses_moving = False
         # Changing occurrences
         pymodule = self.pycore.resource_to_pyobject(self.source)
-        pymodule, has_changed = self._rename_in_module(pymodule, self.new_name)
+        pymodule, has_changed = self._rename_in_module(
+            pymodule, self._new_name(dest))
         if has_changed:
             uses_moving = True
         source = self._get_moved_moving_source(pymodule)
         if uses_moving:
             pymodule = self.pycore.get_string_module(source, self.source)
             # Adding new import
-            source = self._add_imports_to_module(pymodule, [self.new_import])
-
+            source = _add_imports_to_module(self.import_tools, pymodule,
+                                            [self._new_import(dest)])
         changes.add_change(ChangeContents(self.source, source))
 
     def _get_moved_moving_source(self, pymodule):
         source = source[:start] + source[end + 1:]
         return source
 
-    def _change_destination_module(self, changes):
+    def _change_destination_module(self, changes, dest):
         # Changing occurrences
-        pymodule = self.pycore.resource_to_pyobject(self.destination)
+        pymodule = self.pycore.resource_to_pyobject(dest)
         pymodule, has_changed = self._rename_in_module(pymodule, self.old_name)
 
         moving, imports = self._get_moving_element_with_imports()
 
         # Organizing imports
         source = result
-        pymodule = self.pycore.get_string_module(source, self.destination)
+        pymodule = self.pycore.get_string_module(source, dest)
         source = self.import_tools.organize_imports(pymodule)
-        changes.add_change(ChangeContents(self.destination, source))
+        changes.add_change(ChangeContents(dest, source))
 
     def _get_moving_element_with_imports(self):
         moving = self._get_moving_element()
         source_pymodule = self.pycore.resource_to_pyobject(self.source)
         new_imports = self._get_used_imports_by_the_moving_element()
-        new_imports.append(self.import_tools.get_from_import_for_module(source_pymodule, '*'))
+        new_imports.append(self.import_tools.
+                           get_from_import_for_module(source_pymodule, '*'))
 
         pymodule = self.pycore.get_string_module(moving, self.source)
         pymodule, has_changed = self._add_imports_to_module2(pymodule, new_imports)
         module_with_imports = self.import_tools.get_module_imports(pymodule)
         return module_with_imports.get_used_imports(self.old_pyname.get_object())
 
-    def _change_other_modules(self, changes):
+    def _change_other_modules(self, changes, dest):
         for file_ in self.pycore.get_python_files():
-            if file_ in (self.source, self.destination):
+            if file_ in (self.source, dest):
                 continue
             is_changed = False
             should_import = False
             pymodule = self.pycore.resource_to_pyobject(file_)
             # Changing occurrences
-            pymodule, has_changed = self._rename_in_module(pymodule, self.new_name)
+            pymodule, has_changed = self._rename_in_module(
+                pymodule, self._new_name(dest))
             if has_changed:
                 should_import = True
                 is_changed = True
                 is_changed = True
             # Adding new import
             if should_import:
-                source = self._add_imports_to_module(pymodule, [self.new_import])
+                source = _add_imports_to_module(self.import_tools, pymodule,
+                                                [self._new_import(dest)])
             if is_changed:
                 changes.add_change(ChangeContents(file_, source))
 
+    def _add_imports_to_module2(self, pymodule, new_imports):
+        source = _add_imports_to_module(self.import_tools, pymodule,
+                                        new_imports)
+        if source is None:
+            return pymodule, False
+        else:
+            return self.pycore.get_string_module(source, pymodule.get_resource()), True
+
 
 class MoveModule(_Mover):
     """For moving modules and packages"""
 
-    def __init__(self, pycore, pyname, destination):
+    def __init__(self, project, resource):
+        pycore = project.pycore
+        if not resource.is_folder() and resource.name == '__init__.py':
+            resource = resource.parent
+        dummy_pymodule = pycore.get_string_module('')
+        pyname = pynames.ImportedModule(
+            dummy_pymodule, resource=resource)
         source = pyname.get_object().get_resource()
         if source.is_folder():
             old_name = source.name
         else:
             old_name = source.name[:-3]
-        package = importutils.get_module_name(pycore, destination)
-        if package:
-            new_name = package + '.' + old_name
-        else:
-            new_name = old_name
-        super(MoveModule, self).__init__(pycore, source, destination,
-                                           pyname, old_name, new_name)
-        self.new_import = importutils.NormalImport([(self.new_name, None)])
+        super(MoveModule, self).__init__(pycore, source, pyname, old_name)
 
-    def _check_exceptional_conditions(self):
+    def get_changes(self, dest):
         moving_pyobject = self.old_pyname.get_object()
-        if not self.destination.is_folder():
-            raise rope.base.exceptions.RefactoringError(
+        if not dest.is_folder():
+            raise exceptions.RefactoringError(
                 'Move destination for modules should be packages.')
-
-    def get_changes(self):
         changes = ChangeSet('Moving module <%s>' % self.old_name)
-        self._change_other_modules(changes)
-        self._change_moving_module(changes)
+        self._change_other_modules(changes, dest)
+        self._change_moving_module(changes, dest)
         return changes
 
-    def _change_moving_module(self, changes):
+    def _new_import(self, dest):
+        return importutils.NormalImport([(self._new_name(dest), None)])
+
+    def _new_name(self, dest):
+        package = importutils.get_module_name(self.pycore, dest)
+        if package:
+            new_name = package + '.' + self.old_name
+        else:
+            new_name = self.old_name
+        return new_name
+
+    def _change_moving_module(self, changes, dest):
         if not self.source.is_folder():
             is_changed = False
             pymodule = self.pycore.resource_to_pyobject(self.source)
             if source is not None:
                 pymodule = self.pycore.get_string_module(source, self.source)
                 is_changed = True
-            source = self._change_occurrences_in_module(pymodule)
+            source = self._change_occurrences_in_module(
+                pymodule, self._new_import(dest), self._new_name(dest))
             if source is not None:
                 is_changed = True
             else:
                 source = pymodule.source_code
             if is_changed:
                 changes.add_change(ChangeContents(self.source, source))
-        changes.add_change(MoveResource(self.source,
-                                        self.destination.path))
+        changes.add_change(MoveResource(self.source, dest.path))
 
-    def _change_other_modules(self, changes):
+    def _change_other_modules(self, changes, dest):
         for module in self.pycore.get_python_files():
-            if module in (self.source, self.destination):
+            if module in (self.source, dest):
                 continue
             pymodule = self.pycore.resource_to_pyobject(module)
-            source = self._change_occurrences_in_module(pymodule)
+            source = self._change_occurrences_in_module(
+                pymodule, self._new_import(dest), self._new_name(dest))
             if source is not None:
                 changes.add_change(ChangeContents(module, source))
 
-    def _change_occurrences_in_module(self, pymodule):
+    def _change_occurrences_in_module(self, pymodule, new_import, new_name):
         is_changed = False
         should_import = False
-        pymodule, has_changed = self._rename_in_module(pymodule, self.new_name,
+        pymodule, has_changed = self._rename_in_module(pymodule, new_name,
                                                        imports=True)
         if has_changed:
             is_changed = True
             should_import = True
             is_changed = True
         if should_import:
-            source = self._add_imports_to_module(pymodule, [self.new_import])
+            source = _add_imports_to_module(self.import_tools, pymodule,
+                                            [new_import])
         else:
             source = pymodule.source_code
         if is_changed:
             return source
+
+
+def _add_imports_to_module(import_tools, pymodule, new_imports):
+    module_with_imports = import_tools.get_module_imports(pymodule)
+    for new_import in new_imports:
+        module_with_imports.add_import(new_import)
+    return module_with_imports.get_changed_source()

rope/refactor/rename.py

     def _get_old_pynames(self, in_file, in_hierarchy):
         if self.old_pyname is None:
             return []
-        if self.is_method() and not in_file and in_hierarchy:
+        if self.is_method() and in_hierarchy:
             return get_all_methods_in_hierarchy(self.old_pyname.get_object().
                                                 parent, self.old_name)
         else:

rope/refactor/sourceutils.py

     return fix_indentation(pymodule.source_code[start:end], 0)
 
 
-def get_body_region(pyfunction):
+def get_body_region(defined):
     """Return the start and end offsets of function body"""
-    scope = pyfunction.get_scope()
-    pymodule = pyfunction.get_module()
+    scope = defined.get_scope()
+    pymodule = defined.get_module()
     lines = pymodule.lines
     logical_lines = codeanalyze.LogicalLineFinder(lines)
     start_line = logical_lines.get_logical_line_in(scope.get_start())[1] + 1
-    if pyfunction._get_ast().doc is not None:
+    if defined._get_ast().doc is not None:
         start_line = logical_lines.get_logical_line_in(start_line)[1] + 1
     start = lines.get_line_start(start_line)
     end = min(lines.get_line_end(scope.get_end()) + 1,

rope/ui/refactor.py

             context.project, resource, offset)
 
     def _get_changes(self):
-        destination = self.project.get_pycore().find_module(self.new_name_entry.get())
-        return self.mover.get_changes(destination)
+        destination = None
+        if isinstance(self.mover, rope.refactor.move.MoveMethod):
+            destination = self.destination_entry.get()
+            new_method = self.new_method_entry.get()
+            return self.mover.get_changes(destination, new_method)
+        else:
+            destination = self.project.get_pycore().find_module(self.destination_entry.get())
+            return self.mover.get_changes(destination)
 
     def _get_dialog_frame(self):
+        if isinstance(self.mover, rope.refactor.move.MoveMethod):
+            return self._get_method_frame()
         frame = Tkinter.Frame(self.toplevel)
         label = Tkinter.Label(frame, text='Destination Module :')
         label.grid(row=0, column=0)
-        self.new_name_entry = Tkinter.Entry(frame)
-        self.new_name_entry.grid(row=0, column=1)
+        self.destination_entry = Tkinter.Entry(frame)
+        self.destination_entry.grid(row=0, column=1)
         def do_select(resource):
             name = rope.refactor.importutils.get_module_name(
                 self.project.get_pycore(), resource)
-            self.new_name_entry.delete(0, Tkinter.END)
-            self.new_name_entry.insert(0, name)
+            self.destination_entry.delete(0, Tkinter.END)
+            self.destination_entry.insert(0, name)
         def browse():
             toplevel = Tkinter.Toplevel()
             toplevel.title('Choose Destination Module')
             tree_view.list.focus_set()
             toplevel.grab_set()
 
+        self._bind_keys(self.destination_entry)
         browse_button = Tkinter.Button(frame, text='...', command=browse)
         browse_button.grid(row=0, column=2)
-        self.new_name_entry.bind('<Return>', lambda event: self._ok())
-        self.new_name_entry.bind('<Escape>', lambda event: self._cancel())
-        self.new_name_entry.bind('<Control-g>', lambda event: self._cancel())
         frame.grid()
-        self.new_name_entry.focus_set()
+        self.destination_entry.focus_set()
         return frame
 
+    def _bind_keys(self, widget):
+        widget.bind('<Return>', lambda event: self._ok())
+        widget.bind('<Escape>', lambda event: self._cancel())
+        widget.bind('<Control-g>', lambda event: self._cancel())
+
+    def _get_method_frame(self):
+        frame = Tkinter.Frame(self.toplevel)
+        dest_label = Tkinter.Label(frame, text='Destination Attribute :')
+        dest_label.grid(row=0, column=0)
+        self.destination_entry = Tkinter.Entry(frame)
+        self.destination_entry.grid(row=0, column=1)
+
+        new_method_label = Tkinter.Label(frame, text='New Method Name :')
+        new_method_label.grid(row=1, column=0)
+        self.new_method_entry = Tkinter.Entry(frame)
+        self.new_method_entry.grid(row=1, column=1)
+        self.new_method_entry.insert(0, self.mover.get_method_name())
+        self.new_method_entry.select_range(0, Tkinter.END)
+
+        for widget in [self.destination_entry, self.new_method_entry]:
+            self._bind_keys(widget)
+        frame.grid()
+        self.destination_entry.focus_set()
+        return frame
+
+
 def move(context):
     MoveDialog(context).show()
 

ropetest/refactor/inlinetest.py

         self.assertEquals('a = 1 + 2\n',
                           self.mod.read())
 
+    def test_a_function_with_pass_body(self):
+        self.mod.write('def a_func():\n    print(1)\na = a_func()\n')
+        self._inline2(self.mod, self.mod.read().index('a_func') + 1)
+        self.assertEquals('print(1)\na = None\n', self.mod.read())
+
+    def test_inlining_the_last_method_of_a_class(self):
+        self.mod.write('class A(object):\n'
+                       '    def a_func(self):\n        pass\n')
+        self._inline2(self.mod, self.mod.read().rindex('a_func') + 1)
+        self.assertEquals('class A(object):\n    pass\n',
+                          self.mod.read())
+
 
 def suite():
     result = unittest.TestSuite()

ropetest/refactor/movetest.py

 import rope.base.project
 import ropetest
 from rope.refactor import move
+from ropetest import testutils
 
 
 class MoveRefactoringTest(unittest.TestCase):
                           '    return host.attr\n',
                           mover.get_new_method('new_method'))
 
+    def test_moving_methods_getting_new_method_for_multi_line_methods(self):
+        code = 'class A(object):\n' \
+               '    def a_method(self):\n        a = 2\n        return a\n'
+        self.mod1.write(code)
+        mover = move.create_move(self.project, self.mod1,
+                                 code.index('a_method'))
+        self.assertEquals(
+            'def new_method(self):\n    a = 2\n    return a\n',
+            mover.get_new_method('new_method'))
+
     def test_moving_methods_getting_old_method_for_constant_methods(self):
         self.mod2.write('class B(object):\n    pass\n')
         code = 'import mod2\n\nclass A(object):\n    attr = mod2.B()\n' \
             self.mod2.read())
 
     def test_moving_methods_getting_getting_changes_for_goal_class(self):
-        self.mod2.write('class B(object):\n    var = 1\n')
         code = 'class B(object):\n    var = 1\n\n' \
                'class A(object):\n    attr = B()\n' \
                '    def a_method(self):\n        return 1\n'
             '    def a_method(self):\n        return self.attr.new_method()\n',
             self.mod1.read())
 
+    @testutils.assert_raises(rope.base.exceptions.RefactoringError)
+    def test_moving_methods_and_nonexistent_attributes(self):
+        code = 'class A(object):\n' \
+               '    def a_method(self):\n        return 1\n'
+        self.mod1.write(code)
+        mover = move.create_move(self.project, self.mod1,
+                                 code.index('a_method'))
+        mover.get_changes('x', 'new_method')
+
+    @testutils.assert_raises(rope.base.exceptions.RefactoringError)
+    def test_unknown_attribute_type(self):
+        code = 'class A(object):\n    attr = 1\n' \
+               '    def a_method(self):\n        return 1\n'
+        self.mod1.write(code)
+        mover = move.create_move(self.project, self.mod1,
+                                 code.index('a_method'))
+        mover.get_changes('attr', 'new_method')
+
+    def test_moving_methods_and_moving_used_imports(self):
+        self.mod2.write('class B(object):\n    var = 1\n')
+        code = 'import sys\nimport mod2\n\nclass A(object):\n    attr = mod2.B()\n' \
+               '    def a_method(self):\n        return sys.version\n'
+        self.mod1.write(code)
+        mover = move.create_move(self.project, self.mod1,
+                                 code.index('a_method'))
+        mover.get_changes('attr', 'new_method').do()
+        self.assertEquals(
+            'import sys\n'
+            'class B(object):\n    var = 1\n\n\n'
+            '    def new_method(self):\n        return sys.version\n',
+            self.mod2.read())
+
+    def test_moving_methods_getting_getting_changes_for_goal_class(self):
+        self.mod2.write('class B(object):\n    pass\n')
+        code = 'import mod2\n\nclass A(object):\n    attr = mod2.B()\n' \
+               '    def a_method(self):\n        return 1\n'
+        self.mod1.write(code)
+        mover = move.create_move(self.project, self.mod1,
+                                 code.index('a_method'))
+        mover.get_changes('attr', 'new_method').do()
+        self.assertEquals(
+            'class B(object):\n\n    def new_method(self):\n        return 1\n',
+            self.mod2.read())
+
 
 if __name__ == '__main__':
     unittest.main()
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.