Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(323)

Unified Diff: tools/telemetry/third_party/rope/rope/refactor/rename.py

Issue 1132103009: Example of refactoring using rope library. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Created 5 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: tools/telemetry/third_party/rope/rope/refactor/rename.py
diff --git a/tools/telemetry/third_party/rope/rope/refactor/rename.py b/tools/telemetry/third_party/rope/rope/refactor/rename.py
new file mode 100644
index 0000000000000000000000000000000000000000..3f1f5b7e6d61271124767e29a8be067c96e5199a
--- /dev/null
+++ b/tools/telemetry/third_party/rope/rope/refactor/rename.py
@@ -0,0 +1,220 @@
+import warnings
+
+from rope.base import (exceptions, pyobjects, pynames, taskhandle,
+ evaluate, worder, codeanalyze, libutils)
+from rope.base.change import ChangeSet, ChangeContents, MoveResource
+from rope.refactor import occurrences
+
+
+class Rename(object):
+ """A class for performing rename refactoring
+
+ It can rename everything: classes, functions, modules, packages,
+ methods, variables and keyword arguments.
+
+ """
+
+ def __init__(self, project, resource, offset=None):
+ """If `offset` is None, the `resource` itself will be renamed"""
+ self.project = project
+ self.resource = resource
+ if offset is not None:
+ self.old_name = worder.get_name_at(self.resource, offset)
+ this_pymodule = self.project.get_pymodule(self.resource)
+ self.old_instance, self.old_pyname = \
+ evaluate.eval_location2(this_pymodule, offset)
+ if self.old_pyname is None:
+ raise exceptions.RefactoringError(
+ 'Rename refactoring should be performed'
+ ' on resolvable python identifiers.')
+ else:
+ if not resource.is_folder() and resource.name == '__init__.py':
+ resource = resource.parent
+ dummy_pymodule = libutils.get_string_module(self.project, '')
+ self.old_instance = None
+ self.old_pyname = pynames.ImportedModule(dummy_pymodule,
+ resource=resource)
+ if resource.is_folder():
+ self.old_name = resource.name
+ else:
+ self.old_name = resource.name[:-3]
+
+ def get_old_name(self):
+ return self.old_name
+
+ def get_changes(self, new_name, in_file=None, in_hierarchy=False,
+ unsure=None, docs=False, resources=None,
+ task_handle=taskhandle.NullTaskHandle()):
+ """Get the changes needed for this refactoring
+
+ Parameters:
+
+ - `in_hierarchy`: when renaming a method this keyword forces
+ to rename all matching methods in the hierarchy
+ - `docs`: when `True` rename refactoring will rename
+ occurrences in comments and strings where the name is
+ visible. Setting it will make renames faster, too.
+ - `unsure`: decides what to do about unsure occurrences.
+ If `None`, they are ignored. Otherwise `unsure` is
+ called with an instance of `occurrence.Occurrence` as
+ parameter. If it returns `True`, the occurrence is
+ considered to be a match.
+ - `resources` can be a list of `rope.base.resources.File`\s to
+ apply this refactoring on. If `None`, the restructuring
+ will be applied to all python files.
+ - `in_file`: this argument has been deprecated; use
+ `resources` instead.
+
+ """
+ if unsure in (True, False):
+ warnings.warn(
+ 'unsure parameter should be a function that returns '
+ 'True or False', DeprecationWarning, stacklevel=2)
+
+ def unsure_func(value=unsure):
+ return value
+ unsure = unsure_func
+ if in_file is not None:
+ warnings.warn(
+ '`in_file` argument has been deprecated; use `resources` '
+ 'instead. ', DeprecationWarning, stacklevel=2)
+ if in_file:
+ resources = [self.resource]
+ if _is_local(self.old_pyname):
+ resources = [self.resource]
+ if resources is None:
+ resources = self.project.get_python_files()
+ changes = ChangeSet('Renaming <%s> to <%s>' %
+ (self.old_name, new_name))
+ finder = occurrences.create_finder(
+ self.project, self.old_name, self.old_pyname, unsure=unsure,
+ docs=docs, instance=self.old_instance,
+ in_hierarchy=in_hierarchy and self.is_method())
+ job_set = task_handle.create_jobset('Collecting Changes',
+ len(resources))
+ for file_ in resources:
+ job_set.started_job(file_.path)
+ new_content = rename_in_module(finder, new_name, resource=file_)
+ if new_content is not None:
+ changes.add_change(ChangeContents(file_, new_content))
+ job_set.finished_job()
+ if self._is_renaming_a_module():
+ resource = self.old_pyname.get_object().get_resource()
+ if self._is_allowed_to_move(resources, resource):
+ self._rename_module(resource, new_name, changes)
+ return changes
+
+ def _is_allowed_to_move(self, resources, resource):
+ if resource.is_folder():
+ try:
+ return resource.get_child('__init__.py') in resources
+ except exceptions.ResourceNotFoundError:
+ return False
+ else:
+ return resource in resources
+
+ def _is_renaming_a_module(self):
+ if isinstance(self.old_pyname.get_object(), pyobjects.AbstractModule):
+ return True
+ return False
+
+ def is_method(self):
+ pyname = self.old_pyname
+ return isinstance(pyname, pynames.DefinedName) and \
+ isinstance(pyname.get_object(), pyobjects.PyFunction) and \
+ isinstance(pyname.get_object().parent, pyobjects.PyClass)
+
+ def _rename_module(self, resource, new_name, changes):
+ if not resource.is_folder():
+ new_name = new_name + '.py'
+ parent_path = resource.parent.path
+ if parent_path == '':
+ new_location = new_name
+ else:
+ new_location = parent_path + '/' + new_name
+ changes.add_change(MoveResource(resource, new_location))
+
+
+class ChangeOccurrences(object):
+ """A class for changing the occurrences of a name in a scope
+
+ This class replaces the occurrences of a name. Note that it only
+ changes the scope containing the offset passed to the constructor.
+ What's more it does not have any side-effects. That is for
+ example changing occurrences of a module does not rename the
+ module; it merely replaces the occurrences of that module in a
+ scope with the given expression. This class is useful for
+ performing many custom refactorings.
+
+ """
+
+ def __init__(self, project, resource, offset):
+ self.project = project
+ self.resource = resource
+ self.offset = offset
+ self.old_name = worder.get_name_at(resource, offset)
+ self.pymodule = project.get_pymodule(self.resource)
+ self.old_pyname = evaluate.eval_location(self.pymodule, offset)
+
+ def get_old_name(self):
+ word_finder = worder.Worder(self.resource.read())
+ return word_finder.get_primary_at(self.offset)
+
+ def _get_scope_offset(self):
+ lines = self.pymodule.lines
+ scope = self.pymodule.get_scope().\
+ get_inner_scope_for_line(lines.get_line_number(self.offset))
+ start = lines.get_line_start(scope.get_start())
+ end = lines.get_line_end(scope.get_end())
+ return start, end
+
+ def get_changes(self, new_name, only_calls=False, reads=True, writes=True):
+ changes = ChangeSet('Changing <%s> occurrences to <%s>' %
+ (self.old_name, new_name))
+ scope_start, scope_end = self._get_scope_offset()
+ finder = occurrences.create_finder(
+ self.project, self.old_name, self.old_pyname,
+ imports=False, only_calls=only_calls)
+ new_contents = rename_in_module(
+ finder, new_name, pymodule=self.pymodule, replace_primary=True,
+ region=(scope_start, scope_end), reads=reads, writes=writes)
+ if new_contents is not None:
+ changes.add_change(ChangeContents(self.resource, new_contents))
+ return changes
+
+
+def rename_in_module(occurrences_finder, new_name, resource=None,
+ pymodule=None, replace_primary=False, region=None,
+ reads=True, writes=True):
+ """Returns the changed source or `None` if there is no changes"""
+ if resource is not None:
+ source_code = resource.read()
+ else:
+ source_code = pymodule.source_code
+ change_collector = codeanalyze.ChangeCollector(source_code)
+ for occurrence in occurrences_finder.find_occurrences(resource, pymodule):
+ if replace_primary and occurrence.is_a_fixed_primary():
+ continue
+ if replace_primary:
+ start, end = occurrence.get_primary_range()
+ else:
+ start, end = occurrence.get_word_range()
+ if (not reads and not occurrence.is_written()) or \
+ (not writes and occurrence.is_written()):
+ continue
+ if region is None or region[0] <= start < region[1]:
+ change_collector.add_change(start, end, new_name)
+ return change_collector.get_changed()
+
+
+def _is_local(pyname):
+ module, lineno = pyname.get_definition_location()
+ if lineno is None:
+ return False
+ scope = module.get_scope().get_inner_scope_for_line(lineno)
+ if isinstance(pyname, pynames.DefinedName) and \
+ scope.get_kind() in ('Function', 'Class'):
+ scope = scope.parent
+ return scope.get_kind() == 'Function' and \
+ pyname in scope.get_names().values() and \
+ isinstance(pyname, pynames.AssignedName)

Powered by Google App Engine
This is Rietveld 408576698