[Checkins] SVN: zope.fixers/trunk/zope/fixers/ Big refactor, now supports all usecases I could come up with.

Lennart Regebro regebro at gmail.com
Mon Apr 6 13:16:32 EDT 2009


Log message for revision 98943:
  Big refactor, now supports all usecases I could come up with.
  

Changed:
  U   zope.fixers/trunk/zope/fixers/fix_implements.py
  U   zope.fixers/trunk/zope/fixers/tests.py

-=-
Modified: zope.fixers/trunk/zope/fixers/fix_implements.py
===================================================================
--- zope.fixers/trunk/zope/fixers/fix_implements.py	2009-04-06 16:44:16 UTC (rev 98942)
+++ zope.fixers/trunk/zope/fixers/fix_implements.py	2009-04-06 17:16:32 UTC (rev 98943)
@@ -26,38 +26,52 @@
 
 class FixImplements(BaseFix):
 
-    NAMED_IMPORT_PATTERN = """
+    IMPORT_PATTERN = """
     import_from< 'from' dotted_name< 'zope' '.' 'interface' > 'import' import_as_names< any* (name='implements') any* > >
     |
     import_from< 'from' dotted_name< 'zope' '.' 'interface' > 'import' name='implements' any* >
+    |
+    import_from< 'from' dotted_name< 'zope' > 'import' name='interface' any* >
+    |
+    import_from< 'from' dotted_name< 'zope' '.' 'interface' > 'import' import_as_name< name='implements' 'as' rename=(any) any*> >
+    |
+    import_from< 'from' dotted_name< 'zope' > 'import' import_as_name< name='interface' 'as' rename=(any) any*> >
+    |
+    import_from< 'from' 'zope' 'import' import_as_name< 'interface' 'as' interface_rename=(any) > >
     """
     
-    RENAMED_IMPORT_PATTERN = """
-    import_from< 'from' dotted_name< 'zope' '.' 'interface' > 'import' import_as_name< name='implements' 'as' rename='renamed' any*> >
-    """
-    
     CLASS_PATTERN = """
-    classdef< 'class' any* ':' suite< any* simple_stmt< power< statement='%s' trailer < '(' interface=any ')' > any* > any* > any* > >
+    classdef< 'class' any* ':' suite< any* simple_stmt< power< statement=(%s) trailer < '(' interface=any ')' > any* > any* > any* > >
     """
 
     IMPLEMENTS_PATTERN = """
