Commits

Anonymous committed 6d4bb01

Changing setters in encapsulate field

Comments (0)

Files changed (9)

       def __init__(self, ..., occurrence_finder):
           pass
   
-  done.
 
-This new approach makes easy for writing a new Rename class that
+This new approach makes it easy to write a new Rename class that
 only replaces what it wants.
 
 

docs/workingon.txt

 Encapsulate Field
 =================
 
-- Renaming occurances to occurrences
+- Adding `Occurrence.is_written`
+- Gets as sets
+- Adding ``add_methods(pymodule, class_scope, methods)``
+- Adding methods to the end of class scope rather than module
+
+* What if performed on non-classes
+* Performing encapsulate field in some other module than defining
+* Changing occurances in the module that contains the class
+* Tuple assignments
+* `AugAssign` nodes
+* Not considering keyword parameters as assignments
+
+* Using properties for source_code and lines
+* Using cached decorator
+* Refactoring `sourcetools` module; pack methods
+* Changing refactor modules to use `sourcetools.add_methods`
+* Adding ``add_statement(method, statement)``
+* Change local variable to field refactoring
+* Changing `Rename` to take an `OccuranceFinder`
 * Caching `Occurrence` fields
-* For renaming variable writes for encapsulate field
-  we need to replace occurances with different variables.
 
 
 Remaining Stories
 =================
 
 * Considering logical lines in `rope.codeanalyze`
-* Adding ``add_method(scope, method, preceding_method=None)``
-* Adding ``add_statement(method, statement)``
 * Reporting unhandled exceptions as error dialogs in the GUI
 * Better move dialog; complete modules names; use `editor._CompletionListHandle`
 * Moving/renaming current module

rope/codeanalyze.py

             return True
         return False
     
-    def _is_name_assigned_here(self, offset):
+    def _is_name_assigned_in_class_body(self, offset):
         word_start = self._find_word_start(offset - 1)
         word_end = self._find_word_end(offset - 1) + 1
         if '.' in self.source_code[word_start:word_end]:
                 opens += 1
             current_offset -= 1
         return current_offset
+    
+    def is_assigned_here(self, offset):
+        word_end = self._find_word_end(offset)
+        next_char = self._find_first_non_space_char(word_end + 1)
+        if next_char < len(self.source_code) and self.source_code[next_char] == '=' and \
+           len(self.source_code) > next_char + 1 and \
+           self.source_code[next_char + 1] != '=':
+            return True
+        return False
 
 
 class StatementEvaluator(object):
             return True
         if lineno != holding_scope.get_start() and \
            holding_scope.pyobject.get_type() == rope.pyobjects.PyObject.get_base_type('Type') and \
-           self.word_finder._is_name_assigned_here(offset):
+           self.word_finder._is_name_assigned_in_class_body(offset):
             return True
         return False
     

rope/refactor/encapsulate_field.py

 import rope.codeanalyze
+import rope.refactor.occurrences
+from rope.refactor import sourcetools
+
 from rope.refactor.rename import RenameInModule
 from rope.refactor.change import ChangeSet, ChangeFileContents
 
+
 class EncapsulateFieldRefactoring(object):
     
     def __init__(self, pycore, resource, offset):
         self.pycore = pycore
         self.resource = resource
         self.offset = offset
+        self.name = rope.codeanalyze.get_name_at(self.resource, self.offset)
+        self.pyname = rope.codeanalyze.get_pyname_at(self.pycore, self.resource, self.offset)
     
     def encapsulate_field(self):
         changes = ChangeSet()
