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/similarfinder.py
1494 views
1
"""This module can be used for finding similar code"""
2
import re
3
4
import rope.refactor.wildcards
5
from rope.base import codeanalyze, evaluate, exceptions, ast, builtins
6
from rope.refactor import (patchedast, sourceutils, occurrences,
7
wildcards, importutils)
8
9
10
class BadNameInCheckError(exceptions.RefactoringError):
11
pass
12
13
14
class SimilarFinder(object):
15
"""`SimilarFinder` can be used to find similar pieces of code
16
17
See the notes in the `rope.refactor.restructure` module for more
18
info.
19
20
"""
21
22
def __init__(self, pymodule, wildcards=None):
23
"""Construct a SimilarFinder"""
24
self.source = pymodule.source_code
25
self.raw_finder = RawSimilarFinder(
26
pymodule.source_code, pymodule.get_ast(), self._does_match)
27
self.pymodule = pymodule
28
if wildcards is None:
29
self.wildcards = {}
30
for wildcard in [rope.refactor.wildcards.
31
DefaultWildcard(pymodule.pycore.project)]:
32
self.wildcards[wildcard.get_name()] = wildcard
33
else:
34
self.wildcards = wildcards
35
36
def get_matches(self, code, args={}, start=0, end=None):
37
self.args = args
38
if end is None:
39
end = len(self.source)
40
skip_region = None
41
if 'skip' in args.get('', {}):
42
resource, region = args['']['skip']
43
if resource == self.pymodule.get_resource():
44
skip_region = region
45
return self.raw_finder.get_matches(code, start=start, end=end,
46
skip=skip_region)
47
48
def get_match_regions(self, *args, **kwds):
49
for match in self.get_matches(*args, **kwds):
50
yield match.get_region()
51
52
def _does_match(self, node, name):
53
arg = self.args.get(name, '')
54
kind = 'default'
55
if isinstance(arg, (tuple, list)):
56
kind = arg[0]
57
arg = arg[1]
58
suspect = wildcards.Suspect(self.pymodule, node, name)
59
return self.wildcards[kind].matches(suspect, arg)
60
61
62
class RawSimilarFinder(object):
63
"""A class for finding similar expressions and statements"""
64
65
def __init__(self, source, node=None, does_match=None):
66
if node is None:
67
node = ast.parse(source)
68
if does_match is None:
69
self.does_match = self._simple_does_match
70
else:
71
self.does_match = does_match
72
self._init_using_ast(node, source)
73
74
def _simple_does_match(self, node, name):
75
return isinstance(node, (ast.expr, ast.Name))
76
77
def _init_using_ast(self, node, source):
78
self.source = source
79
self._matched_asts = {}
80
if not hasattr(node, 'region'):
81
patchedast.patch_ast(node, source)
82
self.ast = node
83
84
def get_matches(self, code, start=0, end=None, skip=None):
85
"""Search for `code` in source and return a list of `Match`\es
86
87
`code` can contain wildcards. ``${name}`` matches normal
88
names and ``${?name} can match any expression. You can use
89
`Match.get_ast()` for getting the node that has matched a
90
given pattern.
91
92
"""
93
if end is None:
94
end = len(self.source)
95
for match in self._get_matched_asts(code):
96
match_start, match_end = match.get_region()
97
if start <= match_start and match_end <= end:
98
if skip is not None and (skip[0] < match_end and
99
skip[1] > match_start):
100
continue
101
yield match
102
103
def _get_matched_asts(self, code):
104
if code not in self._matched_asts:
105
wanted = self._create_pattern(code)
106
matches = _ASTMatcher(self.ast, wanted,
107
self.does_match).find_matches()
108
self._matched_asts[code] = matches
109
return self._matched_asts[code]
110
111
def _create_pattern(self, expression):
112
expression = self._replace_wildcards(expression)
113
node = ast.parse(expression)
114
# Getting Module.Stmt.nodes
115
nodes = node.body
116
if len(nodes) == 1 and isinstance(nodes[0], ast.Expr):
117
# Getting Discard.expr
118
wanted = nodes[0].value
119
else:
120
wanted = nodes
121
return wanted
122
123
def _replace_wildcards(self, expression):
124
ropevar = _RopeVariable()
125
template = CodeTemplate(expression)
126
mapping = {}
127
for name in template.get_names():
128
mapping[name] = ropevar.get_var(name)
129
return template.substitute(mapping)
130
131
132
class _ASTMatcher(object):
133
134
def __init__(self, body, pattern, does_match):
135
"""Searches the given pattern in the body AST.
136
137
body is an AST node and pattern can be either an AST node or
138
a list of ASTs nodes
139
"""
140
self.body = body
141
self.pattern = pattern
142
self.matches = None
143
self.ropevar = _RopeVariable()
144
self.matches_callback = does_match
145
146
def find_matches(self):
147
if self.matches is None:
148
self.matches = []
149
ast.call_for_nodes(self.body, self._check_node, recursive=True)
150
return self.matches
151
152
def _check_node(self, node):
153
if isinstance(self.pattern, list):
154
self._check_statements(node)
155
else:
156
self._check_expression(node)
157
158
def _check_expression(self, node):
159
mapping = {}
160
if self._match_nodes(self.pattern, node, mapping):
161
self.matches.append(ExpressionMatch(node, mapping))
162
163
def _check_statements(self, node):
164
for child in ast.get_children(node):
165
if isinstance(child, (list, tuple)):
166
self.__check_stmt_list(child)
167
168
def __check_stmt_list(self, nodes):
169
for index in range(len(nodes)):
170
if len(nodes) - index >= len(self.pattern):
171
current_stmts = nodes[index:index + len(self.pattern)]
172
mapping = {}
173
if self._match_stmts(current_stmts, mapping):
174
self.matches.append(StatementMatch(current_stmts, mapping))
175
176
def _match_nodes(self, expected, node, mapping):
177
if isinstance(expected, ast.Name):
178
if self.ropevar.is_var(expected.id):
179
return self._match_wildcard(expected, node, mapping)
180
if not isinstance(expected, ast.AST):
181
return expected == node
182
if expected.__class__ != node.__class__:
183
return False
184
185
children1 = self._get_children(expected)
186
children2 = self._get_children(node)
187
if len(children1) != len(children2):
188
return False
189
for child1, child2 in zip(children1, children2):
190
if isinstance(child1, ast.AST):
191
if not self._match_nodes(child1, child2, mapping):
192
return False
193
elif isinstance(child1, (list, tuple)):
194
if not isinstance(child2, (list, tuple)) or \
195
len(child1) != len(child2):
196
return False
197
for c1, c2 in zip(child1, child2):
198
if not self._match_nodes(c1, c2, mapping):
199
return False
200
else:
201
if child1 != child2:
202
return False
203
return True
204
205
def _get_children(self, node):
206
"""Return not `ast.expr_context` children of `node`"""
207
children = ast.get_children(node)
208
return [child for child in children
209
if not isinstance(child, ast.expr_context)]
210
211
def _match_stmts(self, current_stmts, mapping):
212
if len(current_stmts) != len(self.pattern):
213
return False
214
for stmt, expected in zip(current_stmts, self.pattern):
215
if not self._match_nodes(expected, stmt, mapping):
216
return False
217
return True
218
219
def _match_wildcard(self, node1, node2, mapping):
220
name = self.ropevar.get_base(node1.id)
221
if name not in mapping:
222
if self.matches_callback(node2, name):
223
mapping[name] = node2
224
return True
225
return False
226
else:
227
return self._match_nodes(mapping[name], node2, {})
228
229
230
class Match(object):
231
232
def __init__(self, mapping):
233
self.mapping = mapping
234
235
def get_region(self):
236
"""Returns match region"""
237
238
def get_ast(self, name):
239
"""Return the ast node that has matched rope variables"""
240
return self.mapping.get(name, None)
241
242
243
class ExpressionMatch(Match):
244
245
def __init__(self, ast, mapping):
246
super(ExpressionMatch, self).__init__(mapping)
247
self.ast = ast
248
249
def get_region(self):
250
return self.ast.region
251
252
253
class StatementMatch(Match):
254
255
def __init__(self, ast_list, mapping):
256
super(StatementMatch, self).__init__(mapping)
257
self.ast_list = ast_list
258
259
def get_region(self):
260
return self.ast_list[0].region[0], self.ast_list[-1].region[1]
261
262
263
class CodeTemplate(object):
264
265
def __init__(self, template):
266
self.template = template
267
self._find_names()
268
269
def _find_names(self):
270
self.names = {}
271
for match in CodeTemplate._get_pattern().finditer(self.template):
272
if 'name' in match.groupdict() and \
273
match.group('name') is not None:
274
start, end = match.span('name')
275
name = self.template[start + 2:end - 1]
276
if name not in self.names:
277
self.names[name] = []
278
self.names[name].append((start, end))
279
280
def get_names(self):
281
return self.names.keys()
282
283
def substitute(self, mapping):
284
collector = codeanalyze.ChangeCollector(self.template)
285
for name, occurrences in self.names.items():
286
for region in occurrences:
287
collector.add_change(region[0], region[1], mapping[name])
288
result = collector.get_changed()
289
if result is None:
290
return self.template
291
return result
292
293
_match_pattern = None
294
295
@classmethod
296
def _get_pattern(cls):
297
if cls._match_pattern is None:
298
pattern = codeanalyze.get_comment_pattern() + '|' + \
299
codeanalyze.get_string_pattern() + '|' + \
300
r'(?P<name>\$\{[^\s\$\}]*\})'
301
cls._match_pattern = re.compile(pattern)
302
return cls._match_pattern
303
304
305
class _RopeVariable(object):
306
"""Transform and identify rope inserted wildcards"""
307
308
_normal_prefix = '__rope__variable_normal_'
309
_any_prefix = '__rope__variable_any_'
310
311
def get_var(self, name):
312
if name.startswith('?'):
313
return self._get_any(name)
314
else:
315
return self._get_normal(name)
316
317
def is_var(self, name):
318
return self._is_normal(name) or self._is_var(name)
319
320
def get_base(self, name):
321
if self._is_normal(name):
322
return name[len(self._normal_prefix):]
323
if self._is_var(name):
324
return '?' + name[len(self._any_prefix):]
325
326
def _get_normal(self, name):
327
return self._normal_prefix + name
328
329
def _get_any(self, name):
330
return self._any_prefix + name[1:]
331
332
def _is_normal(self, name):
333
return name.startswith(self._normal_prefix)
334
335
def _is_var(self, name):
336
return name.startswith(self._any_prefix)
337
338
339
def make_pattern(code, variables):
340
variables = set(variables)
341
collector = codeanalyze.ChangeCollector(code)
342
def does_match(node, name):
343
return isinstance(node, ast.Name) and node.id == name
344
finder = RawSimilarFinder(code, does_match=does_match)
345
for variable in variables:
346
for match in finder.get_matches('${%s}' % variable):
347
start, end = match.get_region()
348
collector.add_change(start, end, '${%s}' % variable)
349
result = collector.get_changed()
350
return result if result is not None else code
351
352
353
def _pydefined_to_str(pydefined):
354
address = []
355
if isinstance(pydefined, (builtins.BuiltinClass, builtins.BuiltinFunction)):
356
return '__builtins__.' + pydefined.get_name()
357
else:
358
while pydefined.parent is not None:
359
address.insert(0, pydefined.get_name())
360
pydefined = pydefined.parent
361
module_name = pydefined.pycore.modname(pydefined.resource)
362
return '.'.join(module_name.split('.') + address)
363
364