-    simple_stmt< power< old_statement='%s' trailer < '(' any* ')' > > any* >
+    simple_stmt< power< old_statement=(%s) trailer < '(' any* ')' > > any* >
     """
-    #classdef< 'class' 'Foo' ':' suite<  simple_stmt< power< 'implements' trailer< '(' 'IFoo' ')' > > '\n' > '' > >
+
+    TEST_PATTERN = """
+    import_name< 'import' dotted_name< interface_full=('zope' '.' 'interface') > >
+    """
     
     fixups = []
     
+    def should_skip(self, node):
+        # TODO Could possibly be faster is we used a pattern. Worth trying.
+        module = str(node)
+        return not ('zope' in module and 'interface' in module)
+
     def compile_pattern(self):
         """Compiles self.PATTERN into self.pattern.
 
         Subclass may override if it doesn't want to use
         self.{pattern,PATTERN} in .match().
         """
-        self.named_import_pattern = PatternCompiler().compile_pattern(self.NAMED_IMPORT_PATTERN)
-        self.renamed_import_pattern = PatternCompiler().compile_pattern(self.RENAMED_IMPORT_PATTERN)
+        self.named_import_pattern = PatternCompiler().compile_pattern(self.IMPORT_PATTERN)
+        self.test_pattern = PatternCompiler().compile_pattern(self.TEST_PATTERN)
             
     def start_tree(self, tree, filename):
-        self.matches = ['implements']
+        self.matches = ["'implements'",
+                        "'interface' trailer< '.' 'implements' >",
+                        ]
         super(FixImplements, self).start_tree(tree, filename)
         
     def match(self, node):
@@ -65,8 +79,9 @@
         results = {"node": node}
         if self.named_import_pattern.match(node, results):
             return results
-        if self.renamed_import_pattern.match(node, results):
+        if self.test_pattern.match(node, results):
             return results
+
         for name in self.matches:
             pattern = PatternCompiler().compile_pattern(self.CLASS_PATTERN % name)
             if pattern.match(node, results):
@@ -80,22 +95,45 @@
             # This matched an import statement. Fix that up:
             name = results["name"]
             name.replace(Name("implementor", prefix=name.get_prefix()))
-            if 'rename' in results:
-                # The import statement use import as
-                self.matches.append(results['rename'].value)
+        if 'rename' in results:
+            # The import statement use import as
+            self.matches.append("'%s'" % results['rename'].value)
+        if 'interface_rename' in results:
+            self.matches.append("'%s' trailer< '.' 'implements' > " % results['interface_rename'].value)
+        if 'interface_full' in results:
+            self.matches.append("'zope' trailer< '.' 'interface' > trailer< '.' 'implements' >")
         if 'statement' in results:
-            # This matched a class that has an impements(IFoo) statement.
-            # Stick a class decorator first.
-            statement = results['statement'].value
+            # This matched a class that has an implements(IFoo) statement.
+            # We must convert that statement to a class decorator
+            # and put it before the class definition.
+            
+            statement = results['statement']
             interface = results['interface'].value
-            if statement == 'implements':
-                statement = 'implementor'
+            
+            if not isinstance(statement, list):
+                statement = [statement]
+            # Make a copy for insertion before the class:
+            statement = [x.clone() for x in statement]
+            # Get rid of leading whitespace:
+            statement[0].prefix = ''
+            # Rename implements to implementor:
+            if statement[-1].children:
+                implements = statement[-1].children[-1]
             else:
-                statement = results['statement'].value
-            decorator = Node(syms.decorator, [Leaf(50, '@'), Leaf(1, statement), 
+                implements = statement[-1]
+            if implements.value == 'implements':
+                implements.value = 'implementor'
+            
+            # Create the decorator:
+            decorator = Node(syms.decorator, [Leaf(50, '@'), ] + statement + [ 
                                               Leaf(7, '('), Leaf(1, interface), 
                                               Leaf(8, ')'), Leaf(4, '\n')])
+            # And stick it in before the class defintion:
+            prefix = node.get_prefix()
+            node.set_prefix('')
             node.insert_child(0, decorator)
+            node.set_prefix(prefix)
+            
         if 'old_statement' in results:
             # This matched an implements statement. We'll remove it.
             self.fixups.append(node)

Modified: zope.fixers/trunk/zope/fixers/tests.py
===================================================================
--- zope.fixers/trunk/zope/fixers/tests.py	2009-04-06 16:44:16 UTC (rev 98942)
+++ zope.fixers/trunk/zope/fixers/tests.py	2009-04-06 17:16:32 UTC (rev 98943)
@@ -1,15 +1,29 @@
 import unittest
 from lib2to3.refactor import RefactoringTool
 
-example = """
-# Basic test:
+# Check that various import syntaxes get renamed properly.
+imports_source = """
 from zope.interface import Interface, implements, providedBy
 from zope.interface import providedBy, implements, Interface
 from zope.interface import providedBy, implements
 from zope.interface import implements, Interface
 from zope.interface import implements
 from zope.interface import implements as renamed
+"""
 