-        name = rope.codeanalyze.get_name_at(self.resource, self.offset)
-        pyname = rope.codeanalyze.get_pyname_at(self.pycore, self.resource, self.offset)
-        getter = '\n    def get_%s(self):\n        return self.%s\n' % (name, name)
-        setter = '\n    def set_%s(self, value):\n        self.%s = value\n' % (name, name)
-        changes.add_change(ChangeFileContents(self.resource,
-                                              self.resource.read() + getter + setter))
-        rename_in_module = RenameInModule(self.pycore, [pyname], name,
-                                          'get_%s()' % name)
+        rename_in_module = GetterSetterRenameInModule(self.pycore, self.name,
+                                                      [self.pyname])
+        
+        self._change_holding_module(changes, self.name)
         for file in self.pycore.get_python_files():
             if file == self.resource:
                 continue
             if result is not None:
                 changes.add_change(ChangeFileContents(file, result))
         return changes
+    
+    def _get_defining_class_scope(self, pyname):
+        defining_pymodule, defining_line = pyname.get_definition_location()
+        defining_scope = defining_pymodule.get_scope().get_inner_scope_for_line(defining_line)
+        if defining_scope.get_kind() == 'Function':
+            defining_scope = defining_scope.parent
+        return defining_scope
+
+    def _change_holding_module(self, changes, name):
+        getter = '    def get_%s(self):\n        return self.%s' % (name, name)
+        setter = '    def set_%s(self, value):\n        self.%s = value' % (name, name)
+        pymodule = self.pycore.resource_to_pyobject(self.resource)
+        new_source = sourcetools.add_methods(
+            self.pyname.get_definition_location()[0],
+            self._get_defining_class_scope(self.pyname), [getter, setter])
+        changes.add_change(ChangeFileContents(pymodule.get_resource(), new_source))
+
+
+class GetterSetterRenameInModule(object):
+    
+    def __init__(self, pycore, name, pynames):
+        self.pycore = pycore
+        self.name = name
+        self.occurrences_finder = rope.refactor.occurrences.\
+                                  FilteredOccurrenceFinder(pycore, name, pynames)
+        self.getter = 'get_' + name
+        self.setter = 'set_' + name
+    
+    def get_changed_module(self, resource=None, pymodule=None):
+        return _FindChangesForModule(self, resource, pymodule).get_changed_module()
+
+
+class _FindChangesForModule(object):
+    
+    def __init__(self, rename_in_module, resource, pymodule):
+        self.pycore = rename_in_module.pycore
+        self.occurrences_finder = rename_in_module.occurrences_finder
+        self.getter = rename_in_module.getter
+        self.setter = rename_in_module.setter
+        self.resource = resource
+        self.pymodule = pymodule
+        self.source_code = None
+        self.lines = None
+        self.last_modified = 0
+        self.last_set = None
+        self.set_index = None
+        
+    def get_changed_module(self):
+        result = []
+        line_finder = None
+        for occurrence in self.occurrences_finder.find_occurrences(self.resource,
+                                                                   self.pymodule):
+            start, end = occurrence.get_word_range()
+            self._manage_writes(start, result)
+            result.append(self._get_source()[self.last_modified:start])
+            if occurrence.is_written():
+                result.append(self.setter + '(')
+                if line_finder is None:
+                    line_finder = rope.codeanalyze.LogicalLineFinder(self._get_lines())
+                current_line = self._get_lines().get_line_number(start)
+                start_line, end_line = line_finder.get_logical_line_in(current_line)
+                self.last_set = self._get_lines().get_line_end(end_line)                
+                end = self._get_source().index('=', end) + 1
+                self.set_index = len(result)
+            else:
+                result.append(self.getter + '()')
+            self.last_modified = end
+        if self.last_modified != 0:
+            self._manage_writes(len(self._get_source()), result)
+            result.append(self._get_source()[self.last_modified:])
+            return ''.join(result)
+        return None
+
+    def _manage_writes(self, offset, result):
+        if self.last_set is not None and self.last_set <= offset:
+            result.append(self._get_source()[self.last_modified:self.last_set])
+            set_value = ''.join(result[self.set_index:]).strip()
+            del result[self.set_index:]
+            result.append(set_value + ')')
+            self.last_modified = self.last_set
+            self.last_set = None
+    
+    def _get_source(self):
+        if self.source_code is None:
+            if self.resource is not None:
+                self.source_code = self.resource.read()
+            else:
+                self.source_code = self.pymodule.source_code
+        return self.source_code
+
+    def _get_lines(self):
+        if self.lines is None:
+            if self.pymodule is None:
+                self.pymodule = self.pycore.resource_to_pyobject(self.resource)
+            self.lines = self.pymodule.lines
+        return self.lines

