Commits

Ali Gholami Rudi  committed 60076fb

Added cross-project refactorings support.

* Making other refactoring not to change out of project files
* Added a new section to library.txt

  • Participants
  • Parent commits 4959fdb

Comments (0)

Files changed (7)

File docs/dev/issues.txt

 * Should `rope.base` be thread safe? which parts?
 
 
-Cross-Project Refactorings
-==========================
-
-Issues:
-
-* Adding base to project classpaths
-* Some refactorings return changes to out of project
-* resources Undoing Specifying the main project Adding
-* ``rope.refactor.multiproject.perform``
-
-Changing refactorings to handle source, goal resources in resource
-loop:
-
-* move
-* rename
-* change_signature
-* encapsulate_field
-* inline
-* introduce_factory
-* moduletopackage
-
-Possible approaches:
-
-* Changing all refactorings not to change out of project resources;
-  should be careful which change should happen first
-* Filtering ChangeSets by using change.resource.project
-
-
 Distributing Refactorings
 =========================
 

File docs/library.txt

 
 
 `rope.contrib.codeassist`
-=========================
+-------------------------
 
 The `rope.contrib` package contains modules that use rope base parts
 and provide useful features.  `rope.contrib.codeassist` module can
 See pydocs and source code for more information (other functions in
 that module might be interesting, too; like `get_doc`,
 `get_definition_location` and `find_occurrences`).
+
+
+Cross-Project Refactorings
+--------------------------
+
+`rope.refactor.multiproject` can be used to perform a refactoring
+across multiple projects.
+
+Before we begin, note that there should always be a main project.
+That is the project that contains the definition of the changing
+python name.  Other projects depend on the main one and uses of the
+changed name in them should be updated.
+
+Each refactoring changes only one project (the project passed to its
+constructor).  But we can use `MultiProjectRefactoring` proxy to
+perform a refactoring on other projects, too.
+
+First we need create a multi-project refactoring constructor.  As an
+example consider we want to perform a rename refactoring::
+
+  from rope.refactor import multiproject, rename
+
+
+  CrossRename = multiproject.MultiProjectRefactoring(rename.Rename,
+                                                     projects)
+
+
+Here `projects` is the list of dependant projects; it does not include
+the main project.  The first argument is the refactoring class (such
+as `Rename`) or factory function (like `create_move`).
+
+Next we can construct the refactoring::
+
+  renamer = CrossRename(project, resource, offset)
+
+We create the rename refactoring as we do for normal refactorings.
+Note that `project` is the main project.
+
+Finally we can calculate the changes.  But instead of calling
+`get_changes()` (which returns main project changes, only), we should
+call `get_all_changes()` with the same arguments.  It returns a list
+of ``(project, changes)`` tuples.  You can perform them manually by
+calling ``project.do(changes)`` for each tuple or use
+`multiproject.perform()`::
+
+  project_and_changes = renamer.get_all_changes('newname')
+
+  multiproject.perform(project_and_changes)

File docs/rope.txt

 
   * Extracting similar statements in extract refactorings
   * Basic implicit interfaces handling in rename and change signature
+  * Cross-project refactorings
   * Fixing imports when needed
   * Previewing refactorings
   * Stopping refactorings

File rope/refactor/__init__.py

         if not parent.path:
             parent_path = ''
         new_path = parent_path + '%s/__init__.py' % name
-        changes.add_change(MoveResource(self.resource, new_path))
+        if self.resource.project == self.project:
+            changes.add_change(MoveResource(self.resource, new_path))
         return changes
 
     def _transform_relatives_to_absolute(self, resource):

File rope/refactor/encapsulate_field.py

             'Collecting Changes', len(self.pycore.get_python_files()))
         rename_in_module = GetterSetterRenameInModule(self.pycore, self.name,
                                                       [self.pyname])
-        job_set.started_job('Working on defining file')
-        self._change_holding_module(changes, rename_in_module)
-        job_set.finished_job()
         for file in self.pycore.get_python_files():
+            job_set.started_job('Working on <%s>' % file.path)
             if file == self.resource:
-                continue
-            job_set.started_job('Working on <%s>' % file.path)
-            result = rename_in_module.get_changed_module(file)
-            if result is not None:
-                changes.add_change(ChangeContents(file, result))
+                self._change_holding_module(changes, rename_in_module)
+            else:
+                result = rename_in_module.get_changed_module(file)
+                if result is not None:
+                    changes.add_change(ChangeContents(file, result))
             job_set.finished_job()
         return changes
 

File rope/refactor/inline.py

         changes = ChangeSet('Inline method <%s>' % self.name)
         job_set = task_handle.create_jobset(
             'Collecting Changes', len(self.pycore.get_python_files()))
