| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216 |
- import warnings
- from rope.base import exceptions, pyobjects, pynames, taskhandle, evaluate, worder, codeanalyze
- from rope.base.change import ChangeSet, ChangeContents, MoveResource
- from rope.refactor import occurrences, sourceutils
- 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.pycore = project.pycore
- self.resource = resource
- if offset is not None:
- self.old_name = worder.get_name_at(self.resource, offset)
- this_pymodule = self.pycore.resource_to_pyobject(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 = self.pycore.get_string_module('')
- 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.pycore.get_python_files()
- changes = ChangeSet('Renaming <%s> to <%s>' %
- (self.old_name, new_name))
- finder = occurrences.create_finder(
- self.pycore, 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.pycore = project.pycore
- self.resource = resource
- self.offset = offset
- self.old_name = worder.get_name_at(resource, offset)
- self.pymodule = self.pycore.resource_to_pyobject(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.pycore, 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)
|