rope/refactor/occurrences.py

 
 
 class OccurrenceFinder(object):
+    """For finding textual occurrences of a name"""
     
     def __init__(self, pycore, name):
         self.pycore = pycore
         self.pattern = self._get_occurrence_pattern(self.name)
     
     def find_occurrences(self, resource=None, pymodule=None):
+        """Generate `Occurrence` instances"""
         tools = _OccurrenceToolsCreator(self.pycore, resource, pymodule)
         source_code = tools.source_code
         for match in self.pattern.finditer(source_code):
         return "(?P<%s>" % name + "|".join(list_) + ")"
 
 
+class Occurrence(object):
+    
+    def __init__(self, tools, offset):
+        self.tools = tools
+        self.offset = offset
+    
+    def get_word_range(self):
+        start = self.tools.word_finder._find_word_start(self.offset - 1)
+        end = self.tools.word_finder._find_word_end(self.offset - 1) + 1
+        return (start, end)
+    
+    def get_primary_range(self):
+        start = self.tools.word_finder._find_primary_start(self.offset - 1)
+        end = self.tools.word_finder._find_word_end(self.offset - 1) + 1
+        return (start, end)
+    
+    def get_pyname(self):
+        return self.tools.name_finder.get_pyname_at(self.offset)
+    
+    def is_in_import_statement(self):
+        return (self.tools.word_finder.is_from_statement(self.offset) or
+                self.tools.word_finder.is_import_statement(self.offset))
+    
+    def is_called(self):
+        return self.tools.word_finder.is_a_function_being_called(self.offset)
+    
+    def is_a_fixed_primary(self):
+        return self.tools.word_finder.is_a_class_or_function_name_in_header(self.offset) or \
+               self.tools.word_finder.is_a_name_after_from_import(self.offset)
+    
+    def is_written(self):
+        return self.tools.word_finder.is_assigned_here(self.offset)
+
+    def is_read(self):
+        # TODO: Implement it
+        raise NotImplementedError()
+    
+
+class FilteredOccurrenceFinder(object):
+    
+    def __init__(self, pycore, name, pynames, only_calls=False, imports=True):
+        self.pycore = pycore
+        self.pynames = pynames
+        self.name = name
+        self.only_calls = only_calls
+        self.imports = imports
+        self.occurrence_finder = OccurrenceFinder(pycore, name)
+    
+    def find_occurrences(self, resource=None, pymodule=None):
+        for occurrence in self.occurrence_finder.find_occurrences(
+            resource, pymodule):
+            if self._is_a_match(occurrence):
+                yield occurrence
+
+    def _is_a_match(self, occurrence):
+        if self.only_calls and not occurrence.is_called():
+            return False
+        if not self.imports and occurrence.is_in_import_statement():
+            return False
+        new_pyname = occurrence.get_pyname()
+        for pyname in self.pynames:
+            if self._are_pynames_the_same(pyname, new_pyname):
+                return True
+        return False
+
+    def _are_pynames_the_same(self, pyname1, pyname2):
+        return pyname1 == pyname2 or \
+               (pyname1 is not None and pyname2 is not None and 
+                pyname1.get_object() == pyname2.get_object() and
+                pyname1.get_definition_location() == pyname2.get_definition_location())
+    
+
 class _OccurrenceToolsCreator(object):
     
     def __init__(self, pycore, resource=None, pymodule=None):
     source_code = property(get_source_code)
     
 
