| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342 |
- import copy
- import rope.base.exceptions
- from rope.base import pyobjects, taskhandle, evaluate, worder, codeanalyze, utils
- from rope.base.change import ChangeContents, ChangeSet
- from rope.refactor import occurrences, functionutils
- class ChangeSignature(object):
- def __init__(self, project, resource, offset):
- self.pycore = project.pycore
- self.resource = resource
- self.offset = offset
- self._set_name_and_pyname()
- if self.pyname is None or self.pyname.get_object() is None or \
- not isinstance(self.pyname.get_object(), pyobjects.PyFunction):
- raise rope.base.exceptions.RefactoringError(
- 'Change method signature should be performed on functions')
- def _set_name_and_pyname(self):
- self.name = worder.get_name_at(self.resource, self.offset)
- this_pymodule = self.pycore.resource_to_pyobject(self.resource)
- self.primary, self.pyname = evaluate.eval_location2(
- this_pymodule, self.offset)
- if self.pyname is None:
- return
- pyobject = self.pyname.get_object()
- if isinstance(pyobject, pyobjects.PyClass) and \
- '__init__' in pyobject:
- self.pyname = pyobject['__init__']
- self.name = '__init__'
- pyobject = self.pyname.get_object()
- self.others = None
- if self.name == '__init__' and \
- isinstance(pyobject, pyobjects.PyFunction) and \
- isinstance(pyobject.parent, pyobjects.PyClass):
- pyclass = pyobject.parent
- self.others = (pyclass.get_name(),
- pyclass.parent[pyclass.get_name()])
- def _change_calls(self, call_changer, in_hierarchy=None, resources=None,
- handle=taskhandle.NullTaskHandle()):
- if resources is None:
- resources = self.pycore.get_python_files()
- changes = ChangeSet('Changing signature of <%s>' % self.name)
- job_set = handle.create_jobset('Collecting Changes', len(resources))
- finder = occurrences.create_finder(
- self.pycore, self.name, self.pyname, instance=self.primary,
- in_hierarchy=in_hierarchy and self.is_method())
- if self.others:
- name, pyname = self.others
- constructor_finder = occurrences.create_finder(
- self.pycore, name, pyname, only_calls=True)
- finder = _MultipleFinders([finder, constructor_finder])
- for file in resources:
- job_set.started_job(file.path)
- change_calls = _ChangeCallsInModule(
- self.pycore, finder, file, call_changer)
- changed_file = change_calls.get_changed_module()
- if changed_file is not None:
- changes.add_change(ChangeContents(file, changed_file))
- job_set.finished_job()
- return changes
- def get_args(self):
- """Get function arguments.
- Return a list of ``(name, default)`` tuples for all but star
- and double star arguments. For arguments that don't have a
- default, `None` will be used.
- """
- return self._definfo().args_with_defaults
- def is_method(self):
- pyfunction = self.pyname.get_object()
- return isinstance(pyfunction.parent, pyobjects.PyClass)
- @utils.deprecated('Use `ChangeSignature.get_args()` instead')
- def get_definition_info(self):
- return self._definfo()
- def _definfo(self):
- return functionutils.DefinitionInfo.read(self.pyname.get_object())
- @utils.deprecated()
- def normalize(self):
- changer = _FunctionChangers(
- self.pyname.get_object(), self.get_definition_info(),
- [ArgumentNormalizer()])
- return self._change_calls(changer)
- @utils.deprecated()
- def remove(self, index):
- changer = _FunctionChangers(
- self.pyname.get_object(), self.get_definition_info(),
- [ArgumentRemover(index)])
- return self._change_calls(changer)
- @utils.deprecated()
- def add(self, index, name, default=None, value=None):
- changer = _FunctionChangers(
- self.pyname.get_object(), self.get_definition_info(),
- [ArgumentAdder(index, name, default, value)])
- return self._change_calls(changer)
- @utils.deprecated()
- def inline_default(self, index):
- changer = _FunctionChangers(
- self.pyname.get_object(), self.get_definition_info(),
- [ArgumentDefaultInliner(index)])
- return self._change_calls(changer)
- @utils.deprecated()
- def reorder(self, new_ordering):
- changer = _FunctionChangers(
- self.pyname.get_object(), self.get_definition_info(),
- [ArgumentReorderer(new_ordering)])
- return self._change_calls(changer)
- def get_changes(self, changers, in_hierarchy=False, resources=None,
- task_handle=taskhandle.NullTaskHandle()):
- """Get changes caused by this refactoring
- `changers` is a list of `_ArgumentChanger`\s. If `in_hierarchy`
- is `True` the changers are applyed to all matching methods in
- the class hierarchy.
- `resources` can be a list of `rope.base.resource.File`\s that
- should be searched for occurrences; if `None` all python files
- in the project are searched.
- """
- function_changer = _FunctionChangers(self.pyname.get_object(),
- self._definfo(), changers)
- return self._change_calls(function_changer, in_hierarchy,
- resources, task_handle)
- class _FunctionChangers(object):
- def __init__(self, pyfunction, definition_info, changers=None):
- self.pyfunction = pyfunction
- self.definition_info = definition_info
- self.changers = changers
- self.changed_definition_infos = self._get_changed_definition_infos()
- def _get_changed_definition_infos(self):
- result = []
- definition_info = self.definition_info
- result.append(definition_info)
- for changer in self.changers:
- definition_info = copy.deepcopy(definition_info)
- changer.change_definition_info(definition_info)
- result.append(definition_info)
- return result
- def change_definition(self, call):
- return self.changed_definition_infos[-1].to_string()
- def change_call(self, primary, pyname, call):
- call_info = functionutils.CallInfo.read(
- primary, pyname, self.definition_info, call)
- mapping = functionutils.ArgumentMapping(self.definition_info, call_info)
- for definition_info, changer in zip(self.changed_definition_infos, self.changers):
- changer.change_argument_mapping(definition_info, mapping)
- return mapping.to_call_info(self.changed_definition_infos[-1]).to_string()
- class _ArgumentChanger(object):
- def change_definition_info(self, definition_info):
- pass
- def change_argument_mapping(self, definition_info, argument_mapping):
- pass
- class ArgumentNormalizer(_ArgumentChanger):
- pass
- class ArgumentRemover(_ArgumentChanger):
- def __init__(self, index):
- self.index = index
- def change_definition_info(self, call_info):
- if self.index < len(call_info.args_with_defaults):
- del call_info.args_with_defaults[self.index]
- elif self.index == len(call_info.args_with_defaults) and \
- call_info.args_arg is not None:
- call_info.args_arg = None
- elif (self.index == len(call_info.args_with_defaults) and
- call_info.args_arg is None and call_info.keywords_arg is not None) or \
- (self.index == len(call_info.args_with_defaults) + 1 and
- call_info.args_arg is not None and call_info.keywords_arg is not None):
- call_info.keywords_arg = None
- def change_argument_mapping(self, definition_info, mapping):
- if self.index < len(definition_info.args_with_defaults):
- name = definition_info.args_with_defaults[0]
- if name in mapping.param_dict:
- del mapping.param_dict[name]
- class ArgumentAdder(_ArgumentChanger):
- def __init__(self, index, name, default=None, value=None):
- self.index = index
- self.name = name
- self.default = default
- self.value = value
- def change_definition_info(self, definition_info):
- for pair in definition_info.args_with_defaults:
- if pair[0] == self.name:
- raise rope.base.exceptions.RefactoringError(
- 'Adding duplicate parameter: <%s>.' % self.name)
- definition_info.args_with_defaults.insert(self.index,
- (self.name, self.default))
- def change_argument_mapping(self, definition_info, mapping):
- if self.value is not None:
- mapping.param_dict[self.name] = self.value
- class ArgumentDefaultInliner(_ArgumentChanger):
- def __init__(self, index):
- self.index = index
- self.remove = False
- def change_definition_info(self, definition_info):
- if self.remove:
- definition_info.args_with_defaults[self.index] = \
- (definition_info.args_with_defaults[self.index][0], None)
- def change_argument_mapping(self, definition_info, mapping):
- default = definition_info.args_with_defaults[self.index][1]
- name = definition_info.args_with_defaults[self.index][0]
- if default is not None and name not in mapping.param_dict:
- mapping.param_dict[name] = default
- class ArgumentReorderer(_ArgumentChanger):
- def __init__(self, new_order, autodef=None):
- """Construct an `ArgumentReorderer`
- Note that the `new_order` is a list containing the new
- position of parameters; not the position each parameter
- is going to be moved to. (changed in ``0.5m4``)
- For example changing ``f(a, b, c)`` to ``f(c, a, b)``
- requires passing ``[2, 0, 1]`` and *not* ``[1, 2, 0]``.
- The `autodef` (automatic default) argument, forces rope to use
- it as a default if a default is needed after the change. That
- happens when an argument without default is moved after
- another that has a default value. Note that `autodef` should
- be a string or `None`; the latter disables adding automatic
- default.
- """
- self.new_order = new_order
- self.autodef = autodef
- def change_definition_info(self, definition_info):
- new_args = list(definition_info.args_with_defaults)
- for new_index, index in enumerate(self.new_order):
- new_args[new_index] = definition_info.args_with_defaults[index]
- seen_default = False
- for index, (arg, default) in enumerate(list(new_args)):
- if default is not None:
- seen_default = True
- if seen_default and default is None and self.autodef is not None:
- new_args[index] = (arg, self.autodef)
- definition_info.args_with_defaults = new_args
- class _ChangeCallsInModule(object):
- def __init__(self, pycore, occurrence_finder, resource, call_changer):
- self.pycore = pycore
- self.occurrence_finder = occurrence_finder
- self.resource = resource
- self.call_changer = call_changer
- def get_changed_module(self):
- word_finder = worder.Worder(self.source)
- change_collector = codeanalyze.ChangeCollector(self.source)
- for occurrence in self.occurrence_finder.find_occurrences(self.resource):
- if not occurrence.is_called() and not occurrence.is_defined():
- continue
- start, end = occurrence.get_primary_range()
- begin_parens, end_parens = word_finder.get_word_parens_range(end - 1)
- if occurrence.is_called():
- primary, pyname = occurrence.get_primary_and_pyname()
- changed_call = self.call_changer.change_call(
- primary, pyname, self.source[start:end_parens])
- else:
- changed_call = self.call_changer.change_definition(
- self.source[start:end_parens])
- if changed_call is not None:
- change_collector.add_change(start, end_parens, changed_call)
- return change_collector.get_changed()
- @property
- @utils.saveit
- def pymodule(self):
- return self.pycore.resource_to_pyobject(self.resource)
- @property
- @utils.saveit
- def source(self):
- if self.resource is not None:
- return self.resource.read()
- else:
- return self.pymodule.source_code
- @property
- @utils.saveit
- def lines(self):
- return self.pymodule.lines
- class _MultipleFinders(object):
- def __init__(self, finders):
- self.finders = finders
- def find_occurrences(self, resource=None, pymodule=None):
- all_occurrences = []
- for finder in self.finders:
- all_occurrences.extend(finder.find_occurrences(resource, pymodule))
- all_occurrences.sort(self._cmp_occurrences)
- return all_occurrences
- def _cmp_occurrences(self, o1, o2):
- return cmp(o1.get_primary_range(), o2.get_primary_range())
|