Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1985)

Unified Diff: tools/telemetry/third_party/rope/rope/refactor/similarfinder.py

Issue 1132103009: Example of refactoring using rope library. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Created 5 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: tools/telemetry/third_party/rope/rope/refactor/similarfinder.py
diff --git a/tools/telemetry/third_party/rope/rope/refactor/similarfinder.py b/tools/telemetry/third_party/rope/rope/refactor/similarfinder.py
new file mode 100644
index 0000000000000000000000000000000000000000..425f9ed955c27eab2741cfb496f3257747f5bbaf
--- /dev/null
+++ b/tools/telemetry/third_party/rope/rope/refactor/similarfinder.py
@@ -0,0 +1,370 @@
+"""This module can be used for finding similar code"""
+import re
+
+import rope.refactor.wildcards
+from rope.base import libutils
+from rope.base import codeanalyze, exceptions, ast, builtins
+from rope.refactor import (patchedast, wildcards)
+
+from rope.refactor.patchedast import MismatchedTokenError
+
+
+class BadNameInCheckError(exceptions.RefactoringError):
+ pass
+
+
+class SimilarFinder(object):
+ """`SimilarFinder` can be used to find similar pieces of code
+
+ See the notes in the `rope.refactor.restructure` module for more
+ info.
+
+ """
+
+ def __init__(self, pymodule, wildcards=None):
+ """Construct a SimilarFinder"""
+ self.source = pymodule.source_code
+ try:
+ self.raw_finder = RawSimilarFinder(
+ pymodule.source_code, pymodule.get_ast(), self._does_match)
+ except MismatchedTokenError:
+ print("in file %s" % pymodule.resource.path)
+ raise
+ self.pymodule = pymodule
+ if wildcards is None:
+ self.wildcards = {}
+ for wildcard in [rope.refactor.wildcards.
+ DefaultWildcard(pymodule.pycore.project)]:
+ self.wildcards[wildcard.get_name()] = wildcard
+ else:
+ self.wildcards = wildcards
+
+ def get_matches(self, code, args={}, start=0, end=None):
+ self.args = args
+ if end is None:
+ end = len(self.source)
+ skip_region = None
+ if 'skip' in args.get('', {}):
+ resource, region = args['']['skip']
+ if resource == self.pymodule.get_resource():
+ skip_region = region
+ return self.raw_finder.get_matches(code, start=start, end=end,
+ skip=skip_region)
+
+ def get_match_regions(self, *args, **kwds):
+ for match in self.get_matches(*args, **kwds):
+ yield match.get_region()
+
+ def _does_match(self, node, name):
+ arg = self.args.get(name, '')
+ kind = 'default'
+ if isinstance(arg, (tuple, list)):
+ kind = arg[0]
+ arg = arg[1]
+ suspect = wildcards.Suspect(self.pymodule, node, name)
+ return self.wildcards[kind].matches(suspect, arg)
+
+
+class RawSimilarFinder(object):
+ """A class for finding similar expressions and statements"""
+
+ def __init__(self, source, node=None, does_match=None):
+ if node is None:
+ node = ast.parse(source)
+ if does_match is None:
+ self.does_match = self._simple_does_match
+ else:
+ self.does_match = does_match
+ self._init_using_ast(node, source)
+
+ def _simple_does_match(self, node, name):
+ return isinstance(node, (ast.expr, ast.Name))
+
+ def _init_using_ast(self, node, source):
+ self.source = source
+ self._matched_asts = {}
+ if not hasattr(node, 'region'):
+ patchedast.patch_ast(node, source)
+ self.ast = node
+
+ def get_matches(self, code, start=0, end=None, skip=None):
+ """Search for `code` in source and return a list of `Match`\es
+
+ `code` can contain wildcards. ``${name}`` matches normal
+ names and ``${?name} can match any expression. You can use
+ `Match.get_ast()` for getting the node that has matched a
+ given pattern.
+
+ """
+ if end is None:
+ end = len(self.source)
+ for match in self._get_matched_asts(code):
+ match_start, match_end = match.get_region()
+ if start <= match_start and match_end <= end:
+ if skip is not None and (skip[0] < match_end and
+ skip[1] > match_start):
+ continue
+ yield match
+
+ def _get_matched_asts(self, code):
+ if code not in self._matched_asts:
+ wanted = self._create_pattern(code)
+ matches = _ASTMatcher(self.ast, wanted,
+ self.does_match).find_matches()
+ self._matched_asts[code] = matches
+ return self._matched_asts[code]
+
+ def _create_pattern(self, expression):
+ expression = self._replace_wildcards(expression)
+ node = ast.parse(expression)
+ # Getting Module.Stmt.nodes
+ nodes = node.body
+ if len(nodes) == 1 and isinstance(nodes[0], ast.Expr):
+ # Getting Discard.expr
+ wanted = nodes[0].value
+ else:
+ wanted = nodes
+ return wanted
+
+ def _replace_wildcards(self, expression):
+ ropevar = _RopeVariable()
+ template = CodeTemplate(expression)
+ mapping = {}
+ for name in template.get_names():
+ mapping[name] = ropevar.get_var(name)
+ return template.substitute(mapping)
+
+
+class _ASTMatcher(object):
+
+ def __init__(self, body, pattern, does_match):
+ """Searches the given pattern in the body AST.
+
+ body is an AST node and pattern can be either an AST node or
+ a list of ASTs nodes
+ """
+ self.body = body
+ self.pattern = pattern
+ self.matches = None
+ self.ropevar = _RopeVariable()
+ self.matches_callback = does_match
+
+ def find_matches(self):
+ if self.matches is None:
+ self.matches = []
+ ast.call_for_nodes(self.body, self._check_node, recursive=True)
+ return self.matches
+
+ def _check_node(self, node):
+ if isinstance(self.pattern, list):
+ self._check_statements(node)
+ else:
+ self._check_expression(node)
+
+ def _check_expression(self, node):
+ mapping = {}
+ if self._match_nodes(self.pattern, node, mapping):
+ self.matches.append(ExpressionMatch(node, mapping))
+
+ def _check_statements(self, node):
+ for child in ast.get_children(node):
+ if isinstance(child, (list, tuple)):
+ self.__check_stmt_list(child)
+
+ def __check_stmt_list(self, nodes):
+ for index in range(len(nodes)):
+ if len(nodes) - index >= len(self.pattern):
+ current_stmts = nodes[index:index + len(self.pattern)]
+ mapping = {}
+ if self._match_stmts(current_stmts, mapping):
+ self.matches.append(StatementMatch(current_stmts, mapping))
+
+ def _match_nodes(self, expected, node, mapping):
+ if isinstance(expected, ast.Name):
+ if self.ropevar.is_var(expected.id):
+ return self._match_wildcard(expected, node, mapping)
+ if not isinstance(expected, ast.AST):
+ return expected == node
+ if expected.__class__ != node.__class__:
+ return False
+
+ children1 = self._get_children(expected)
+ children2 = self._get_children(node)
+ if len(children1) != len(children2):
+ return False
+ for child1, child2 in zip(children1, children2):
+ if isinstance(child1, ast.AST):
+ if not self._match_nodes(child1, child2, mapping):
+ return False
+ elif isinstance(child1, (list, tuple)):
+ if not isinstance(child2, (list, tuple)) or \
+ len(child1) != len(child2):
+ return False
+ for c1, c2 in zip(child1, child2):
+ if not self._match_nodes(c1, c2, mapping):
+ return False
+ else:
+ if child1 != child2:
+ return False
+ return True
+
+ def _get_children(self, node):
+ """Return not `ast.expr_context` children of `node`"""
+ children = ast.get_children(node)
+ return [child for child in children
+ if not isinstance(child, ast.expr_context)]
+
+ def _match_stmts(self, current_stmts, mapping):
+ if len(current_stmts) != len(self.pattern):
+ return False
+ for stmt, expected in zip(current_stmts, self.pattern):
+ if not self._match_nodes(expected, stmt, mapping):
+ return False
+ return True
+
+ def _match_wildcard(self, node1, node2, mapping):
+ name = self.ropevar.get_base(node1.id)
+ if name not in mapping:
+ if self.matches_callback(node2, name):
+ mapping[name] = node2
+ return True
+ return False
+ else:
+ return self._match_nodes(mapping[name], node2, {})
+
+
+class Match(object):
+
+ def __init__(self, mapping):
+ self.mapping = mapping
+
+ def get_region(self):
+ """Returns match region"""
+
+ def get_ast(self, name):
+ """Return the ast node that has matched rope variables"""
+ return self.mapping.get(name, None)
+
+
+class ExpressionMatch(Match):
+
+ def __init__(self, ast, mapping):
+ super(ExpressionMatch, self).__init__(mapping)
+ self.ast = ast
+
+ def get_region(self):
+ return self.ast.region
+
+
+class StatementMatch(Match):
+
+ def __init__(self, ast_list, mapping):
+ super(StatementMatch, self).__init__(mapping)
+ self.ast_list = ast_list
+
+ def get_region(self):
+ return self.ast_list[0].region[0], self.ast_list[-1].region[1]
+
+
+class CodeTemplate(object):
+
+ def __init__(self, template):
+ self.template = template
+ self._find_names()
+
+ def _find_names(self):
+ self.names = {}
+ for match in CodeTemplate._get_pattern().finditer(self.template):
+ if 'name' in match.groupdict() and \
+ match.group('name') is not None:
+ start, end = match.span('name')
+ name = self.template[start + 2:end - 1]
+ if name not in self.names:
+ self.names[name] = []
+ self.names[name].append((start, end))
+
+ def get_names(self):
+ return self.names.keys()
+
+ def substitute(self, mapping):
+ collector = codeanalyze.ChangeCollector(self.template)
+ for name, occurrences in self.names.items():
+ for region in occurrences:
+ collector.add_change(region[0], region[1], mapping[name])
+ result = collector.get_changed()
+ if result is None:
+ return self.template
+ return result
+
+ _match_pattern = None
+
+ @classmethod
+ def _get_pattern(cls):
+ if cls._match_pattern is None:
+ pattern = codeanalyze.get_comment_pattern() + '|' + \
+ codeanalyze.get_string_pattern() + '|' + \
+ r'(?P<name>\$\{[^\s\$\}]*\})'
+ cls._match_pattern = re.compile(pattern)
+ return cls._match_pattern
+
+
+class _RopeVariable(object):
+ """Transform and identify rope inserted wildcards"""
+
+ _normal_prefix = '__rope__variable_normal_'
+ _any_prefix = '__rope__variable_any_'
+
+ def get_var(self, name):
+ if name.startswith('?'):
+ return self._get_any(name)
+ else:
+ return self._get_normal(name)
+
+ def is_var(self, name):
+ return self._is_normal(name) or self._is_var(name)
+
+ def get_base(self, name):
+ if self._is_normal(name):
+ return name[len(self._normal_prefix):]
+ if self._is_var(name):
+ return '?' + name[len(self._any_prefix):]
+
+ def _get_normal(self, name):
+ return self._normal_prefix + name
+
+ def _get_any(self, name):
+ return self._any_prefix + name[1:]
+
+ def _is_normal(self, name):
+ return name.startswith(self._normal_prefix)
+
+ def _is_var(self, name):
+ return name.startswith(self._any_prefix)
+
+
+def make_pattern(code, variables):
+ variables = set(variables)
+ collector = codeanalyze.ChangeCollector(code)
+
+ def does_match(node, name):
+ return isinstance(node, ast.Name) and node.id == name
+ finder = RawSimilarFinder(code, does_match=does_match)
+ for variable in variables:
+ for match in finder.get_matches('${%s}' % variable):
+ start, end = match.get_region()
+ collector.add_change(start, end, '${%s}' % variable)
+ result = collector.get_changed()
+ return result if result is not None else code
+
+
+def _pydefined_to_str(pydefined):
+ address = []
+ if isinstance(pydefined,
+ (builtins.BuiltinClass, builtins.BuiltinFunction)):
+ return '__builtins__.' + pydefined.get_name()
+ else:
+ while pydefined.parent is not None:
+ address.insert(0, pydefined.get_name())
+ pydefined = pydefined.parent
+ module_name = libutils.modname(pydefined.resource)
+ return '.'.join(module_name.split('.') + address)

Powered by Google App Engine
This is Rietveld 408576698