-class FilteredOccurrenceFinder(object):
-    
-    def __init__(self, pycore, pynames, name, only_calls=False,
-                 imports=True, only_writes=False):
-        self.pycore = pycore
-        self.pynames = pynames
-        self.name = name
-        self.only_calls = only_calls
-        self.imports = imports
-        self.occurrence_finder = OccurrenceFinder(pycore, name)
-    
-    def find_occurrences(self, resource=None, pymodule=None):
-        for occurrence in self.occurrence_finder.find_occurrences(
-            resource, pymodule):
-            if self._is_a_match(occurrence):
-                yield occurrence
-
-    def _is_a_match(self, occurrence):
-        if self.only_calls and not occurrence.is_called():
-            return False
-        if not self.imports and occurrence.is_in_import_statement():
-            return False
-        new_pyname = occurrence.get_pyname()
-        for pyname in self.pynames:
-            if self._are_pynames_the_same(pyname, new_pyname):
-                return True
-        return False
-
-    def _are_pynames_the_same(self, pyname1, pyname2):
-        return pyname1 == pyname2 or \
-               (pyname1 is not None and pyname2 is not None and 
-                pyname1.get_object() == pyname2.get_object() and
-                pyname1.get_definition_location() == pyname2.get_definition_location())
-    
-
-class Occurrence(object):
-    
-    def __init__(self, tools, offset):
-        self.tools = tools
-        self.offset = offset
-    
-    def get_word_range(self):
-        start = self.tools.word_finder._find_word_start(self.offset - 1)
-        end = self.tools.word_finder._find_word_end(self.offset - 1) + 1
-        return (start, end)
-    
-    def get_primary_range(self):
-        start = self.tools.word_finder._find_primary_start(self.offset - 1)
-        end = self.tools.word_finder._find_word_end(self.offset - 1) + 1
-        return (start, end)
-    
-    def get_pyname(self):
-        return self.tools.name_finder.get_pyname_at(self.offset)
-    
-    def is_in_import_statement(self):
-        return (self.tools.word_finder.is_from_statement(self.offset) or
-                self.tools.word_finder.is_import_statement(self.offset))
-    
-    def is_called(self):
-        return self.tools.word_finder.is_a_function_being_called(self.offset)
-    
-    def is_a_fixed_primary(self):
-        return self.tools.word_finder.is_a_class_or_function_name_in_header(self.offset) or \
-               self.tools.word_finder.is_a_name_after_from_import(self.offset)
-    
-    def is_read(self):
-        # TODO: Implement it
-        raise NotImplementedError()
-    
-    def is_written(self):
-        # TODO: Implement it
-        raise NotImplementedError()

rope/refactor/rename.py

     
     def __init__(self, pycore, old_pynames, old_name, new_name,
                  only_calls=False, replace_primary=False,
-                 imports=True, only_writes=False):
+                 imports=True):
         self.occurrences_finder = rope.refactor.occurrences.FilteredOccurrenceFinder(
-            pycore, old_pynames, old_name, only_calls, imports, only_writes)
+            pycore, old_name, old_pynames, only_calls, imports)
         self.whole_primary = replace_primary
         self.new_name = new_name
     

rope/refactor/sourcetools.py

         else:
             result.append('\n')
     return ''.join(result)
-    
+
+
+def add_methods(pymodule, class_scope, methods_sources):
+    source_code = pymodule.source_code
+    lines = pymodule.lines
+    insertion_line = class_scope.get_end()
+    if class_scope.get_scopes():
+        insertion_line = class_scope.get_scopes()[-1].get_end()
+    insertion_offset = lines.get_line_end(insertion_line)
+    methods = '\n\n' + '\n\n'.join(methods_sources)
+    unindented_methods = indent_lines(methods, -find_minimum_indents(methods))
+    indented_methods = indent_lines(unindented_methods,
+                                    get_indents(lines, class_scope.get_start()) + 4)
+    result = []
+    result.append(source_code[:insertion_offset])
+    result.append(indented_methods)
+    result.append(source_code[insertion_offset:])
+    return ''.join(result)
+
+def add_statement(pymodule, method_scope, statement_source):
+    # TODO: Implement it
+    pass
+

