encapsulate_field.py 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  1. from rope.base import pynames, taskhandle, evaluate, exceptions, worder, utils
  2. from rope.base.change import ChangeSet, ChangeContents
  3. from rope.refactor import sourceutils, occurrences
  4. class EncapsulateField(object):
  5. def __init__(self, project, resource, offset):
  6. self.pycore = project.pycore
  7. self.name = worder.get_name_at(resource, offset)
  8. this_pymodule = self.pycore.resource_to_pyobject(resource)
  9. self.pyname = evaluate.eval_location(this_pymodule, offset)
  10. if not self._is_an_attribute(self.pyname):
  11. raise exceptions.RefactoringError(
  12. 'Encapsulate field should be performed on class attributes.')
  13. self.resource = self.pyname.get_definition_location()[0].get_resource()
  14. def get_changes(self, getter=None, setter=None, resources=None,
  15. task_handle=taskhandle.NullTaskHandle()):
  16. """Get the changes this refactoring makes
  17. If `getter` is not `None`, that will be the name of the
  18. getter, otherwise ``get_${field_name}`` will be used. The
  19. same is true for `setter` and if it is None set_${field_name} is
  20. used.
  21. `resources` can be a list of `rope.base.resource.File`\s that
  22. the refactoring should be applied on; if `None` all python
  23. files in the project are searched.
  24. """
  25. if resources is None:
  26. resources = self.pycore.get_python_files()
  27. changes = ChangeSet('Encapsulate field <%s>' % self.name)
  28. job_set = task_handle.create_jobset('Collecting Changes',
  29. len(resources))
  30. if getter is None:
  31. getter = 'get_' + self.name
  32. if setter is None:
  33. setter = 'set_' + self.name
  34. renamer = GetterSetterRenameInModule(
  35. self.pycore, self.name, self.pyname, getter, setter)
  36. for file in resources:
  37. job_set.started_job(file.path)
  38. if file == self.resource:
  39. result = self._change_holding_module(changes, renamer,
  40. getter, setter)
  41. changes.add_change(ChangeContents(self.resource, result))
  42. else:
  43. result = renamer.get_changed_module(file)
  44. if result is not None:
  45. changes.add_change(ChangeContents(file, result))
  46. job_set.finished_job()
  47. return changes
  48. def get_field_name(self):
  49. """Get the name of the field to be encapsulated"""
  50. return self.name
  51. def _is_an_attribute(self, pyname):
  52. if pyname is not None and isinstance(pyname, pynames.AssignedName):
  53. pymodule, lineno = self.pyname.get_definition_location()
  54. scope = pymodule.get_scope().\
  55. get_inner_scope_for_line(lineno)
  56. if scope.get_kind() == 'Class':
  57. return pyname in scope.get_names().values()
  58. parent = scope.parent
  59. if parent is not None and parent.get_kind() == 'Class':
  60. return pyname in parent.get_names().values()
  61. return False
  62. def _get_defining_class_scope(self):
  63. defining_scope = self._get_defining_scope()
  64. if defining_scope.get_kind() == 'Function':
  65. defining_scope = defining_scope.parent
  66. return defining_scope
  67. def _get_defining_scope(self):
  68. pymodule, line = self.pyname.get_definition_location()
  69. return pymodule.get_scope().get_inner_scope_for_line(line)
  70. def _change_holding_module(self, changes, renamer, getter, setter):
  71. pymodule = self.pycore.resource_to_pyobject(self.resource)
  72. class_scope = self._get_defining_class_scope()
  73. defining_object = self._get_defining_scope().pyobject
  74. start, end = sourceutils.get_body_region(defining_object)
  75. new_source = renamer.get_changed_module(pymodule=pymodule,
  76. skip_start=start, skip_end=end)
  77. if new_source is not None:
  78. pymodule = self.pycore.get_string_module(new_source, self.resource)
  79. class_scope = pymodule.get_scope().\
  80. get_inner_scope_for_line(class_scope.get_start())
  81. indents = sourceutils.get_indent(self.pycore) * ' '
  82. getter = 'def %s(self):\n%sreturn self.%s' % \
  83. (getter, indents, self.name)
  84. setter = 'def %s(self, value):\n%sself.%s = value' % \
  85. (setter, indents, self.name)
  86. new_source = sourceutils.add_methods(pymodule, class_scope,
  87. [getter, setter])
  88. return new_source
  89. class GetterSetterRenameInModule(object):
  90. def __init__(self, pycore, name, pyname, getter, setter):
  91. self.pycore = pycore
  92. self.name = name
  93. self.finder = occurrences.create_finder(pycore, name, pyname)
  94. self.getter = getter
  95. self.setter = setter
  96. def get_changed_module(self, resource=None, pymodule=None,
  97. skip_start=0, skip_end=0):
  98. change_finder = _FindChangesForModule(self, resource, pymodule,
  99. skip_start, skip_end)
  100. return change_finder.get_changed_module()
  101. class _FindChangesForModule(object):
  102. def __init__(self, finder, resource, pymodule, skip_start, skip_end):
  103. self.pycore = finder.pycore
  104. self.finder = finder.finder
  105. self.getter = finder.getter
  106. self.setter = finder.setter
  107. self.resource = resource
  108. self.pymodule = pymodule
  109. self.last_modified = 0
  110. self.last_set = None
  111. self.set_index = None
  112. self.skip_start = skip_start
  113. self.skip_end = skip_end
  114. def get_changed_module(self):
  115. result = []
  116. for occurrence in self.finder.find_occurrences(self.resource,
  117. self.pymodule):
  118. start, end = occurrence.get_word_range()
  119. if self.skip_start <= start < self.skip_end:
  120. continue
  121. self._manage_writes(start, result)
  122. result.append(self.source[self.last_modified:start])
  123. if self._is_assigned_in_a_tuple_assignment(occurrence):
  124. raise exceptions.RefactoringError(
  125. 'Cannot handle tuple assignments in encapsulate field.')
  126. if occurrence.is_written():
  127. assignment_type = self.worder.get_assignment_type(start)
  128. if assignment_type == '=':
  129. result.append(self.setter + '(')
  130. else:
  131. var_name = self.source[occurrence.get_primary_range()[0]:
  132. start] + self.getter + '()'
  133. result.append(self.setter + '(' + var_name
  134. + ' %s ' % assignment_type[:-1])
  135. current_line = self.lines.get_line_number(start)
  136. start_line, end_line = self.pymodule.logical_lines.\
  137. logical_line_in(current_line)
  138. self.last_set = self.lines.get_line_end(end_line)
  139. end = self.source.index('=', end) + 1
  140. self.set_index = len(result)
  141. else:
  142. result.append(self.getter + '()')
  143. self.last_modified = end
  144. if self.last_modified != 0:
  145. self._manage_writes(len(self.source), result)
  146. result.append(self.source[self.last_modified:])
  147. return ''.join(result)
  148. return None
  149. def _manage_writes(self, offset, result):
  150. if self.last_set is not None and self.last_set <= offset:
  151. result.append(self.source[self.last_modified:self.last_set])
  152. set_value = ''.join(result[self.set_index:]).strip()
  153. del result[self.set_index:]
  154. result.append(set_value + ')')
  155. self.last_modified = self.last_set
  156. self.last_set = None
  157. def _is_assigned_in_a_tuple_assignment(self, occurance):
  158. offset = occurance.get_word_range()[0]
  159. return self.worder.is_assigned_in_a_tuple_assignment(offset)
  160. @property
  161. @utils.saveit
  162. def source(self):
  163. if self.resource is not None:
  164. return self.resource.read()
  165. else:
  166. return self.pymodule.source_code
  167. @property
  168. @utils.saveit
  169. def lines(self):
  170. if self.pymodule is None:
  171. self.pymodule = self.pycore.resource_to_pyobject(self.resource)
  172. return self.pymodule.lines
  173. @property
  174. @utils.saveit
  175. def worder(self):
  176. return worder.Worder(self.source)