| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629 |
- """A module containing classes for move refactoring
- `create_move()` is a factory for creating move refactoring objects
- based on inputs.
- """
- from rope.base import pyobjects, codeanalyze, exceptions, pynames, taskhandle, evaluate, worder
- from rope.base.change import ChangeSet, ChangeContents, MoveResource
- from rope.refactor import importutils, rename, occurrences, sourceutils, functionutils
- def create_move(project, resource, offset=None):
- """A factory for creating Move objects
- Based on `resource` and `offset`, return one of `MoveModule`,
- `MoveGlobal` or `MoveMethod` for performing move refactoring.
- """
- if offset is None:
- return MoveModule(project, resource)
- this_pymodule = project.pycore.resource_to_pyobject(resource)
- pyname = evaluate.eval_location(this_pymodule, offset)
- if pyname is None:
- raise exceptions.RefactoringError(
- 'Move only works on classes, functions, modules and methods.')
- pyobject = pyname.get_object()
- if isinstance(pyobject, pyobjects.PyModule) or \
- isinstance(pyobject, pyobjects.PyPackage):
- return MoveModule(project, pyobject.get_resource())
- if isinstance(pyobject, pyobjects.PyFunction) and \
- isinstance(pyobject.parent, pyobjects.PyClass):
- return MoveMethod(project, resource, offset)
- if isinstance(pyobject, pyobjects.PyDefinedObject) and \
- isinstance(pyobject.parent, pyobjects.PyModule):
- return MoveGlobal(project, resource, offset)
- raise exceptions.RefactoringError(
- 'Move only works on global classes/functions, modules and methods.')
- class MoveMethod(object):
- """For moving methods
- It makes a new method in the destination class and changes
- the body of the old method to call the new method. You can
- inline the old method to change all of its occurrences.
- """
- def __init__(self, project, resource, offset):
- self.project = project
- self.pycore = project.pycore
- this_pymodule = self.pycore.resource_to_pyobject(resource)
- pyname = evaluate.eval_location(this_pymodule, offset)
- self.method_name = worder.get_name_at(resource, offset)
- self.pyfunction = pyname.get_object()
- if self.pyfunction.get_kind() != 'method':
- raise exceptions.RefactoringError('Only normal methods'
- ' can be moved.')
- def get_changes(self, dest_attr, new_name=None, resources=None,
- task_handle=taskhandle.NullTaskHandle()):
- """Return the changes needed for this refactoring
- Parameters:
- - `dest_attr`: the name of the destination attribute
- - `new_name`: the name of the new method; if `None` uses
- the old name
- - `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.
- """
- changes = ChangeSet('Moving method <%s>' % self.method_name)
- if resources is None:
- resources = self.pycore.get_python_files()
- if new_name is None:
- new_name = self.get_method_name()
- resource1, start1, end1, new_content1 = \
- self._get_changes_made_by_old_class(dest_attr, new_name)
- collector1 = codeanalyze.ChangeCollector(resource1.read())
- collector1.add_change(start1, end1, new_content1)
- resource2, start2, end2, new_content2 = \
- self._get_changes_made_by_new_class(dest_attr, new_name)
- if resource1 == resource2:
- collector1.add_change(start2, end2, new_content2)
- else:
- collector2 = codeanalyze.ChangeCollector(resource2.read())
- collector2.add_change(start2, end2, new_content2)
- result = collector2.get_changed()
- import_tools = importutils.ImportTools(self.pycore)
- new_imports = self._get_used_imports(import_tools)
- if new_imports:
- goal_pymodule = self.pycore.get_string_module(result,
- resource2)
- result = _add_imports_to_module(
- import_tools, goal_pymodule, new_imports)
- if resource2 in resources:
- changes.add_change(ChangeContents(resource2, result))
- if resource1 in resources:
- changes.add_change(ChangeContents(resource1,
- collector1.get_changed()))
- return changes
- def get_method_name(self):
- return self.method_name
- def _get_used_imports(self, import_tools):
- return importutils.get_imports(self.pycore, self.pyfunction)
- def _get_changes_made_by_old_class(self, dest_attr, new_name):
- pymodule = self.pyfunction.get_module()
- indents = self._get_scope_indents(self.pyfunction)
- body = 'return self.%s.%s(%s)\n' % (dest_attr, new_name,
- self._get_passed_arguments_string())
- region = sourceutils.get_body_region(self.pyfunction)
- return (pymodule.get_resource(), region[0], region[1],
- sourceutils.fix_indentation(body, indents))
- def _get_scope_indents(self, pyobject):
- pymodule = pyobject.get_module()
- return sourceutils.get_indents(
- pymodule.lines, pyobject.get_scope().get_start()) + \
- sourceutils.get_indent(self.pycore)
- def _get_changes_made_by_new_class(self, dest_attr, new_name):
- old_pyclass = self.pyfunction.parent
- if dest_attr not in old_pyclass:
- raise exceptions.RefactoringError(
- 'Destination attribute <%s> not found' % dest_attr)
- pyclass = old_pyclass[dest_attr].get_object().get_type()
- if not isinstance(pyclass, pyobjects.PyClass):
- raise exceptions.RefactoringError(
- 'Unknown class type for attribute <%s>' % dest_attr)
- pymodule = pyclass.get_module()
- resource = pyclass.get_module().get_resource()
- start, end = sourceutils.get_body_region(pyclass)
- pre_blanks = '\n'
- if pymodule.source_code[start:end].strip() != 'pass':
- pre_blanks = '\n\n'
- start = end
- indents = self._get_scope_indents(pyclass)
- body = pre_blanks + sourceutils.fix_indentation(
- self.get_new_method(new_name), indents)
- return resource, start, end, body
- def get_new_method(self, name):
- return '%s\n%s' % (
- self._get_new_header(name),
- sourceutils.fix_indentation(self._get_body(),
- sourceutils.get_indent(self.pycore)))
- def _get_unchanged_body(self):
- return sourceutils.get_body(self.pyfunction)
- def _get_body(self, host='host'):
- self_name = self._get_self_name()
- body = self_name + ' = None\n' + self._get_unchanged_body()
- pymodule = self.pycore.get_string_module(body)
- finder = occurrences.create_finder(
- self.pycore, self_name, pymodule[self_name])
- result = rename.rename_in_module(finder, host, pymodule=pymodule)
- if result is None:
- result = body
- return result[result.index('\n') + 1:]
- def _get_self_name(self):
- return self.pyfunction.get_param_names()[0]
- def _get_new_header(self, name):
- header = 'def %s(self' % name
- if self._is_host_used():
- header += ', host'
- definition_info = functionutils.DefinitionInfo.read(self.pyfunction)
- others = definition_info.arguments_to_string(1)
- if others:
- header += ', ' + others
- return header + '):'
- def _get_passed_arguments_string(self):
- result = ''
- if self._is_host_used():
- result = 'self'
- definition_info = functionutils.DefinitionInfo.read(self.pyfunction)
- others = definition_info.arguments_to_string(1)
- if others:
- if result:
- result += ', '
- result += others
- return result
- def _is_host_used(self):
- return self._get_body('__old_self') != self._get_unchanged_body()
- class MoveGlobal(object):
- """For moving global function and classes"""
- def __init__(self, project, resource, offset):
- self.pycore = project.pycore
- this_pymodule = self.pycore.resource_to_pyobject(resource)
- self.old_pyname = evaluate.eval_location(this_pymodule, offset)
- self.old_name = self.old_pyname.get_object().get_name()
- pymodule = self.old_pyname.get_object().get_module()
- self.source = pymodule.get_resource()
- self.tools = _MoveTools(self.pycore, self.source,
- self.old_pyname, self.old_name)
- self.import_tools = self.tools.import_tools
- self._check_exceptional_conditions()
- def _check_exceptional_conditions(self):
- if self.old_pyname is None or \
- not isinstance(self.old_pyname.get_object(), pyobjects.PyDefinedObject):
- raise exceptions.RefactoringError(
- 'Move refactoring should be performed on a class/function.')
- moving_pyobject = self.old_pyname.get_object()
- if not self._is_global(moving_pyobject):
- raise exceptions.RefactoringError(
- 'Move refactoring should be performed on a global class/function.')
- def _is_global(self, pyobject):
- return pyobject.get_scope().parent == pyobject.get_module().get_scope()
- def get_changes(self, dest, resources=None,
- task_handle=taskhandle.NullTaskHandle()):
- if resources is None:
- resources = self.pycore.get_python_files()
- if dest is None or not dest.exists():
- raise exceptions.RefactoringError(
- 'Move destination does not exist.')
- if dest.is_folder() and dest.has_child('__init__.py'):
- dest = dest.get_child('__init__.py')
- if dest.is_folder():
- raise exceptions.RefactoringError(
- 'Move destination for non-modules should not be folders.')
- if self.source == dest:
- raise exceptions.RefactoringError(
- 'Moving global elements to the same module.')
- return self._calculate_changes(dest, resources, task_handle)
- def _calculate_changes(self, dest, resources, task_handle):
- changes = ChangeSet('Moving global <%s>' % self.old_name)
- job_set = task_handle.create_jobset('Collecting Changes',
- len(resources))
- for file_ in resources:
- job_set.started_job(file_.path)
- if file_ == self.source:
- changes.add_change(self._source_module_changes(dest))
- elif file_ == dest:
- changes.add_change(self._dest_module_changes(dest))
- elif self.tools.occurs_in_module(resource=file_):
- pymodule = self.pycore.resource_to_pyobject(file_)
- # Changing occurrences
- placeholder = '__rope_renaming_%s_' % self.old_name
- source = self.tools.rename_in_module(placeholder,
- resource=file_)
- should_import = source is not None
- # Removing out of date imports
- pymodule = self.tools.new_pymodule(pymodule, source)
- source = self.tools.remove_old_imports(pymodule)
- # Adding new import
- if should_import:
- pymodule = self.tools.new_pymodule(pymodule, source)
- source, imported = importutils.add_import(
- self.pycore, pymodule, self._new_modname(dest), self.old_name)
- source = source.replace(placeholder, imported)
- source = self.tools.new_source(pymodule, source)
- if source != file_.read():
- changes.add_change(ChangeContents(file_, source))
- job_set.finished_job()
- return changes
- def _source_module_changes(self, dest):
- placeholder = '__rope_moving_%s_' % self.old_name
- handle = _ChangeMoveOccurrencesHandle(placeholder)
- occurrence_finder = occurrences.create_finder(
- self.pycore, self.old_name, self.old_pyname)
- start, end = self._get_moving_region()
- renamer = ModuleSkipRenamer(occurrence_finder, self.source,
- handle, start, end)
- source = renamer.get_changed_module()
- if handle.occurred:
- pymodule = self.pycore.get_string_module(source, self.source)
- # Adding new import
- source, imported = importutils.add_import(
- self.pycore, pymodule, self._new_modname(dest), self.old_name)
- source = source.replace(placeholder, imported)
- return ChangeContents(self.source, source)
- def _new_modname(self, dest):
- return self.pycore.modname(dest)
- def _dest_module_changes(self, dest):
- # Changing occurrences
- pymodule = self.pycore.resource_to_pyobject(dest)
- source = self.tools.rename_in_module(self.old_name, pymodule)
- pymodule = self.tools.new_pymodule(pymodule, source)
- moving, imports = self._get_moving_element_with_imports()
- source = self.tools.remove_old_imports(pymodule)
- pymodule = self.tools.new_pymodule(pymodule, source)
- pymodule, has_changed = self._add_imports2(pymodule, imports)
- module_with_imports = self.import_tools.module_imports(pymodule)
- source = pymodule.source_code
- lineno = 0
- if module_with_imports.imports:
- lineno = module_with_imports.imports[-1].end_line - 1
- else:
- while lineno < pymodule.lines.length() and \
- pymodule.lines.get_line(lineno + 1).lstrip().startswith('#'):
- lineno += 1
- if lineno > 0:
- cut = pymodule.lines.get_line_end(lineno) + 1
- result = source[:cut] + '\n\n' + moving + source[cut:]
- else:
- result = moving + source
- # Organizing imports
- source = result
- pymodule = self.pycore.get_string_module(source, dest)
- source = self.import_tools.organize_imports(pymodule, sort=False,
- unused=False)
- return ChangeContents(dest, source)
- def _get_moving_element_with_imports(self):
- return moving_code_with_imports(
- self.pycore, self.source, self._get_moving_element())
- def _get_module_with_imports(self, source_code, resource):
- pymodule = self.pycore.get_string_module(source_code, resource)
- return self.import_tools.module_imports(pymodule)
- def _get_moving_element(self):
- start, end = self._get_moving_region()
- moving = self.source.read()[start:end]
- return moving.rstrip() + '\n'
- def _get_moving_region(self):
- pymodule = self.pycore.resource_to_pyobject(self.source)
- lines = pymodule.lines
- scope = self.old_pyname.get_object().get_scope()
- start = lines.get_line_start(scope.get_start())
- end_line = scope.get_end()
- while end_line < lines.length() and \
- lines.get_line(end_line + 1).strip() == '':
- end_line += 1
- end = min(lines.get_line_end(end_line) + 1, len(pymodule.source_code))
- return start, end
- def _add_imports2(self, pymodule, new_imports):
- source = self.tools.add_imports(pymodule, new_imports)
- if source is None:
- return pymodule, False
- else:
- resource = pymodule.get_resource()
- pymodule = self.pycore.get_string_module(source, resource)
- return pymodule, True
- class MoveModule(object):
- """For moving modules and packages"""
- def __init__(self, project, resource):
- self.project = project
- self.pycore = project.pycore
- if not resource.is_folder() and resource.name == '__init__.py':
- resource = resource.parent
- if resource.is_folder() and not resource.has_child('__init__.py'):
- raise exceptions.RefactoringError(
- 'Cannot move non-package folder.')
- dummy_pymodule = self.pycore.get_string_module('')
- self.old_pyname = pynames.ImportedModule(dummy_pymodule,
- resource=resource)
- self.source = self.old_pyname.get_object().get_resource()
- if self.source.is_folder():
- self.old_name = self.source.name
- else:
- self.old_name = self.source.name[:-3]
- self.tools = _MoveTools(self.pycore, self.source,
- self.old_pyname, self.old_name)
- self.import_tools = self.tools.import_tools
- def get_changes(self, dest, resources=None,
- task_handle=taskhandle.NullTaskHandle()):
- moving_pyobject = self.old_pyname.get_object()
- if resources is None:
- resources = self.pycore.get_python_files()
- if dest is None or not dest.is_folder():
- raise exceptions.RefactoringError(
- 'Move destination for modules should be packages.')
- return self._calculate_changes(dest, resources, task_handle)
- def _calculate_changes(self, dest, resources, task_handle):
- changes = ChangeSet('Moving module <%s>' % self.old_name)
- job_set = task_handle.create_jobset('Collecting changes',
- len(resources))
- for module in resources:
- job_set.started_job(module.path)
- if module == self.source:
- self._change_moving_module(changes, dest)
- else:
- source = self._change_occurrences_in_module(dest,
- resource=module)
- if source is not None:
- changes.add_change(ChangeContents(module, source))
- job_set.finished_job()
- if self.project == self.source.project:
- changes.add_change(MoveResource(self.source, dest.path))
- return changes
- def _new_modname(self, dest):
- destname = self.pycore.modname(dest)
- if destname:
- return destname + '.' + self.old_name
- return self.old_name
- def _new_import(self, dest):
- return importutils.NormalImport([(self._new_modname(dest), None)])
- def _change_moving_module(self, changes, dest):
- if not self.source.is_folder():
- pymodule = self.pycore.resource_to_pyobject(self.source)
- source = self.import_tools.relatives_to_absolutes(pymodule)
- pymodule = self.tools.new_pymodule(pymodule, source)
- source = self._change_occurrences_in_module(dest, pymodule)
- source = self.tools.new_source(pymodule, source)
- if source != self.source.read():
- changes.add_change(ChangeContents(self.source, source))
- def _change_occurrences_in_module(self, dest, pymodule=None,
- resource=None):
- if not self.tools.occurs_in_module(pymodule=pymodule,
- resource=resource):
- return
- if pymodule is None:
- pymodule = self.pycore.resource_to_pyobject(resource)
- new_name = self._new_modname(dest)
- new_import = self._new_import(dest)
- source = self.tools.rename_in_module(
- new_name, imports=True, pymodule=pymodule, resource=resource)
- should_import = self.tools.occurs_in_module(
- pymodule=pymodule, resource=resource, imports=False)
- pymodule = self.tools.new_pymodule(pymodule, source)
- source = self.tools.remove_old_imports(pymodule)
- if should_import:
- pymodule = self.tools.new_pymodule(pymodule, source)
- source = self.tools.add_imports(pymodule, [new_import])
- source = self.tools.new_source(pymodule, source)
- if source != pymodule.resource.read():
- return source
- class _ChangeMoveOccurrencesHandle(object):
- def __init__(self, new_name):
- self.new_name = new_name
- self.occurred = False
- def occurred_inside_skip(self, change_collector, occurrence):
- pass
- def occurred_outside_skip(self, change_collector, occurrence):
- start, end = occurrence.get_primary_range()
- change_collector.add_change(start, end, self.new_name)
- self.occurred = True
- class _MoveTools(object):
- def __init__(self, pycore, source, pyname, old_name):
- self.pycore = pycore
- self.source = source
- self.old_pyname = pyname
- self.old_name = old_name
- self.import_tools = importutils.ImportTools(self.pycore)
- def remove_old_imports(self, pymodule):
- old_source = pymodule.source_code
- module_with_imports = self.import_tools.module_imports(pymodule)
- class CanSelect(object):
- changed = False
- old_name = self.old_name
- old_pyname = self.old_pyname
- def __call__(self, name):
- try:
- if name == self.old_name and \
- pymodule[name].get_object() == \
- self.old_pyname.get_object():
- self.changed = True
- return False
- except exceptions.AttributeNotFoundError:
- pass
- return True
- can_select = CanSelect()
- module_with_imports.filter_names(can_select)
- new_source = module_with_imports.get_changed_source()
- if old_source != new_source:
- return new_source
- def rename_in_module(self, new_name, pymodule=None,
- imports=False, resource=None):
- occurrence_finder = self._create_finder(imports)
- source = rename.rename_in_module(
- occurrence_finder, new_name, replace_primary=True,
- pymodule=pymodule, resource=resource)
- return source
- def occurs_in_module(self, pymodule=None, resource=None, imports=True):
- finder = self._create_finder(imports)
- for occurrence in finder.find_occurrences(pymodule=pymodule,
- resource=resource):
- return True
- return False
- def _create_finder(self, imports):
- return occurrences.create_finder(self.pycore, self.old_name,
- self.old_pyname, imports=imports)
- def new_pymodule(self, pymodule, source):
- if source is not None:
- return self.pycore.get_string_module(
- source, pymodule.get_resource())
- return pymodule
- def new_source(self, pymodule, source):
- if source is None:
- return pymodule.source_code
- return source
- def add_imports(self, pymodule, new_imports):
- return _add_imports_to_module(self.import_tools, pymodule, new_imports)
- def _add_imports_to_module(import_tools, pymodule, new_imports):
- module_with_imports = import_tools.module_imports(pymodule)
- for new_import in new_imports:
- module_with_imports.add_import(new_import)
- return module_with_imports.get_changed_source()
- def moving_code_with_imports(pycore, resource, source):
- import_tools = importutils.ImportTools(pycore)
- pymodule = pycore.get_string_module(source, resource)
- origin = pycore.resource_to_pyobject(resource)
- imports = []
- for stmt in import_tools.module_imports(origin).imports:
- imports.append(stmt.import_info)
- back_names = []
- for name in origin:
- if name not in pymodule:
- back_names.append(name)
- imports.append(import_tools.get_from_import(resource, back_names))
- source = _add_imports_to_module(import_tools, pymodule, imports)
- pymodule = pycore.get_string_module(source, resource)
- source = import_tools.relatives_to_absolutes(pymodule)
- pymodule = pycore.get_string_module(source, resource)
- source = import_tools.organize_imports(pymodule, selfs=False)
- pymodule = pycore.get_string_module(source, resource)
- # extracting imports after changes
- module_imports = import_tools.module_imports(pymodule)
- imports = [import_stmt.import_info
- for import_stmt in module_imports.imports]
- start = 1
- if module_imports.imports:
- start = module_imports.imports[-1].end_line
- lines = codeanalyze.SourceLinesAdapter(source)
- while start < lines.length() and not lines.get_line(start).strip():
- start += 1
- moving = source[lines.get_line_start(start):]
- return moving, imports
- class ModuleSkipRenamerHandle(object):
- def occurred_outside_skip(self, change_collector, occurrence):
- pass
- def occurred_inside_skip(self, change_collector, occurrence):
- pass
- class ModuleSkipRenamer(object):
- """Rename occurrences in a module
- This class can be used when you want to treat a region in a file
- separately from other parts when renaming.
- """
- def __init__(self, occurrence_finder, resource, handle=None,
- skip_start=0, skip_end=0, replacement=''):
- """Constructor
- if replacement is `None` the region is not changed. Otherwise
- it is replaced with `replacement`.
- """
- self.occurrence_finder = occurrence_finder
- self.resource = resource
- self.skip_start = skip_start
- self.skip_end = skip_end
- self.replacement = replacement
- self.handle = handle
- if self.handle is None:
- self.handle = ModuleSkipHandle()
- def get_changed_module(self):
- source = self.resource.read()
- change_collector = codeanalyze.ChangeCollector(source)
- if self.replacement is not None:
- change_collector.add_change(self.skip_start, self.skip_end,
- self.replacement)
- for occurrence in self.occurrence_finder.find_occurrences(self.resource):
- start, end = occurrence.get_primary_range()
- if self.skip_start <= start < self.skip_end:
- self.handle.occurred_inside_skip(change_collector, occurrence)
- else:
- self.handle.occurred_outside_skip(change_collector, occurrence)
- result = change_collector.get_changed()
- if result is not None and result != source:
- return result
|