Anonymous avatar Anonymous committed 53fd6a3

usefunction: better exceptional condition reporting

Comments (0)

Files changed (2)

rope/refactor/usefunction.py

-from rope.base import change, taskhandle, evaluate, exceptions, pyobjects, pynames
+from rope.base import (change, taskhandle, evaluate,
+                       exceptions, pyobjects, pynames, ast)
 from rope.refactor import restructure, sourceutils, similarfinder, importutils
 
 
         this_pymodule = project.pycore.resource_to_pyobject(resource)
         pyname = evaluate.get_pyname_at(this_pymodule, offset)
         if pyname is None:
-            raise exceptions.RefactoringError(
-                'Unresolvable name selected')
+            raise exceptions.RefactoringError('Unresolvable name selected')
         self.pyfunction = pyname.get_object()
         if not isinstance(self.pyfunction, pyobjects.PyFunction) or \
            not isinstance(self.pyfunction.parent, pyobjects.PyModule):
             raise exceptions.RefactoringError(
                 'Use function works for global functions, only.')
         self.resource = self.pyfunction.get_module().get_resource()
+        self._check_returns()
+
+    def _check_returns(self):
+        class CountReturns(object):
+            returns = 0
+            yields = 0
+            def __call__(self, node):
+                if isinstance(node, ast.Return):
+                    self.returns += 1
+                if isinstance(node, ast.Yield):
+                    self.yields += 1
+        counter = CountReturns()
+        node = self.pyfunction.get_ast()
+        ast.call_for_nodes(node, counter, recursive=True)
+        if counter.yields:
+            raise exceptions.RefactoringError('Use function should not '
+                                              'be used on generators.')
+        if counter.returns > 1:
+            raise exceptions.RefactoringError(
+                'usefunction: Function has more than '
+                'one return statement.')
+        if counter.returns == 1 and not isinstance(node.body[-1], ast.Return):
+            raise exceptions.RefactoringError(
+                'usefunction: return should be the last statement.')
 
     def get_changes(self, resources=None,
                     task_handle=taskhandle.NullTaskHandle()):

ropetest/refactor/usefunctiontest.py

         self.assertEquals('def f(p):\n    a = p + 1\n    return a\n'
                           'var = f(p)\nprint(var)\n', self.mod1.read())
 
+    @testutils.assert_raises(exceptions.RefactoringError)
+    def test_exception_when_performing_a_function_with_yield(self):
+        code = 'def func():\n    yield 1\n'
+        self.mod1.write(code)
+        user = UseFunction(self.project, self.mod1, code.index('func'))
+
+    @testutils.assert_raises(exceptions.RefactoringError)
+    def test_exception_when_performing_a_function_two_returns(self):
+        code = 'def func():\n    return 1\n    return 2\n'
+        self.mod1.write(code)
+        user = UseFunction(self.project, self.mod1, code.index('func'))
+
+    @testutils.assert_raises(exceptions.RefactoringError)
+    def test_exception_when_returns_is_not_the_last_statement(self):
+        code = 'def func():\n    return 2\n    a = 1\n'
+        self.mod1.write(code)
+        user = UseFunction(self.project, self.mod1, code.index('func'))
+
 
 if __name__ == '__main__':
     unittest.main()
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.