Commits

Anonymous committed 3e8e60f

Adding similarfinder.CheckingFinder

Comments (0)

Files changed (4)

docs/dev/issues.txt

 
 Now that we can match similar AST nodes, we can do our last move for
 making the examples in `a look into the future`_ section real.  I
-propose to add a `CheckingFinder`::
+propose to add `CheckingFinder` class.
 
-  
+Possible accessors can be nothing for checking `PyName`\s, ``.object``
+for checking `PyObject`\s (derived by calling ``pyname.get_object()``)
+and ``.type`` for checking the type (derived by calling ``pyname.
+get_object.get_type()``).
 
 
 A Look Into The Future
   # ``a = b`` to ``a.set(b)`` where the type of `a` is `type_pyclass`
   pattern = '${?a} = ${?b}'
   goal = '${?a}.set(${?b})'
-  checks = {'?a.object.type': type_pyclass}
+  checks = {'?a.type': type_pyclass}
 
   # ``a.set(b)`` to ``a = b`` where the type of `a` is `type_pyclass`
   pattern = '${?a}.set(${?b})'
   goal = '${?a} = ${?b}'
-  checks = {'?a.object.type': type_pyclass}
+  checks = {'?a.type': type_pyclass}
 
   # Can we use it for giving self assignment warnings?
   pattern = '${?a} = ${?b}'

docs/dev/workingon.txt

-Similar Finder
-==============
+Checking Similar Finder
+=======================
 
-- Handling `AssName`
-- AssName should not matche Name
+- What if evaluate return `None`?
+
+* When to match? not ``==``; see occurrences; ImportedName
+* Match when unsure; `occurrences._do_pynames_match()`
+* ``${?name} = ...`` and `?name` checks
 
 * Only scanning selected region for matches
 * Changing patchedast only to patch a branch?
 * One failure on windows!
 
 * Handling '<>' in patchedast
-
-* py3k: How to handle both branches?
-
-  * metadata=? and classes
-  * function annotations
-  * // and /
-  * decorated class
-  * ...
+* Consider filling python source
 
 
 Remaining Small Stories

rope/refactor/similarfinder.py

 import compiler.ast
 import re
 
-from rope.base import codeanalyze
+from rope.base import codeanalyze, evaluate
 from rope.refactor import patchedast, sourceutils
 
 
     """A class for finding similar expressions and statements"""
 
     def __init__(self, source, start=0, end=None):
+        ast = compiler.parse(source)
+        self._init_using_ast(ast, source, start, end)
+
+    def _init_using_ast(self, ast, source, start, end):
         self.start = start
         self.end = len(source)
         if end is not None:
             self.end = end
