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 |
+ |