+imports_target = """
+from zope.interface import Interface, implementor, providedBy
+from zope.interface import providedBy, implementor, Interface
+from zope.interface import providedBy, implementor
+from zope.interface import implementor, Interface
+from zope.interface import implementor
+from zope.interface import implementor as renamed
+"""
+
+# Test a simple case.
+simple_source = """
+from zope.interface import implements
+
 class IFoo(Interface):
     pass
 
@@ -17,7 +31,23 @@
     "An IFoo class"
     
     implements(IFoo)
-    
+"""
+
+simple_target = """
+from zope.interface import implementor
+
+class IFoo(Interface):
+    pass
+
+ at implementor(IFoo)
+class Foo:
+    "An IFoo class"
+"""
+
+# Make sure it works even if implements gets renamed.
+renamed_source = """
+from zope.interface import implements as renamed
+
 class IBar(Interface):
     pass
     
@@ -25,40 +55,117 @@
     "An IBar class"
     
     renamed(IBar)
-    
-# Test ends
 """
 
-target = """
-# Basic test:
-from zope.interface import Interface, implementor, providedBy
-from zope.interface import providedBy, implementor, Interface
-from zope.interface import providedBy, implementor
-from zope.interface import implementor, Interface
-from zope.interface import implementor
+renamed_target = """
 from zope.interface import implementor as renamed
 
+class IBar(Interface):
+    pass
+    
+ at renamed(IBar)
+class Bar:
+    "An IBar class"
+"""
+
+# Often only the module gets imported.
+module_import_source = """
+from zope import interface
+
 class IFoo(Interface):
     pass
 
- at implementor(IFoo)
 class Foo:
     "An IFoo class"
     
-class IBar(Interface):
+    interface.implements(IFoo)
+"""
+
+module_import_target = """
+from zope import interface
+
+class IFoo(Interface):
     pass
+
+ at interface.implementor(IFoo)
+class Foo:
+    "An IFoo class"
+"""
+
+# Interface can get renamed. It's unusual, but should be supported.
+module_renamed_source= """
+from zope import interface as zopeinterface
+
+class Foo:
+    "An IFoo class"
     
- at renamed(IBar)
-class Bar:
-    "An IBar class"
+    zopeinterface.implements(IFoo)
+"""
+
+module_renamed_target= """
+from zope import interface as zopeinterface
+
+ at zopeinterface.implementor(IFoo)
+class Foo:
+    "An IFoo class"
+"""
+
+# And lastly, many always uses the full module name.
+full_import_source= """
+import zope.interface
+
+class Foo:
+    "An IFoo class"
     
-# Test ends
+    zope.interface.implements(IFoo)
 """
 
+full_import_target= """
+import zope.interface
+
+ at zope.interface.implementor(IFoo)
+class Foo:
+    "An IFoo class"
+"""
+
+TESTS = [
+]
+
 class FixerTest(unittest.TestCase):
     
-    def test_fixer(self):
-        tool = RefactoringTool(['zope.fixers.fix_implements'])
-        refactored = str(tool.refactor_string(example, 'zope.fixer.test'))
-        self.assertEquals(refactored, target)
-        
\ No newline at end of file
+    def setUp(self):
+        self.tool = RefactoringTool(['zope.fixers.fix_implements'])
+    
+    def _test(self, source, target):
+        refactored = str(self.tool.refactor_string(source, 'zope.fixer.test'))
+        if refactored != target:
+            match = ''
+            for i in range(min(len(refactored), len(target))):
+                if refactored[i] == target[i]:
+                    match += refactored[i]
+                else:
+                    break
+            msg = "Test failed at character %i" % i
+            msg += "\nResult:\n" + refactored
+            msg += "\nFailed:\n" + refactored[i:]
+            msg += "\nTarget:\n" + target[i:]
+            self.fail(msg)
+        
+    def test_imports(self):
+        self._test(imports_source, imports_target)
+
+    def test_simple(self):
+        self._test(simple_source, simple_target)
+        
+    def test_renamed(self):
+        self._test(renamed_source, renamed_target)
+        
+    def test_module_import(self):
+        self._test(module_import_source, module_import_target)
+        
+    def test_module_renamed(self):
+        self._test(module_renamed_source, module_renamed_target)
+        
+    def test_full_import(self):
+        self._test(full_import_source, full_import_target)
+    
\ No newline at end of file



More information about the Checkins mailing list