@@ -27,6 +27,45 @@ def _all_elements_are_true(gen):
27
27
return values and all (values )
28
28
29
29
30
+ def _is_node_return_ended (node ):
31
+ """Check if the node ends with an explicit return statement.
32
+
33
+ Args:
34
+ node (astroid.NodeNG): node to be checked.
35
+
36
+ Returns:
37
+ bool: True if the node ends with an explicit statement, False otherwise.
38
+
39
+ """
40
+ # Recursion base case
41
+ if isinstance (node , astroid .Return ):
42
+ return True
43
+ if isinstance (node , astroid .Raise ):
44
+ # a Raise statement doesn't need to end with a return statement
45
+ # but if the exception raised is handled, then the handler has to
46
+ # ends with a return statement
47
+ exc = utils .safe_infer (node .exc )
48
+ if exc is None or exc is astroid .Uninferable :
49
+ return False
50
+ exc_name = exc .pytype ().split ('.' )[- 1 ]
51
+ handlers = utils .get_exception_handlers (node , exc_name )
52
+ if handlers :
53
+ # among all the handlers handling the exception at least one
54
+ # must end with a return statement
55
+ return any (_is_node_return_ended (_handler ) for _handler in handlers )
56
+ # if no handlers handle the exception then it's ok
57
+ return True
58
+ if isinstance (node , astroid .If ):
59
+ # if statement is returning if there are exactly two return statements in its
60
+ # children : one for the body part, the other for the orelse part
61
+ return_stmts = [_is_node_return_ended (_child ) for _child in node .get_children ()]
62
+ return sum (return_stmts ) == 2
63
+ # recurses on the children of the node except for those which are except handler
64
+ # because one cannot be sure that the handler will really be used
65
+ return any (_is_node_return_ended (_child ) for _child in node .get_children ()
66
+ if not isinstance (_child , astroid .ExceptHandler ))
67
+
68
+
30
69
def _if_statement_is_always_returning (if_node ):
31
70
def _has_return_node (elems , scope ):
32
71
for node in elems :
@@ -111,6 +150,14 @@ class RefactoringChecker(checkers.BaseTokenChecker):
111
150
'a generator may lead to hard to find bugs. This PEP specify that '
112
151
'raise StopIteration has to be replaced by a simple return statement' ,
113
152
{'minversion' : (3 , 0 )}),
153
+ 'R1710' : ('Either all return statements in a function should return an expression, '
154
+ 'or none of them should.' ,
155
+ 'inconsistent-return-statements' ,
156
+ 'According to PEP8, if any return statement returns an expression, '
157
+ 'any return statements where no value is returned should explicitly '
158
+ 'state this as return None, and an explicit return statement '
159
+ 'should be present at the end of the function (if reachable)'
160
+ ),
114
161
}
115
162
options = (('max-nested-blocks' ,
116
163
{'default' : 5 , 'type' : 'int' , 'metavar' : '<int>' ,
@@ -122,6 +169,7 @@ class RefactoringChecker(checkers.BaseTokenChecker):
122
169
123
170
def __init__ (self , linter = None ):
124
171
checkers .BaseTokenChecker .__init__ (self , linter )
172
+ self ._return_nodes = {}
125
173
self ._init ()
126
174
127
175
def _init (self ):
@@ -309,12 +357,15 @@ def visit_if(self, node):
309
357
self ._check_nested_blocks (node )
310
358
self ._check_superfluous_else_return (node )
311
359
312
- @utils .check_messages ('too-many-nested-blocks' )
313
- def leave_functiondef (self , _ ):
360
+ @utils .check_messages ('too-many-nested-blocks' , 'inconsistent-return-statements' )
361
+ def leave_functiondef (self , node ):
314
362
# check left-over nested blocks stack
315
363
self ._emit_nested_blocks_message_if_needed (self ._nested_blocks )
316
364
# new scope = reinitialize the stack of nested blocks
317
365
self ._nested_blocks = []
366
+ # check consistent return statements
367
+ self ._check_consistent_returns (node )
368
+ self ._return_nodes [node .name ] = []
318
369
319
370
def visit_raise (self , node ):
320
371
self ._check_stop_iteration_inside_generator (node )
@@ -491,6 +542,33 @@ def _seq_based_ternary_params(node):
491
542
condition = node .slice .value
492
543
return condition , true_value , false_value
493
544
545
+ def visit_functiondef (self , node ):
546
+ self ._return_nodes [node .name ] = []
547
+ return_nodes = node .nodes_of_class (astroid .Return )
548
+ self ._return_nodes [node .name ] = [_rnode for _rnode in return_nodes
549
+ if _rnode .frame () == node .frame ()]
550
+
551
+ def _check_consistent_returns (self , node ):
552
+ """Check that all return statements inside a function are consistent.
553
+
554
+ Return statements are consistent if:
555
+ - all returns are explicit and if there is no implicit return;
556
+ - all returns are empty and if there is, possibly, an implicit return.
557
+
558
+ Args:
559
+ node (astroid.FunctionDef): the function holding the return statements.
560
+
561
+ """
562
+ # explicit return statements are those with a not None value
563
+ explicit_returns = [_node for _node in self ._return_nodes [node .name ]
564
+ if _node .value is not None ]
565
+ if not explicit_returns :
566
+ return
567
+ if (len (explicit_returns ) == len (self ._return_nodes [node .name ])
568
+ and _is_node_return_ended (node )):
569
+ return
570
+ self .add_message ('inconsistent-return-statements' , node = node )
571
+
494
572
495
573
class RecommandationChecker (checkers .BaseChecker ):
496
574
__implements__ = (interfaces .IAstroidChecker ,)
0 commit comments