OLD | NEW |
(Empty) | |
| 1 """A module containing classes for move refactoring |
| 2 |
| 3 `create_move()` is a factory for creating move refactoring objects |
| 4 based on inputs. |
| 5 |
| 6 """ |
| 7 from rope.base import (pyobjects, codeanalyze, exceptions, pynames, |
| 8 taskhandle, evaluate, worder, libutils) |
| 9 from rope.base.change import ChangeSet, ChangeContents, MoveResource |
| 10 from rope.refactor import importutils, rename, occurrences, sourceutils, \ |
| 11 functionutils |
| 12 |
| 13 |
| 14 def create_move(project, resource, offset=None): |
| 15 """A factory for creating Move objects |
| 16 |
| 17 Based on `resource` and `offset`, return one of `MoveModule`, |
| 18 `MoveGlobal` or `MoveMethod` for performing move refactoring. |
| 19 |
| 20 """ |
| 21 if offset is None: |
| 22 return MoveModule(project, resource) |
| 23 this_pymodule = project.get_pymodule(resource) |
| 24 pyname = evaluate.eval_location(this_pymodule, offset) |
| 25 if pyname is None: |
| 26 raise exceptions.RefactoringError( |
| 27 'Move only works on classes, functions, modules and methods.') |
| 28 pyobject = pyname.get_object() |
| 29 if isinstance(pyobject, pyobjects.PyModule) or \ |
| 30 isinstance(pyobject, pyobjects.PyPackage): |
| 31 return MoveModule(project, pyobject.get_resource()) |
| 32 if isinstance(pyobject, pyobjects.PyFunction) and \ |
| 33 isinstance(pyobject.parent, pyobjects.PyClass): |
| 34 return MoveMethod(project, resource, offset) |
| 35 if isinstance(pyobject, pyobjects.PyDefinedObject) and \ |
| 36 isinstance(pyobject.parent, pyobjects.PyModule): |
| 37 return MoveGlobal(project, resource, offset) |
| 38 raise exceptions.RefactoringError( |
| 39 'Move only works on global classes/functions, modules and methods.') |
| 40 |
| 41 |
| 42 class MoveMethod(object): |
| 43 """For moving methods |
| 44 |
| 45 It makes a new method in the destination class and changes |
| 46 the body of the old method to call the new method. You can |
| 47 inline the old method to change all of its occurrences. |
| 48 |
| 49 """ |
| 50 |
| 51 def __init__(self, project, resource, offset): |
| 52 self.project = project |
| 53 this_pymodule = self.project.get_pymodule(resource) |
| 54 pyname = evaluate.eval_location(this_pymodule, offset) |
| 55 self.method_name = worder.get_name_at(resource, offset) |
| 56 self.pyfunction = pyname.get_object() |
| 57 if self.pyfunction.get_kind() != 'method': |
| 58 raise exceptions.RefactoringError('Only normal methods' |
| 59 ' can be moved.') |
| 60 |
| 61 def get_changes(self, dest_attr, new_name=None, resources=None, |
| 62 task_handle=taskhandle.NullTaskHandle()): |
| 63 """Return the changes needed for this refactoring |
| 64 |
| 65 Parameters: |
| 66 |
| 67 - `dest_attr`: the name of the destination attribute |
| 68 - `new_name`: the name of the new method; if `None` uses |
| 69 the old name |
| 70 - `resources` can be a list of `rope.base.resources.File`\s to |
| 71 apply this refactoring on. If `None`, the restructuring |
| 72 will be applied to all python files. |
| 73 |
| 74 """ |
| 75 changes = ChangeSet('Moving method <%s>' % self.method_name) |
| 76 if resources is None: |
| 77 resources = self.project.get_python_files() |
| 78 if new_name is None: |
| 79 new_name = self.get_method_name() |
| 80 resource1, start1, end1, new_content1 = \ |
| 81 self._get_changes_made_by_old_class(dest_attr, new_name) |
| 82 collector1 = codeanalyze.ChangeCollector(resource1.read()) |
| 83 collector1.add_change(start1, end1, new_content1) |
| 84 |
| 85 resource2, start2, end2, new_content2 = \ |
| 86 self._get_changes_made_by_new_class(dest_attr, new_name) |
| 87 if resource1 == resource2: |
| 88 collector1.add_change(start2, end2, new_content2) |
| 89 else: |
| 90 collector2 = codeanalyze.ChangeCollector(resource2.read()) |
| 91 collector2.add_change(start2, end2, new_content2) |
| 92 result = collector2.get_changed() |
| 93 import_tools = importutils.ImportTools(self.project) |
| 94 new_imports = self._get_used_imports(import_tools) |
| 95 if new_imports: |
| 96 goal_pymodule = libutils.get_string_module( |
| 97 self.project, result, resource2) |
| 98 result = _add_imports_to_module( |
| 99 import_tools, goal_pymodule, new_imports) |
| 100 if resource2 in resources: |
| 101 changes.add_change(ChangeContents(resource2, result)) |
| 102 |
| 103 if resource1 in resources: |
| 104 changes.add_change(ChangeContents(resource1, |
| 105 collector1.get_changed())) |
| 106 return changes |
| 107 |
| 108 def get_method_name(self): |
| 109 return self.method_name |
| 110 |
| 111 def _get_used_imports(self, import_tools): |
| 112 return importutils.get_imports(self.project, self.pyfunction) |
| 113 |
| 114 def _get_changes_made_by_old_class(self, dest_attr, new_name): |
| 115 pymodule = self.pyfunction.get_module() |
| 116 indents = self._get_scope_indents(self.pyfunction) |
| 117 body = 'return self.%s.%s(%s)\n' % ( |
| 118 dest_attr, new_name, self._get_passed_arguments_string()) |
| 119 region = sourceutils.get_body_region(self.pyfunction) |
| 120 return (pymodule.get_resource(), region[0], region[1], |
| 121 sourceutils.fix_indentation(body, indents)) |
| 122 |
| 123 def _get_scope_indents(self, pyobject): |
| 124 pymodule = pyobject.get_module() |
| 125 return sourceutils.get_indents( |
| 126 pymodule.lines, pyobject.get_scope().get_start()) + \ |
| 127 sourceutils.get_indent(self.project) |
| 128 |
| 129 def _get_changes_made_by_new_class(self, dest_attr, new_name): |
| 130 old_pyclass = self.pyfunction.parent |
| 131 if dest_attr not in old_pyclass: |
| 132 raise exceptions.RefactoringError( |
| 133 'Destination attribute <%s> not found' % dest_attr) |
| 134 pyclass = old_pyclass[dest_attr].get_object().get_type() |
| 135 if not isinstance(pyclass, pyobjects.PyClass): |
| 136 raise exceptions.RefactoringError( |
| 137 'Unknown class type for attribute <%s>' % dest_attr) |
| 138 pymodule = pyclass.get_module() |
| 139 resource = pyclass.get_module().get_resource() |
| 140 start, end = sourceutils.get_body_region(pyclass) |
| 141 pre_blanks = '\n' |
| 142 if pymodule.source_code[start:end].strip() != 'pass': |
| 143 pre_blanks = '\n\n' |
| 144 start = end |
| 145 indents = self._get_scope_indents(pyclass) |
| 146 body = pre_blanks + sourceutils.fix_indentation( |
| 147 self.get_new_method(new_name), indents) |
| 148 return resource, start, end, body |
| 149 |
| 150 def get_new_method(self, name): |
| 151 return '%s\n%s' % ( |
| 152 self._get_new_header(name), |
| 153 sourceutils.fix_indentation(self._get_body(), |
| 154 sourceutils.get_indent(self.project))) |
| 155 |
| 156 def _get_unchanged_body(self): |
| 157 return sourceutils.get_body(self.pyfunction) |
| 158 |
| 159 def _get_body(self, host='host'): |
| 160 self_name = self._get_self_name() |
| 161 body = self_name + ' = None\n' + self._get_unchanged_body() |
| 162 pymodule = libutils.get_string_module(self.project, body) |
| 163 finder = occurrences.create_finder( |
| 164 self.project, self_name, pymodule[self_name]) |
| 165 result = rename.rename_in_module(finder, host, pymodule=pymodule) |
| 166 if result is None: |
| 167 result = body |
| 168 return result[result.index('\n') + 1:] |
| 169 |
| 170 def _get_self_name(self): |
| 171 return self.pyfunction.get_param_names()[0] |
| 172 |
| 173 def _get_new_header(self, name): |
| 174 header = 'def %s(self' % name |
| 175 if self._is_host_used(): |
| 176 header += ', host' |
| 177 definition_info = functionutils.DefinitionInfo.read(self.pyfunction) |
| 178 others = definition_info.arguments_to_string(1) |
| 179 if others: |
| 180 header += ', ' + others |
| 181 return header + '):' |
| 182 |
| 183 def _get_passed_arguments_string(self): |
| 184 result = '' |
| 185 if self._is_host_used(): |
| 186 result = 'self' |
| 187 definition_info = functionutils.DefinitionInfo.read(self.pyfunction) |
| 188 others = definition_info.arguments_to_string(1) |
| 189 if others: |
| 190 if result: |
| 191 result += ', ' |
| 192 result += others |
| 193 return result |
| 194 |
| 195 def _is_host_used(self): |
| 196 return self._get_body('__old_self') != self._get_unchanged_body() |
| 197 |
| 198 |
| 199 class MoveGlobal(object): |
| 200 """For moving global function and classes""" |
| 201 |
| 202 def __init__(self, project, resource, offset): |
| 203 self.project = project |
| 204 this_pymodule = self.project.get_pymodule(resource) |
| 205 self.old_pyname = evaluate.eval_location(this_pymodule, offset) |
| 206 self.old_name = self.old_pyname.get_object().get_name() |
| 207 pymodule = self.old_pyname.get_object().get_module() |
| 208 self.source = pymodule.get_resource() |
| 209 self.tools = _MoveTools(self.project, self.source, |
| 210 self.old_pyname, self.old_name) |
| 211 self.import_tools = self.tools.import_tools |
| 212 self._check_exceptional_conditions() |
| 213 |
| 214 def _check_exceptional_conditions(self): |
| 215 if self.old_pyname is None or \ |
| 216 not isinstance(self.old_pyname.get_object(), |
| 217 pyobjects.PyDefinedObject): |
| 218 raise exceptions.RefactoringError( |
| 219 'Move refactoring should be performed on a class/function.') |
| 220 moving_pyobject = self.old_pyname.get_object() |
| 221 if not self._is_global(moving_pyobject): |
| 222 raise exceptions.RefactoringError( |
| 223 'Move refactoring should be performed ' + |
| 224 'on a global class/function.') |
| 225 |
| 226 def _is_global(self, pyobject): |
| 227 return pyobject.get_scope().parent == pyobject.get_module().get_scope() |
| 228 |
| 229 def get_changes(self, dest, resources=None, |
| 230 task_handle=taskhandle.NullTaskHandle()): |
| 231 if resources is None: |
| 232 resources = self.project.get_python_files() |
| 233 if dest is None or not dest.exists(): |
| 234 raise exceptions.RefactoringError( |
| 235 'Move destination does not exist.') |
| 236 if dest.is_folder() and dest.has_child('__init__.py'): |
| 237 dest = dest.get_child('__init__.py') |
| 238 if dest.is_folder(): |
| 239 raise exceptions.RefactoringError( |
| 240 'Move destination for non-modules should not be folders.') |
| 241 if self.source == dest: |
| 242 raise exceptions.RefactoringError( |
| 243 'Moving global elements to the same module.') |
| 244 return self._calculate_changes(dest, resources, task_handle) |
| 245 |
| 246 def _calculate_changes(self, dest, resources, task_handle): |
| 247 changes = ChangeSet('Moving global <%s>' % self.old_name) |
| 248 job_set = task_handle.create_jobset('Collecting Changes', |
| 249 len(resources)) |
| 250 for file_ in resources: |
| 251 job_set.started_job(file_.path) |
| 252 if file_ == self.source: |
| 253 changes.add_change(self._source_module_changes(dest)) |
| 254 elif file_ == dest: |
| 255 changes.add_change(self._dest_module_changes(dest)) |
| 256 elif self.tools.occurs_in_module(resource=file_): |
| 257 pymodule = self.project.get_pymodule(file_) |
| 258 # Changing occurrences |
| 259 placeholder = '__rope_renaming_%s_' % self.old_name |
| 260 source = self.tools.rename_in_module(placeholder, |
| 261 resource=file_) |
| 262 should_import = source is not None |
| 263 # Removing out of date imports |
| 264 pymodule = self.tools.new_pymodule(pymodule, source) |
| 265 source = self.tools.remove_old_imports(pymodule) |
| 266 # Adding new import |
| 267 if should_import: |
| 268 pymodule = self.tools.new_pymodule(pymodule, source) |
| 269 source, imported = importutils.add_import( |
| 270 self.project, pymodule, self._new_modname(dest), |
| 271 self.old_name) |
| 272 source = source.replace(placeholder, imported) |
| 273 source = self.tools.new_source(pymodule, source) |
| 274 if source != file_.read(): |
| 275 changes.add_change(ChangeContents(file_, source)) |
| 276 job_set.finished_job() |
| 277 return changes |
| 278 |
| 279 def _source_module_changes(self, dest): |
| 280 placeholder = '__rope_moving_%s_' % self.old_name |
| 281 handle = _ChangeMoveOccurrencesHandle(placeholder) |
| 282 occurrence_finder = occurrences.create_finder( |
| 283 self.project, self.old_name, self.old_pyname) |
| 284 start, end = self._get_moving_region() |
| 285 renamer = ModuleSkipRenamer(occurrence_finder, self.source, |
| 286 handle, start, end) |
| 287 source = renamer.get_changed_module() |
| 288 if handle.occurred: |
| 289 pymodule = libutils.get_string_module( |
| 290 self.project, source, self.source) |
| 291 # Adding new import |
| 292 source, imported = importutils.add_import( |
| 293 self.project, pymodule, self._new_modname(dest), self.old_name) |
| 294 source = source.replace(placeholder, imported) |
| 295 return ChangeContents(self.source, source) |
| 296 |
| 297 def _new_modname(self, dest): |
| 298 return libutils.modname(dest) |
| 299 |
| 300 def _dest_module_changes(self, dest): |
| 301 # Changing occurrences |
| 302 pymodule = self.project.get_pymodule(dest) |
| 303 source = self.tools.rename_in_module(self.old_name, pymodule) |
| 304 pymodule = self.tools.new_pymodule(pymodule, source) |
| 305 |
| 306 moving, imports = self._get_moving_element_with_imports() |
| 307 source = self.tools.remove_old_imports(pymodule) |
| 308 pymodule = self.tools.new_pymodule(pymodule, source) |
| 309 pymodule, has_changed = self._add_imports2(pymodule, imports) |
| 310 |
| 311 module_with_imports = self.import_tools.module_imports(pymodule) |
| 312 source = pymodule.source_code |
| 313 lineno = 0 |
| 314 if module_with_imports.imports: |
| 315 lineno = module_with_imports.imports[-1].end_line - 1 |
| 316 else: |
| 317 while lineno < pymodule.lines.length() and \ |
| 318 pymodule.lines.get_line(lineno + 1).\ |
| 319 lstrip().startswith('#'): |
| 320 lineno += 1 |
| 321 if lineno > 0: |
| 322 cut = pymodule.lines.get_line_end(lineno) + 1 |
| 323 result = source[:cut] + '\n\n' + moving + source[cut:] |
| 324 else: |
| 325 result = moving + source |
| 326 |
| 327 # Organizing imports |
| 328 source = result |
| 329 pymodule = libutils.get_string_module(self.project, source, dest) |
| 330 source = self.import_tools.organize_imports(pymodule, sort=False, |
| 331 unused=False) |
| 332 return ChangeContents(dest, source) |
| 333 |
| 334 def _get_moving_element_with_imports(self): |
| 335 return moving_code_with_imports( |
| 336 self.project, self.source, self._get_moving_element()) |
| 337 |
| 338 def _get_module_with_imports(self, source_code, resource): |
| 339 pymodule = libutils.get_string_module( |
| 340 self.project, source_code, resource) |
| 341 return self.import_tools.module_imports(pymodule) |
| 342 |
| 343 def _get_moving_element(self): |
| 344 start, end = self._get_moving_region() |
| 345 moving = self.source.read()[start:end] |
| 346 return moving.rstrip() + '\n' |
| 347 |
| 348 def _get_moving_region(self): |
| 349 pymodule = self.project.get_pymodule(self.source) |
| 350 lines = pymodule.lines |
| 351 scope = self.old_pyname.get_object().get_scope() |
| 352 start = lines.get_line_start(scope.get_start()) |
| 353 end_line = scope.get_end() |
| 354 while end_line < lines.length() and \ |
| 355 lines.get_line(end_line + 1).strip() == '': |
| 356 end_line += 1 |
| 357 end = min(lines.get_line_end(end_line) + 1, len(pymodule.source_code)) |
| 358 return start, end |
| 359 |
| 360 def _add_imports2(self, pymodule, new_imports): |
| 361 source = self.tools.add_imports(pymodule, new_imports) |
| 362 if source is None: |
| 363 return pymodule, False |
| 364 else: |
| 365 resource = pymodule.get_resource() |
| 366 pymodule = libutils.get_string_module( |
| 367 self.project, source, resource) |
| 368 return pymodule, True |
| 369 |
| 370 |
| 371 class MoveModule(object): |
| 372 """For moving modules and packages""" |
| 373 |
| 374 def __init__(self, project, resource): |
| 375 self.project = project |
| 376 if not resource.is_folder() and resource.name == '__init__.py': |
| 377 resource = resource.parent |
| 378 if resource.is_folder() and not resource.has_child('__init__.py'): |
| 379 raise exceptions.RefactoringError( |
| 380 'Cannot move non-package folder.') |
| 381 dummy_pymodule = libutils.get_string_module(self.project, '') |
| 382 self.old_pyname = pynames.ImportedModule(dummy_pymodule, |
| 383 resource=resource) |
| 384 self.source = self.old_pyname.get_object().get_resource() |
| 385 if self.source.is_folder(): |
| 386 self.old_name = self.source.name |
| 387 else: |
| 388 self.old_name = self.source.name[:-3] |
| 389 self.tools = _MoveTools(self.project, self.source, |
| 390 self.old_pyname, self.old_name) |
| 391 self.import_tools = self.tools.import_tools |
| 392 |
| 393 def get_changes(self, dest, resources=None, |
| 394 task_handle=taskhandle.NullTaskHandle()): |
| 395 if resources is None: |
| 396 resources = self.project.get_python_files() |
| 397 if dest is None or not dest.is_folder(): |
| 398 raise exceptions.RefactoringError( |
| 399 'Move destination for modules should be packages.') |
| 400 return self._calculate_changes(dest, resources, task_handle) |
| 401 |
| 402 def _calculate_changes(self, dest, resources, task_handle): |
| 403 changes = ChangeSet('Moving module <%s>' % self.old_name) |
| 404 job_set = task_handle.create_jobset('Collecting changes', |
| 405 len(resources)) |
| 406 for module in resources: |
| 407 job_set.started_job(module.path) |
| 408 if module == self.source: |
| 409 self._change_moving_module(changes, dest) |
| 410 else: |
| 411 source = self._change_occurrences_in_module(dest, |
| 412 resource=module) |
| 413 if source is not None: |
| 414 changes.add_change(ChangeContents(module, source)) |
| 415 job_set.finished_job() |
| 416 if self.project == self.source.project: |
| 417 changes.add_change(MoveResource(self.source, dest.path)) |
| 418 return changes |
| 419 |
| 420 def _new_modname(self, dest): |
| 421 destname = libutils.modname(dest) |
| 422 if destname: |
| 423 return destname + '.' + self.old_name |
| 424 return self.old_name |
| 425 |
| 426 def _new_import(self, dest): |
| 427 return importutils.NormalImport([(self._new_modname(dest), None)]) |
| 428 |
| 429 def _change_moving_module(self, changes, dest): |
| 430 if not self.source.is_folder(): |
| 431 pymodule = self.project.get_pymodule(self.source) |
| 432 source = self.import_tools.relatives_to_absolutes(pymodule) |
| 433 pymodule = self.tools.new_pymodule(pymodule, source) |
| 434 source = self._change_occurrences_in_module(dest, pymodule) |
| 435 source = self.tools.new_source(pymodule, source) |
| 436 if source != self.source.read(): |
| 437 changes.add_change(ChangeContents(self.source, source)) |
| 438 |
| 439 def _change_occurrences_in_module(self, dest, pymodule=None, |
| 440 resource=None): |
| 441 if not self.tools.occurs_in_module(pymodule=pymodule, |
| 442 resource=resource): |
| 443 return |
| 444 if pymodule is None: |
| 445 pymodule = self.project.get_pymodule(resource) |
| 446 new_name = self._new_modname(dest) |
| 447 module_imports = importutils.get_module_imports(self.project, pymodule) |
| 448 changed = False |
| 449 |
| 450 source = None |
| 451 if libutils.modname(dest): |
| 452 changed = self._change_import_statements(dest, new_name, |
| 453 module_imports) |
| 454 if changed: |
| 455 source = module_imports.get_changed_source() |
| 456 source = self.tools.new_source(pymodule, source) |
| 457 pymodule = self.tools.new_pymodule(pymodule, source) |
| 458 |
| 459 new_import = self._new_import(dest) |
| 460 source = self.tools.rename_in_module( |
| 461 new_name, imports=True, pymodule=pymodule, |
| 462 resource=resource if not changed else None) |
| 463 should_import = self.tools.occurs_in_module( |
| 464 pymodule=pymodule, resource=resource, imports=False) |
| 465 pymodule = self.tools.new_pymodule(pymodule, source) |
| 466 source = self.tools.remove_old_imports(pymodule) |
| 467 if should_import: |
| 468 pymodule = self.tools.new_pymodule(pymodule, source) |
| 469 source = self.tools.add_imports(pymodule, [new_import]) |
| 470 source = self.tools.new_source(pymodule, source) |
| 471 if source is not None and source != pymodule.resource.read(): |
| 472 return source |
| 473 return None |
| 474 |
| 475 |
| 476 def _change_import_statements(self, dest, new_name, module_imports): |
| 477 moving_module = self.source |
| 478 parent_module = moving_module.parent |
| 479 |
| 480 changed = False |
| 481 for import_stmt in module_imports.imports: |
| 482 if not any(name_and_alias[0] == self.old_name |
| 483 for name_and_alias in |
| 484 import_stmt.import_info.names_and_aliases) and \ |
| 485 not any(name_and_alias[0] == libutils.modname(self.source) |
| 486 for name_and_alias in |
| 487 import_stmt.import_info.names_and_aliases): |
| 488 continue |
| 489 |
| 490 # Case 1: Look for normal imports of the moving module. |
| 491 if isinstance(import_stmt.import_info, importutils.NormalImport): |
| 492 continue |
| 493 |
| 494 # Case 2: The moving module is from-imported. |
| 495 changed = self._handle_moving_in_from_import_stmt( |
| 496 dest, import_stmt, module_imports, parent_module) or changed |
| 497 |
| 498 # Case 3: Names are imported from the moving module. |
| 499 context = importutils.importinfo.ImportContext(self.project, None) |
| 500 if not import_stmt.import_info.is_empty() and \ |
| 501 import_stmt.import_info.get_imported_resource(context) == \ |
| 502 moving_module: |
| 503 import_stmt.import_info = importutils.FromImport( |
| 504 new_name, import_stmt.import_info.level, |
| 505 import_stmt.import_info.names_and_aliases) |
| 506 changed = True |
| 507 |
| 508 return changed |
| 509 |
| 510 def _handle_moving_in_from_import_stmt(self, dest, import_stmt, |
| 511 module_imports, parent_module): |
| 512 changed = False |
| 513 context = importutils.importinfo.ImportContext(self.project, None) |
| 514 if import_stmt.import_info.get_imported_resource(context) == \ |
| 515 parent_module: |
| 516 imports = import_stmt.import_info.names_and_aliases |
| 517 new_imports = [] |
| 518 for name, alias in imports: |
| 519 # The moving module was imported. |
| 520 if name == self.old_name: |
| 521 changed = True |
| 522 new_import = importutils.FromImport( |
| 523 libutils.modname(dest), 0, |
| 524 [(self.old_name, alias)]) |
| 525 module_imports.add_import(new_import) |
| 526 else: |
| 527 new_imports.append((name, alias)) |
| 528 |
| 529 # Update the imports if the imported names were changed. |
| 530 if new_imports != imports: |
| 531 changed = True |
| 532 if new_imports: |
| 533 import_stmt.import_info = importutils.FromImport( |
| 534 import_stmt.import_info.module_name, |
| 535 import_stmt.import_info.level, |
| 536 new_imports) |
| 537 else: |
| 538 import_stmt.empty_import() |
| 539 return changed |
| 540 |
| 541 |
| 542 class _ChangeMoveOccurrencesHandle(object): |
| 543 |
| 544 def __init__(self, new_name): |
| 545 self.new_name = new_name |
| 546 self.occurred = False |
| 547 |
| 548 def occurred_inside_skip(self, change_collector, occurrence): |
| 549 pass |
| 550 |
| 551 def occurred_outside_skip(self, change_collector, occurrence): |
| 552 start, end = occurrence.get_primary_range() |
| 553 change_collector.add_change(start, end, self.new_name) |
| 554 self.occurred = True |
| 555 |
| 556 |
| 557 class _MoveTools(object): |
| 558 |
| 559 def __init__(self, project, source, pyname, old_name): |
| 560 self.project = project |
| 561 self.source = source |
| 562 self.old_pyname = pyname |
| 563 self.old_name = old_name |
| 564 self.import_tools = importutils.ImportTools(self.project) |
| 565 |
| 566 def remove_old_imports(self, pymodule): |
| 567 old_source = pymodule.source_code |
| 568 module_with_imports = self.import_tools.module_imports(pymodule) |
| 569 |
| 570 class CanSelect(object): |
| 571 changed = False |
| 572 old_name = self.old_name |
| 573 old_pyname = self.old_pyname |
| 574 |
| 575 def __call__(self, name): |
| 576 try: |
| 577 if name == self.old_name and \ |
| 578 pymodule[name].get_object() == \ |
| 579 self.old_pyname.get_object(): |
| 580 self.changed = True |
| 581 return False |
| 582 except exceptions.AttributeNotFoundError: |
| 583 pass |
| 584 return True |
| 585 can_select = CanSelect() |
| 586 module_with_imports.filter_names(can_select) |
| 587 new_source = module_with_imports.get_changed_source() |
| 588 if old_source != new_source: |
| 589 return new_source |
| 590 |
| 591 def rename_in_module(self, new_name, pymodule=None, |
| 592 imports=False, resource=None): |
| 593 occurrence_finder = self._create_finder(imports) |
| 594 source = rename.rename_in_module( |
| 595 occurrence_finder, new_name, replace_primary=True, |
| 596 pymodule=pymodule, resource=resource) |
| 597 return source |
| 598 |
| 599 def occurs_in_module(self, pymodule=None, resource=None, imports=True): |
| 600 finder = self._create_finder(imports) |
| 601 for occurrence in finder.find_occurrences(pymodule=pymodule, |
| 602 resource=resource): |
| 603 return True |
| 604 return False |
| 605 |
| 606 def _create_finder(self, imports): |
| 607 return occurrences.create_finder(self.project, self.old_name, |
| 608 self.old_pyname, imports=imports) |
| 609 |
| 610 def new_pymodule(self, pymodule, source): |
| 611 if source is not None: |
| 612 return libutils.get_string_module( |
| 613 self.project, source, pymodule.get_resource()) |
| 614 return pymodule |
| 615 |
| 616 def new_source(self, pymodule, source): |
| 617 if source is None: |
| 618 return pymodule.source_code |
| 619 return source |
| 620 |
| 621 def add_imports(self, pymodule, new_imports): |
| 622 return _add_imports_to_module(self.import_tools, pymodule, new_imports) |
| 623 |
| 624 |
| 625 def _add_imports_to_module(import_tools, pymodule, new_imports): |
| 626 module_with_imports = import_tools.module_imports(pymodule) |
| 627 for new_import in new_imports: |
| 628 module_with_imports.add_import(new_import) |
| 629 return module_with_imports.get_changed_source() |
| 630 |
| 631 |
| 632 def moving_code_with_imports(project, resource, source): |
| 633 import_tools = importutils.ImportTools(project) |
| 634 pymodule = libutils.get_string_module(project, source, resource) |
| 635 origin = project.get_pymodule(resource) |
| 636 |
| 637 imports = [] |
| 638 for stmt in import_tools.module_imports(origin).imports: |
| 639 imports.append(stmt.import_info) |
| 640 |
| 641 back_names = [] |
| 642 for name in origin: |
| 643 if name not in pymodule: |
| 644 back_names.append(name) |
| 645 imports.append(import_tools.get_from_import(resource, back_names)) |
| 646 |
| 647 source = _add_imports_to_module(import_tools, pymodule, imports) |
| 648 pymodule = libutils.get_string_module(project, source, resource) |
| 649 |
| 650 source = import_tools.relatives_to_absolutes(pymodule) |
| 651 pymodule = libutils.get_string_module(project, source, resource) |
| 652 source = import_tools.organize_imports(pymodule, selfs=False) |
| 653 pymodule = libutils.get_string_module(project, source, resource) |
| 654 |
| 655 # extracting imports after changes |
| 656 module_imports = import_tools.module_imports(pymodule) |
| 657 imports = [import_stmt.import_info |
| 658 for import_stmt in module_imports.imports] |
| 659 start = 1 |
| 660 if module_imports.imports: |
| 661 start = module_imports.imports[-1].end_line |
| 662 lines = codeanalyze.SourceLinesAdapter(source) |
| 663 while start < lines.length() and not lines.get_line(start).strip(): |
| 664 start += 1 |
| 665 moving = source[lines.get_line_start(start):] |
| 666 return moving, imports |
| 667 |
| 668 |
| 669 class ModuleSkipRenamerHandle(object): |
| 670 |
| 671 def occurred_outside_skip(self, change_collector, occurrence): |
| 672 pass |
| 673 |
| 674 def occurred_inside_skip(self, change_collector, occurrence): |
| 675 pass |
| 676 |
| 677 |
| 678 class ModuleSkipRenamer(object): |
| 679 """Rename occurrences in a module |
| 680 |
| 681 This class can be used when you want to treat a region in a file |
| 682 separately from other parts when renaming. |
| 683 |
| 684 """ |
| 685 |
| 686 def __init__(self, occurrence_finder, resource, handle=None, |
| 687 skip_start=0, skip_end=0, replacement=''): |
| 688 """Constructor |
| 689 |
| 690 if replacement is `None` the region is not changed. Otherwise |
| 691 it is replaced with `replacement`. |
| 692 |
| 693 """ |
| 694 self.occurrence_finder = occurrence_finder |
| 695 self.resource = resource |
| 696 self.skip_start = skip_start |
| 697 self.skip_end = skip_end |
| 698 self.replacement = replacement |
| 699 self.handle = handle |
| 700 if self.handle is None: |
| 701 self.handle = ModuleSkipRenamerHandle() |
| 702 |
| 703 def get_changed_module(self): |
| 704 source = self.resource.read() |
| 705 change_collector = codeanalyze.ChangeCollector(source) |
| 706 if self.replacement is not None: |
| 707 change_collector.add_change(self.skip_start, self.skip_end, |
| 708 self.replacement) |
| 709 for occurrence in self.occurrence_finder.find_occurrences( |
| 710 self.resource): |
| 711 start, end = occurrence.get_primary_range() |
| 712 if self.skip_start <= start < self.skip_end: |
| 713 self.handle.occurred_inside_skip(change_collector, occurrence) |
| 714 else: |
| 715 self.handle.occurred_outside_skip(change_collector, occurrence) |
| 716 result = change_collector.get_changed() |
| 717 if result is not None and result != source: |
| 718 return result |
OLD | NEW |