-        job_set.started_job('Changing defining file')
-        self._change_defining_file(changes, remove=remove)
-        job_set.finished_job()
-        self._change_other_files(changes, job_set)
+        for file in self.pycore.get_python_files():
+            job_set.started_job('Working on <%s>' % file.path)
+            if file == self.resource:
+                changes.add_change(self._defining_file_changes(changes,
+                                                              remove=remove))
+            else:
+                handle = _InlineFunctionCallsForModuleHandle(
+                    self.pycore, file, self.others_generator)
+                result = move.ModuleSkipRenamer(
+                    self.occurrence_finder, file, handle).get_changed_module()
+                if result is not None:
+                    result = self._add_imports(result, file)
+                    changes.add_change(ChangeContents(file, result))
+            job_set.finished_job()
         return changes
 
     def _get_removed_range(self):
                   len(self.pymodule.source_code))
         return (start, end)
 
-    def _change_defining_file(self, changes, remove):
+    def _defining_file_changes(self, changes, remove):
         start_offset, end_offset = self._get_removed_range()
         handle = _InlineFunctionCallsForModuleHandle(
             self.pycore, self.resource, self.normal_generator)
         result = move.ModuleSkipRenamer(
             self.occurrence_finder, self.resource, handle, start_offset,
             end_offset, replacement).get_changed_module()
-        changes.add_change(ChangeContents(self.resource, result))
+        return ChangeContents(self.resource, result)
 
     def _get_method_replacement(self):
         if self._is_the_last_method_of_a_class():
             return True
         return False
 
-    def _change_other_files(self, changes, job_set):
-        for file in self.pycore.get_python_files():
-            if file == self.resource:
-                continue
-            job_set.started_job('Working on <%s>' % file.path)
-            handle = _InlineFunctionCallsForModuleHandle(
-                self.pycore, file, self.others_generator)
-            result = move.ModuleSkipRenamer(
-                self.occurrence_finder, file, handle).get_changed_module()
-            if result is not None:
-                result = self._add_imports(result, file)
-                changes.add_change(ChangeContents(file, result))
-            job_set.finished_job()
-
     def _add_imports(self, source, file):
         if not self.imports:
             return source

File rope/refactor/introduce_factory.py

             'Collecting Changes', len(self.pycore.get_python_files()))
         self._change_occurrences_in_other_modules(changes, factory_name,
                                                   global_factory, job_set)
-        job_set.started_job('Changing definition')
-        self._change_resource(changes, factory_name, global_factory)
-        job_set.finished_job()
         return changes
 
+    def _change_occurrences_in_other_modules(self, changes, factory_name,
+                                             global_factory, job_set):
+        changed_name = self._get_new_function_name(factory_name, global_factory)
+        import_tools = rope.refactor.importutils.ImportTools(self.pycore)
+        new_import = import_tools.get_import(self.resource)
+        if global_factory:
+            changed_name = new_import.names_and_aliases[0][0] + '.' + factory_name
+
+        for file_ in self.pycore.get_python_files():
+            if file_ == self.resource:
+                job_set.started_job('Changing definition')
+                self._change_resource(changes, factory_name, global_factory)
+                job_set.finished_job()
+                continue
+            job_set.started_job('Working on <%s>' % file_.path)
+            changed_code = self._rename_occurrences(file_, changed_name,
+                                                    global_factory)
+            if changed_code is not None:
+                if global_factory:
+                    new_pymodule = self.pycore.get_string_module(changed_code,
+                                                                 self.resource)
+                    module_with_imports = \
+                        import_tools.get_module_imports(new_pymodule)
+                    module_with_imports.add_import(new_import)
+                    changed_code = module_with_imports.get_changed_source()
+                changes.add_change(ChangeContents(file_, changed_code))
+            job_set.finished_job()
+
     def _change_resource(self, changes, factory_name, global_factory):
         class_scope = self.old_pyname.get_object().get_scope()
         source_code = self._rename_occurrences(
         else:
             return self.old_name + '.' + factory_name
 
-    def _change_occurrences_in_other_modules(self, changes, factory_name,
-                                             global_factory, job_set):
-        changed_name = self._get_new_function_name(factory_name, global_factory)
-        import_tools = rope.refactor.importutils.ImportTools(self.pycore)
-        new_import = import_tools.get_import(self.resource)
-        if global_factory:
-            changed_name = new_import.names_and_aliases[0][0] + '.' + factory_name
-
-        for file_ in self.pycore.get_python_files():
-            if file_ == self.resource:
-                continue
-            job_set.started_job('Working on <%s>' % file_.path)
-            changed_code = self._rename_occurrences(file_, changed_name, global_factory)
-            if changed_code is not None:
-                if global_factory:
-                    new_pymodule = self.pycore.get_string_module(changed_code, self.resource)
-                    module_with_imports = import_tools.get_module_imports(new_pymodule)
-                    module_with_imports.add_import(new_import)
-                    changed_code = module_with_imports.get_changed_source()
-                changes.add_change(ChangeContents(file_, changed_code))
-            job_set.finished_job()
-
     def _rename_occurrences(self, file_, changed_name, global_factory):
         occurrence_finder = occurrences.FilteredFinder(
             self.pycore, self.old_name, [self.old_pyname], only_calls=True)