| Index: tools/telemetry/third_party/rope/rope/refactor/change_signature.py
|
| diff --git a/tools/telemetry/third_party/rope/rope/refactor/change_signature.py b/tools/telemetry/third_party/rope/rope/refactor/change_signature.py
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..b5ba1856a80746cef2ff8c2cbeb84fd296138fbc
|
| --- /dev/null
|
| +++ b/tools/telemetry/third_party/rope/rope/refactor/change_signature.py
|
| @@ -0,0 +1,352 @@
|
| +import copy
|
| +
|
| +import rope.base.exceptions
|
| +from rope.base import codeanalyze
|
| +from rope.base import evaluate
|
| +from rope.base import pyobjects
|
| +from rope.base import taskhandle
|
| +from rope.base import utils
|
| +from rope.base import worder
|
| +from rope.base.change import ChangeContents, ChangeSet
|
| +from rope.refactor import occurrences, functionutils
|
| +
|
| +
|
| +class ChangeSignature(object):
|
| +
|
| + def __init__(self, project, resource, offset):
|
| + self.project = project
|
| + 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.project.get_pymodule(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.project.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.project, 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.project, name, pyname, only_calls=True)
|
| + finder = _MultipleFinders([finder, constructor_finder])
|
| + for file in resources:
|
| + job_set.started_job(file.path)
|
| + change_calls = _ChangeCallsInModule(
|
| + self.project, 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, project, occurrence_finder, resource, call_changer):
|
| + self.project = project
|
| + 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.project.get_pymodule(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(key=lambda x: x.get_primary_range())
|
| + return all_occurrences
|
| +
|
|
|