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/change_signature.py
1494 views
1
import copy
2
3
import rope.base.exceptions
4
from rope.base import pyobjects, taskhandle, evaluate, worder, codeanalyze, utils
5
from rope.base.change import ChangeContents, ChangeSet
6
from rope.refactor import occurrences, functionutils
7
8
9
class ChangeSignature(object):
10
11
def __init__(self, project, resource, offset):
12
self.pycore = project.pycore
13
self.resource = resource
14
self.offset = offset
15
self._set_name_and_pyname()
16
if self.pyname is None or self.pyname.get_object() is None or \
17
not isinstance(self.pyname.get_object(), pyobjects.PyFunction):
18
raise rope.base.exceptions.RefactoringError(
19
'Change method signature should be performed on functions')
20
21
def _set_name_and_pyname(self):
22
self.name = worder.get_name_at(self.resource, self.offset)
23
this_pymodule = self.pycore.resource_to_pyobject(self.resource)
24
self.primary, self.pyname = evaluate.eval_location2(
25
this_pymodule, self.offset)
26
if self.pyname is None:
27
return
28
pyobject = self.pyname.get_object()
29
if isinstance(pyobject, pyobjects.PyClass) and \
30
'__init__' in pyobject:
31
self.pyname = pyobject['__init__']
32
self.name = '__init__'
33
pyobject = self.pyname.get_object()
34
self.others = None
35
if self.name == '__init__' and \
36
isinstance(pyobject, pyobjects.PyFunction) and \
37
isinstance(pyobject.parent, pyobjects.PyClass):
38
pyclass = pyobject.parent
39
self.others = (pyclass.get_name(),
40
pyclass.parent[pyclass.get_name()])
41
42
def _change_calls(self, call_changer, in_hierarchy=None, resources=None,
43
handle=taskhandle.NullTaskHandle()):
44
if resources is None:
45
resources = self.pycore.get_python_files()
46
changes = ChangeSet('Changing signature of <%s>' % self.name)
47
job_set = handle.create_jobset('Collecting Changes', len(resources))
48
finder = occurrences.create_finder(
49
self.pycore, self.name, self.pyname, instance=self.primary,
50
in_hierarchy=in_hierarchy and self.is_method())
51
if self.others:
52
name, pyname = self.others
53
constructor_finder = occurrences.create_finder(
54
self.pycore, name, pyname, only_calls=True)
55
finder = _MultipleFinders([finder, constructor_finder])
56
for file in resources:
57
job_set.started_job(file.path)
58
change_calls = _ChangeCallsInModule(
59
self.pycore, finder, file, call_changer)
60
changed_file = change_calls.get_changed_module()
61
if changed_file is not None:
62
changes.add_change(ChangeContents(file, changed_file))
63
job_set.finished_job()
64
return changes
65
66
def get_args(self):
67
"""Get function arguments.
68
69
Return a list of ``(name, default)`` tuples for all but star
70
and double star arguments. For arguments that don't have a
71
default, `None` will be used.
72
"""
73
return self._definfo().args_with_defaults
74
75
def is_method(self):
76
pyfunction = self.pyname.get_object()
77
return isinstance(pyfunction.parent, pyobjects.PyClass)
78
79
@utils.deprecated('Use `ChangeSignature.get_args()` instead')
80
def get_definition_info(self):
81
return self._definfo()
82
83
def _definfo(self):
84
return functionutils.DefinitionInfo.read(self.pyname.get_object())
85
86
@utils.deprecated()
87
def normalize(self):
88
changer = _FunctionChangers(
89
self.pyname.get_object(), self.get_definition_info(),
90
[ArgumentNormalizer()])
91
return self._change_calls(changer)
92
93
@utils.deprecated()
94
def remove(self, index):
95
changer = _FunctionChangers(
96
self.pyname.get_object(), self.get_definition_info(),
97
[ArgumentRemover(index)])
98
return self._change_calls(changer)
99
100
@utils.deprecated()
101
def add(self, index, name, default=None, value=None):
102
changer = _FunctionChangers(
103
self.pyname.get_object(), self.get_definition_info(),
104
[ArgumentAdder(index, name, default, value)])
105
return self._change_calls(changer)
106
107
@utils.deprecated()
108
def inline_default(self, index):
109
changer = _FunctionChangers(
110
self.pyname.get_object(), self.get_definition_info(),
111
[ArgumentDefaultInliner(index)])
112
return self._change_calls(changer)
113
114
@utils.deprecated()
115
def reorder(self, new_ordering):
116
changer = _FunctionChangers(
117
self.pyname.get_object(), self.get_definition_info(),
118
[ArgumentReorderer(new_ordering)])
119
return self._change_calls(changer)
120
121
def get_changes(self, changers, in_hierarchy=False, resources=None,
122
task_handle=taskhandle.NullTaskHandle()):
123
"""Get changes caused by this refactoring
124
125
`changers` is a list of `_ArgumentChanger`\s. If `in_hierarchy`
126
is `True` the changers are applyed to all matching methods in
127
the class hierarchy.
128
`resources` can be a list of `rope.base.resource.File`\s that
129
should be searched for occurrences; if `None` all python files
130
in the project are searched.
131
132
"""
133
function_changer = _FunctionChangers(self.pyname.get_object(),
134
self._definfo(), changers)
135
return self._change_calls(function_changer, in_hierarchy,
136
resources, task_handle)
137
138
139
class _FunctionChangers(object):
140
141
def __init__(self, pyfunction, definition_info, changers=None):
142
self.pyfunction = pyfunction
143
self.definition_info = definition_info
144
self.changers = changers
145
self.changed_definition_infos = self._get_changed_definition_infos()
146
147
def _get_changed_definition_infos(self):
148
result = []
149
definition_info = self.definition_info
150
result.append(definition_info)
151
for changer in self.changers:
152
definition_info = copy.deepcopy(definition_info)
153
changer.change_definition_info(definition_info)
154
result.append(definition_info)
155
return result
156
157
def change_definition(self, call):
158
return self.changed_definition_infos[-1].to_string()
159
160
def change_call(self, primary, pyname, call):
161
call_info = functionutils.CallInfo.read(
162
primary, pyname, self.definition_info, call)
163
mapping = functionutils.ArgumentMapping(self.definition_info, call_info)
164
165
for definition_info, changer in zip(self.changed_definition_infos, self.changers):
166
changer.change_argument_mapping(definition_info, mapping)
167
168
return mapping.to_call_info(self.changed_definition_infos[-1]).to_string()
169
170
171
class _ArgumentChanger(object):
172
173
def change_definition_info(self, definition_info):
174
pass
175
176
def change_argument_mapping(self, definition_info, argument_mapping):
177
pass
178
179
180
class ArgumentNormalizer(_ArgumentChanger):
181
pass
182
183
184
class ArgumentRemover(_ArgumentChanger):
185
186
def __init__(self, index):
187
self.index = index
188
189
def change_definition_info(self, call_info):
190
if self.index < len(call_info.args_with_defaults):
191
del call_info.args_with_defaults[self.index]
192
elif self.index == len(call_info.args_with_defaults) and \
193
call_info.args_arg is not None:
194
call_info.args_arg = None
195
elif (self.index == len(call_info.args_with_defaults) and
196
call_info.args_arg is None and call_info.keywords_arg is not None) or \
197
(self.index == len(call_info.args_with_defaults) + 1 and
198
call_info.args_arg is not None and call_info.keywords_arg is not None):
199
call_info.keywords_arg = None
200
201
def change_argument_mapping(self, definition_info, mapping):
202
if self.index < len(definition_info.args_with_defaults):
203
name = definition_info.args_with_defaults[0]
204
if name in mapping.param_dict:
205
del mapping.param_dict[name]
206
207
208
class ArgumentAdder(_ArgumentChanger):
209
210
def __init__(self, index, name, default=None, value=None):
211
self.index = index
212
self.name = name
213
self.default = default
214
self.value = value
215
216
def change_definition_info(self, definition_info):
217
for pair in definition_info.args_with_defaults:
218
if pair[0] == self.name:
219
raise rope.base.exceptions.RefactoringError(
220
'Adding duplicate parameter: <%s>.' % self.name)
221
definition_info.args_with_defaults.insert(self.index,
222
(self.name, self.default))
223
224
def change_argument_mapping(self, definition_info, mapping):
225
if self.value is not None:
226
mapping.param_dict[self.name] = self.value
227
228
229
class ArgumentDefaultInliner(_ArgumentChanger):
230
231
def __init__(self, index):
232
self.index = index
233
self.remove = False
234
235
def change_definition_info(self, definition_info):
236
if self.remove:
237
definition_info.args_with_defaults[self.index] = \
238
(definition_info.args_with_defaults[self.index][0], None)
239
240
def change_argument_mapping(self, definition_info, mapping):
241
default = definition_info.args_with_defaults[self.index][1]
242
name = definition_info.args_with_defaults[self.index][0]
243
if default is not None and name not in mapping.param_dict:
244
mapping.param_dict[name] = default
245
246
247
class ArgumentReorderer(_ArgumentChanger):
248
249
def __init__(self, new_order, autodef=None):
250
"""Construct an `ArgumentReorderer`
251
252
Note that the `new_order` is a list containing the new
253
position of parameters; not the position each parameter
254
is going to be moved to. (changed in ``0.5m4``)
255
256
For example changing ``f(a, b, c)`` to ``f(c, a, b)``
257
requires passing ``[2, 0, 1]`` and *not* ``[1, 2, 0]``.
258
259
The `autodef` (automatic default) argument, forces rope to use
260
it as a default if a default is needed after the change. That
261
happens when an argument without default is moved after
262
another that has a default value. Note that `autodef` should
263
be a string or `None`; the latter disables adding automatic
264
default.
265
266
"""
267
self.new_order = new_order
268
self.autodef = autodef
269
270
def change_definition_info(self, definition_info):
271
new_args = list(definition_info.args_with_defaults)
272
for new_index, index in enumerate(self.new_order):
273
new_args[new_index] = definition_info.args_with_defaults[index]
274
seen_default = False
275
for index, (arg, default) in enumerate(list(new_args)):
276
if default is not None:
277
seen_default = True
278
if seen_default and default is None and self.autodef is not None:
279
new_args[index] = (arg, self.autodef)
280
definition_info.args_with_defaults = new_args
281
282
283
class _ChangeCallsInModule(object):
284
285
def __init__(self, pycore, occurrence_finder, resource, call_changer):
286
self.pycore = pycore
287
self.occurrence_finder = occurrence_finder
288
self.resource = resource
289
self.call_changer = call_changer
290
291
def get_changed_module(self):
292
word_finder = worder.Worder(self.source)
293
change_collector = codeanalyze.ChangeCollector(self.source)
294
for occurrence in self.occurrence_finder.find_occurrences(self.resource):
295
if not occurrence.is_called() and not occurrence.is_defined():
296
continue
297
start, end = occurrence.get_primary_range()
298
begin_parens, end_parens = word_finder.get_word_parens_range(end - 1)
299
if occurrence.is_called():
300
primary, pyname = occurrence.get_primary_and_pyname()
301
changed_call = self.call_changer.change_call(
302
primary, pyname, self.source[start:end_parens])
303
else:
304
changed_call = self.call_changer.change_definition(
305
self.source[start:end_parens])
306
if changed_call is not None:
307
change_collector.add_change(start, end_parens, changed_call)
308
return change_collector.get_changed()
309
310
@property
311
@utils.saveit
312
def pymodule(self):
313
return self.pycore.resource_to_pyobject(self.resource)
314
315
@property
316
@utils.saveit
317
def source(self):
318
if self.resource is not None:
319
return self.resource.read()
320
else:
321
return self.pymodule.source_code
322
323
@property
324
@utils.saveit
325
def lines(self):
326
return self.pymodule.lines
327
328
329
class _MultipleFinders(object):
330
331
def __init__(self, finders):
332
self.finders = finders
333
334
def find_occurrences(self, resource=None, pymodule=None):
335
all_occurrences = []
336
for finder in self.finders:
337
all_occurrences.extend(finder.find_occurrences(resource, pymodule))
338
all_occurrences.sort(self._cmp_occurrences)
339
return all_occurrences
340
341
def _cmp_occurrences(self, o1, o2):
342
return cmp(o1.get_primary_range(), o2.get_primary_range())
343
344