introduce_parameter.py 3.8 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495
  1. import rope.base.change
  2. from rope.base import exceptions, evaluate, worder, codeanalyze
  3. from rope.refactor import functionutils, sourceutils, occurrences
  4. class IntroduceParameter(object):
  5. """Introduce parameter refactoring
  6. This refactoring adds a new parameter to a function and replaces
  7. references to an expression in it with the new parameter.
  8. The parameter finding part is different from finding similar
  9. pieces in extract refactorings. In this refactoring parameters
  10. are found based on the object they reference to. For instance
  11. in::
  12. class A(object):
  13. var = None
  14. class B(object):
  15. a = A()
  16. b = B()
  17. a = b.a
  18. def f(a):
  19. x = b.a.var + a.var
  20. using this refactoring on ``a.var`` with ``p`` as the new
  21. parameter name, will result in::
  22. def f(p=a.var):
  23. x = p + p
  24. """
  25. def __init__(self, project, resource, offset):
  26. self.pycore = project.pycore
  27. self.resource = resource
  28. self.offset = offset
  29. self.pymodule = self.pycore.resource_to_pyobject(self.resource)
  30. scope = self.pymodule.get_scope().get_inner_scope_for_offset(offset)
  31. if scope.get_kind() != 'Function':
  32. raise exceptions.RefactoringError(
  33. 'Introduce parameter should be performed inside functions')
  34. self.pyfunction = scope.pyobject
  35. self.name, self.pyname = self._get_name_and_pyname()
  36. if self.pyname is None:
  37. raise exceptions.RefactoringError(
  38. 'Cannot find the definition of <%s>' % self.name)
  39. def _get_primary(self):
  40. word_finder = worder.Worder(self.resource.read())
  41. return word_finder.get_primary_at(self.offset)
  42. def _get_name_and_pyname(self):
  43. return (worder.get_name_at(self.resource, self.offset),
  44. evaluate.eval_location(self.pymodule, self.offset))
  45. def get_changes(self, new_parameter):
  46. definition_info = functionutils.DefinitionInfo.read(self.pyfunction)
  47. definition_info.args_with_defaults.append((new_parameter,
  48. self._get_primary()))
  49. collector = codeanalyze.ChangeCollector(self.resource.read())
  50. header_start, header_end = self._get_header_offsets()
  51. body_start, body_end = sourceutils.get_body_region(self.pyfunction)
  52. collector.add_change(header_start, header_end,
  53. definition_info.to_string())
  54. self._change_function_occurances(collector, body_start,
  55. body_end, new_parameter)
  56. changes = rope.base.change.ChangeSet('Introduce parameter <%s>' %
  57. new_parameter)
  58. change = rope.base.change.ChangeContents(self.resource,
  59. collector.get_changed())
  60. changes.add_change(change)
  61. return changes
  62. def _get_header_offsets(self):
  63. lines = self.pymodule.lines
  64. start_line = self.pyfunction.get_scope().get_start()
  65. end_line = self.pymodule.logical_lines.\
  66. logical_line_in(start_line)[1]
  67. start = lines.get_line_start(start_line)
  68. end = lines.get_line_end(end_line)
  69. start = self.pymodule.source_code.find('def', start) + 4
  70. end = self.pymodule.source_code.rfind(':', start, end)
  71. return start, end
  72. def _change_function_occurances(self, collector, function_start,
  73. function_end, new_name):
  74. finder = occurrences.create_finder(self.pycore, self.name, self.pyname)
  75. for occurrence in finder.find_occurrences(resource=self.resource):
  76. start, end = occurrence.get_primary_range()
  77. if function_start <= start < function_end:
  78. collector.add_change(start, end, new_name)