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