rope/ui/refactor.py

         editor = fileeditor.get_editor()
         editor.refactoring.inline_local_variable(resource, editor.get_current_offset())
     
+def encapsulate_field(context):
+    if context.get_active_editor():
+        fileeditor = context.get_active_editor()
+        resource = fileeditor.get_file()
+        editor = fileeditor.get_editor()
+        editor.refactoring.encapsulate_field(resource, editor.get_current_offset())
+    
     
 
 actions = []
                             ConfirmAllEditorsAreSaved(introduce_factory), None,
                             MenuAddress(['Refactor', 'Introduce Factory Method'], 'c', 1),
                             ['python']))
+actions.append(SimpleAction('Encapsulate Field', 
+                            ConfirmAllEditorsAreSaved(encapsulate_field), None,
+                            MenuAddress(['Refactor', 'Encapsulate Field'], 'n', 1),
+                            ['python']))
 actions.append(SimpleAction('Transform Module to Package', 
                             ConfirmAllEditorsAreSaved(transform_module_to_package), None,
                             MenuAddress(['Refactor', 'Transform Module to Package'], 't', 1), 

ropetest/refactor/__init__.py

         self.a_class = 'class A(object):\n    def __init__(self):\n        self.attr = 1\n'
         self.setter_and_getter = '\n    def get_attr(self):\n        return self.attr\n\n' \
                                  '    def set_attr(self, value):\n        self.attr = value\n'
+        self.encapsulated = 'class A(object):\n    def __init__(self):\n        self.attr = 1\n\n' \
+                            '    def get_attr(self):\n        return self.attr\n\n' \
+                            '    def set_attr(self, value):\n        self.attr = value\n'
 
     def tearDown(self):
         testutils.remove_recursively(self.project_root)
         super(EncapsulateFieldTest, self).tearDown()
 
     def test_adding_getters_and_setters(self):
-        code = 'class A(object):\n    def __init__(self):\n        self.attr = 1\n'
+        code = self.a_class
         self.mod.write(code)
         self.refactoring.encapsulate_field(self.mod, code.index('attr') + 1)
-        expected = 'class A(object):\n    def __init__(self):\n        self.attr = 1\n\n' \
-                   '    def get_attr(self):\n        return self.attr\n\n' \
-                   '    def set_attr(self, value):\n        self.attr = value\n'
-        self.assertEquals(expected, self.mod.read())
+        self.assertEquals(self.encapsulated, self.mod.read())
 
     def test_changing_getters_in_other_modules(self):
         self.mod1.write('import mod\na_var = mod.A()\nrange(a_var.attr)\n')
         self.mod1.write('import mod\na_var = mod.A()\na_var.attr = 1\n')
         self.mod.write(self.a_class)
         self.refactoring.encapsulate_field(self.mod, self.mod.read().index('attr') + 1)
-        self.assertEquals('import mod\na_var = mod.A()\na_var.set_attr(1))\n',
+        self.assertEquals('import mod\na_var = mod.A()\na_var.set_attr(1)\n',
+                          self.mod1.read())
+
+    def test_changing_getters_in_setters(self):
+        self.mod1.write('import mod\na_var = mod.A()\na_var.attr = 1 + a_var.attr\n')
+        self.mod.write(self.a_class)
+        self.refactoring.encapsulate_field(self.mod, self.mod.read().index('attr') + 1)
+        self.assertEquals(
+            'import mod\na_var = mod.A()\na_var.set_attr(1 + a_var.get_attr())\n',
+            self.mod1.read())
+    
+    def test_appending_to_class_end(self):
+        self.mod1.write(self.a_class + 'a_var = A()\n')
+        self.refactoring.encapsulate_field(self.mod1, self.mod1.read().index('attr') + 1)
+        self.assertEquals(self.encapsulated + 'a_var = A()\n',
                           self.mod1.read())
 
 
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.