-        self.ast = patchedast.get_patched_ast(source)
+        if not hasattr(ast, 'sorted_children'):
+            self.ast = patchedast.patch_ast(ast, source)
 
     def get_matches(self, code):
         """Search for `code` in source and return a list of `Match`\es
         return template.substitute(mapping)
 
 
+class CheckingFinder(SimilarFinder):
+    """A `SimilarFinder` that can perform object and name checks
+
+    The constructor takes a `checks` dictionary.  This dictionary
+    contains checks to be performed.  As an example::
+
+      pattern: '${?a}.set(${?b})'
+      checks: {'?a.type': type_pyclass}
+
+      pattern: '${?c} = ${?C}())'
+      checks: {'C': c_pyname}
+
+    This means only match expressions as '?a' only if its type is
+    type_pyclass.  Each matched expression is a `PyName`.  By using
+    nothing, `.object` or `.type` you can specify a check.
+
+    """
+
+    def __init__(self, pymodule, checks, start=0, end=None):
+        super(CheckingFinder, self)._init_using_ast(
+            pymodule.get_ast(), pymodule.source_code, start, end)
+        self.pymodule = pymodule
+        self.checks = checks
+
+    def get_matches(self, code):
+        for match in SimilarFinder.get_matches(self, code):
+            matched = True
+            for check, expected in self.checks.items():
+                if self._evaluate_expression(match, check) != expected:
+                    matched = False
+                    break
+            if matched:
+                yield match
+
+    def _evaluate_expression(self, match, check):
+        scope = self.pymodule.get_scope().get_inner_scope_for_offset(
+            match.get_region()[0])
+        parts = check.split('.')
+        expression = match.get_ast(parts[0])
+        pyname = evaluate.get_statement_result(scope, expression)
+        if len(parts) == 1:
+            return pyname
+        if parts[1] == 'object':
+            return pyname.get_object()
+        if parts[1] == 'type':
+            return pyname.get_object().get_type()
+
+
 class _ASTMatcher(object):
 
     def __init__(self, body, pattern):

ropetest/refactor/similarfindertest.py

 import unittest
 
 from rope.refactor import similarfinder
+from ropetest import testutils
 
 
 class SimilarFinderTest(unittest.TestCase):
         self.assertEquals(0, len(result))
 
 
+class CheckingFinderTest(unittest.TestCase):
+
+    def setUp(self):
+        super(CheckingFinderTest, self).setUp()
+        self.project = testutils.sample_project()
+        self.pycore = self.project.get_pycore()
+        self.mod1 = self.pycore.create_module(self.project.root, 'mod1')
+
+    def tearDown(self):
+        testutils.remove_project(self.project)
+        super(CheckingFinderTest, self).tearDown()
+
+    def test_trivial_case(self):
+        self.mod1.write('')
+        pymodule = self.pycore.resource_to_pyobject(self.mod1)
+        finder = similarfinder.CheckingFinder(pymodule, {})
+        self.assertEquals([], list(finder.get_match_regions('10')))
+
+    def test_simple_finding(self):
+        self.mod1.write('class A(object):\n    pass\na = A()\n')
+        pymodule = self.pycore.resource_to_pyobject(self.mod1)
+        finder = similarfinder.CheckingFinder(pymodule, {})
+        result = list(finder.get_matches('${?anything} = ${?A}()'))
+        self.assertEquals(1, len(result))
+
+    def test_finding2(self):
+        self.mod1.write('class A(object):\n    pass\na = list()\n')
+        pymodule = self.pycore.resource_to_pyobject(self.mod1)
+        finder = similarfinder.CheckingFinder(
+            pymodule, {'?A': pymodule.get_attribute('A')})
+        result = list(finder.get_matches('${?anything} = ${?A}()'))
+        self.assertEquals(0, len(result))
+
+    def test_not_matching_unknowns_finding(self):
+        self.mod1.write('class A(object):\n    pass\na = unknown()\n')
+        pymodule = self.pycore.resource_to_pyobject(self.mod1)
+        finder = similarfinder.CheckingFinder(
+            pymodule, {'?A': pymodule.get_attribute('A')})
+        result = list(finder.get_matches('${?anything} = ${?A}()'))
+        self.assertEquals(0, len(result))
+
+    def test_finding_and_matching_pyobjects(self):
+        source = 'class A(object):\n    pass\nNewA = A\na = NewA()\n'
+        self.mod1.write(source)
+        pymodule = self.pycore.resource_to_pyobject(self.mod1)
+        finder = similarfinder.CheckingFinder(
+            pymodule, {'?A.object': pymodule.get_attribute('A').get_object()})
+        result = list(finder.get_matches('${?anything} = ${?A}()'))
+        self.assertEquals(1, len(result))
+        start = source.rindex('a =')
+        self.assertEquals((start, len(source) - 1), result[0].get_region())
+
+    def test_finding_and_matching_types(self):
+        source = 'class A(object):\n    def f(self):\n        pass\n' \
+                 'a = A()\nb = a.f()\n'
+        self.mod1.write(source)
+        pymodule = self.pycore.resource_to_pyobject(self.mod1)
+        finder = similarfinder.CheckingFinder(
+            pymodule, {'?inst.type': pymodule.get_attribute('A').get_object()})
+        result = list(finder.get_matches('${?anything} = ${?inst}.f()'))
+        self.assertEquals(1, len(result))
+        start = source.rindex('b')
+        self.assertEquals((start, len(source) - 1), result[0].get_region())
+
+
 class TemplateTest(unittest.TestCase):
 
     def test_simple_templates(self):
 def suite():
     result = unittest.TestSuite()
     result.addTests(unittest.makeSuite(SimilarFinderTest))
+    result.addTests(unittest.makeSuite(CheckingFinderTest))
     result.addTests(unittest.makeSuite(TemplateTest))
     return result