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/rename.py
1439 views
1
import warnings
2
3
from rope.base import exceptions, pyobjects, pynames, taskhandle, evaluate, worder, codeanalyze
4
from rope.base.change import ChangeSet, ChangeContents, MoveResource
5
from rope.refactor import occurrences, sourceutils
6
7
8
class Rename(object):
9
"""A class for performing rename refactoring
10
11
It can rename everything: classes, functions, modules, packages,
12
methods, variables and keyword arguments.
13
14
"""
15
16
def __init__(self, project, resource, offset=None):
17
"""If `offset` is None, the `resource` itself will be renamed"""
18
self.project = project
19
self.pycore = project.pycore
20
self.resource = resource
21
if offset is not None:
22
self.old_name = worder.get_name_at(self.resource, offset)
23
this_pymodule = self.pycore.resource_to_pyobject(self.resource)
24
self.old_instance, self.old_pyname = \
25
evaluate.eval_location2(this_pymodule, offset)
26
if self.old_pyname is None:
27
raise exceptions.RefactoringError(
28
'Rename refactoring should be performed'
29
' on resolvable python identifiers.')
30
else:
31
if not resource.is_folder() and resource.name == '__init__.py':
32
resource = resource.parent
33
dummy_pymodule = self.pycore.get_string_module('')
34
self.old_instance = None
35
self.old_pyname = pynames.ImportedModule(dummy_pymodule,
36
resource=resource)
37
if resource.is_folder():
38
self.old_name = resource.name
39
else:
40
self.old_name = resource.name[:-3]
41
42
def get_old_name(self):
43
return self.old_name
44
45
def get_changes(self, new_name, in_file=None, in_hierarchy=False,
46
unsure=None, docs=False, resources=None,
47
task_handle=taskhandle.NullTaskHandle()):
48
"""Get the changes needed for this refactoring
49
50
Parameters:
51
52
- `in_hierarchy`: when renaming a method this keyword forces
53
to rename all matching methods in the hierarchy
54
- `docs`: when `True` rename refactoring will rename
55
occurrences in comments and strings where the name is
56
visible. Setting it will make renames faster, too.
57
- `unsure`: decides what to do about unsure occurrences.
58
If `None`, they are ignored. Otherwise `unsure` is
59
called with an instance of `occurrence.Occurrence` as
60
parameter. If it returns `True`, the occurrence is
61
considered to be a match.
62
- `resources` can be a list of `rope.base.resources.File`\s to
63
apply this refactoring on. If `None`, the restructuring
64
will be applied to all python files.
65
- `in_file`: this argument has been deprecated; use
66
`resources` instead.
67
68
"""
69
if unsure in (True, False):
70
warnings.warn(
71
'unsure parameter should be a function that returns '
72
'True or False', DeprecationWarning, stacklevel=2)
73
def unsure_func(value=unsure):
74
return value
75
unsure = unsure_func
76
if in_file is not None:
77
warnings.warn(
78
'`in_file` argument has been deprecated; use `resources` '
79
'instead. ', DeprecationWarning, stacklevel=2)
80
if in_file:
81
resources = [self.resource]
82
if _is_local(self.old_pyname):
83
resources = [self.resource]
84
if resources is None:
85
resources = self.pycore.get_python_files()
86
changes = ChangeSet('Renaming <%s> to <%s>' %
87
(self.old_name, new_name))
88
finder = occurrences.create_finder(
89
self.pycore, self.old_name, self.old_pyname, unsure=unsure,
90
docs=docs, instance=self.old_instance,
91
in_hierarchy=in_hierarchy and self.is_method())
92
job_set = task_handle.create_jobset('Collecting Changes', len(resources))
93
for file_ in resources:
94
job_set.started_job(file_.path)
95
new_content = rename_in_module(finder, new_name, resource=file_)
96
if new_content is not None:
97
changes.add_change(ChangeContents(file_, new_content))
98
job_set.finished_job()
99
if self._is_renaming_a_module():
100
resource = self.old_pyname.get_object().get_resource()
101
if self._is_allowed_to_move(resources, resource):
102
self._rename_module(resource, new_name, changes)
103
return changes
104
105
def _is_allowed_to_move(self, resources, resource):
106
if resource.is_folder():
107
try:
108
return resource.get_child('__init__.py') in resources
109
except exceptions.ResourceNotFoundError:
110
return False
111
else:
112
return resource in resources
113
114
def _is_renaming_a_module(self):
115
if isinstance(self.old_pyname.get_object(), pyobjects.AbstractModule):
116
return True
117
return False
118
119
def is_method(self):
120
pyname = self.old_pyname
121
return isinstance(pyname, pynames.DefinedName) and \
122
isinstance(pyname.get_object(), pyobjects.PyFunction) and \
123
isinstance(pyname.get_object().parent, pyobjects.PyClass)
124
125
def _rename_module(self, resource, new_name, changes):
126
if not resource.is_folder():
127
new_name = new_name + '.py'
128
parent_path = resource.parent.path
129
if parent_path == '':
130
new_location = new_name
131
else:
132
new_location = parent_path + '/' + new_name
133
changes.add_change(MoveResource(resource, new_location))
134
135
136
class ChangeOccurrences(object):
137
"""A class for changing the occurrences of a name in a scope
138
139
This class replaces the occurrences of a name. Note that it only
140
changes the scope containing the offset passed to the constructor.
141
What's more it does not have any side-effects. That is for
142
example changing occurrences of a module does not rename the
143
module; it merely replaces the occurrences of that module in a
144
scope with the given expression. This class is useful for
145
performing many custom refactorings.
146
147
"""
148
149
def __init__(self, project, resource, offset):
150
self.pycore = project.pycore
151
self.resource = resource
152
self.offset = offset
153
self.old_name = worder.get_name_at(resource, offset)
154
self.pymodule = self.pycore.resource_to_pyobject(self.resource)
155
self.old_pyname = evaluate.eval_location(self.pymodule, offset)
156
157
def get_old_name(self):
158
word_finder = worder.Worder(self.resource.read())
159
return word_finder.get_primary_at(self.offset)
160
161
def _get_scope_offset(self):
162
lines = self.pymodule.lines
163
scope = self.pymodule.get_scope().\
164
get_inner_scope_for_line(lines.get_line_number(self.offset))
165
start = lines.get_line_start(scope.get_start())
166
end = lines.get_line_end(scope.get_end())
167
return start, end
168
169
def get_changes(self, new_name, only_calls=False, reads=True, writes=True):
170
changes = ChangeSet('Changing <%s> occurrences to <%s>' %
171
(self.old_name, new_name))
172
scope_start, scope_end = self._get_scope_offset()
173
finder = occurrences.create_finder(
174
self.pycore, self.old_name, self.old_pyname,
175
imports=False, only_calls=only_calls)
176
new_contents = rename_in_module(
177
finder, new_name, pymodule=self.pymodule, replace_primary=True,
178
region=(scope_start, scope_end), reads=reads, writes=writes)
179
if new_contents is not None:
180
changes.add_change(ChangeContents(self.resource, new_contents))
181
return changes
182
183
184
def rename_in_module(occurrences_finder, new_name, resource=None, pymodule=None,
185
replace_primary=False, region=None, reads=True, writes=True):
186
"""Returns the changed source or `None` if there is no changes"""
187
if resource is not None:
188
source_code = resource.read()
189
else:
190
source_code = pymodule.source_code
191
change_collector = codeanalyze.ChangeCollector(source_code)
192
for occurrence in occurrences_finder.find_occurrences(resource, pymodule):
193
if replace_primary and occurrence.is_a_fixed_primary():
194
continue
195
if replace_primary:
196
start, end = occurrence.get_primary_range()
197
else:
198
start, end = occurrence.get_word_range()
199
if (not reads and not occurrence.is_written()) or \
200
(not writes and occurrence.is_written()):
201
continue
202
if region is None or region[0] <= start < region[1]:
203
change_collector.add_change(start, end, new_name)
204
return change_collector.get_changed()
205
206
def _is_local(pyname):
207
module, lineno = pyname.get_definition_location()
208
if lineno is None:
209
return False
210
scope = module.get_scope().get_inner_scope_for_line(lineno)
211
if isinstance(pyname, pynames.DefinedName) and \
212
scope.get_kind() in ('Function', 'Class'):
213
scope = scope.parent
214
return scope.get_kind() == 'Function' and \
215
pyname in scope.get_names().values() and \
216
isinstance(pyname, pynames.AssignedName)
217
218