Skip to content

Commit bafb149

Browse files
Qwiddle13Pierre-Sassoulasmanderj
authored
Enhancement/add checker consider using min max builtin (#4359)
Co-authored-by: Pierre Sassoulas <[email protected]> Co-authored-by: manderj <[email protected]>
1 parent 1c0ee1d commit bafb149

File tree

6 files changed

+206
-0
lines changed

6 files changed

+206
-0
lines changed

CONTRIBUTORS.txt

+4
Original file line numberDiff line numberDiff line change
@@ -472,3 +472,7 @@ contributors:
472472
* Sebastian Müller: contributor
473473

474474
* Ramiro Leal-Cavazos (ramiro050): Fixed bug preventing pylint from working with emacs tramp
475+
476+
* manderj: contributor
477+
478+
* qwiddle: contributor

ChangeLog

+4
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@ Release date: Undefined
3535

3636
Closes #2822, #4206, #4284
3737

38+
* Add ``consider-using-min-max-builtin`` check for if statement which could be replaced by Python builtin min or max
39+
40+
Closes #3406
41+
3842

3943
What's New in Pylint 2.7.5?
4044
===========================

doc/whatsnew/2.8.rst

+2
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ New checkers
1717
* Add new extension ``ConfusingConsecutiveElifChecker``. This optional checker emits a refactoring message (R5601 ``confusing-consecutive-elif``)
1818
if if/elif statements with different indentation levels follow directly one after the other.
1919

20+
* Add ``consider-using-min-max-builtin`` check for if statement which could be replaced by Python builtin min or max.
21+
2022
Other Changes
2123
=============
2224

pylint/checkers/refactoring/refactoring_checker.py

+88
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,16 @@ class RefactoringChecker(checkers.BaseTokenChecker):
291291
"Comprehension inside of 'any' or 'all' is unnecessary. "
292292
"A generator would be sufficient and faster.",
293293
),
294+
"R1730": (
295+
"Consider using '%s' instead of unnecessary if block",
296+
"consider-using-min-builtin",
297+
"Using the min builtin instead of a conditional improves readability and conciseness.",
298+
),
299+
"R1731": (
300+
"Consider using '%s' instead of unnecessary if block",
301+
"consider-using-max-builtin",
302+
"Using the max builtin instead of a conditional improves readability and conciseness.",
303+
),
294304
}
295305
options = (
296306
(
@@ -609,6 +619,84 @@ def visit_if(self, node):
609619
self._check_superfluous_else_break(node)
610620
self._check_superfluous_else_continue(node)
611621
self._check_consider_get(node)
622+
self._check_consider_using_min_max_builtin(node)
623+
624+
def _check_consider_using_min_max_builtin(self, node: astroid.If):
625+
"""Check if the given if node can be refactored as an min/max python builtin."""
626+
if self._is_actual_elif(node) or node.orelse:
627+
# Not interested in if statements with multiple branches.
628+
return
629+
630+
if len(node.body) != 1:
631+
return
632+
633+
body = node.body[0]
634+
# Check if condition can be reduced.
635+
if not hasattr(body, "targets") or len(body.targets) != 1:
636+
return
637+
638+
target = body.targets[0]
639+
if not (
640+
isinstance(node.test, astroid.Compare)
641+
and not isinstance(target, astroid.Subscript)
642+
and not isinstance(node.test.left, astroid.Subscript)
643+
and isinstance(body, astroid.Assign)
644+
):
645+
return
646+
647+
# Check that the assignation is on the same variable.
648+
if hasattr(node.test.left, "name"):
649+
left_operand = node.test.left.name
650+
elif hasattr(node.test.left, "attrname"):
651+
left_operand = node.test.left.attrname
652+
else:
653+
return
654+
655+
if hasattr(target, "name"):
656+
target_assignation = target.name
657+
elif hasattr(target, "attrname"):
658+
target_assignation = target.attrname
659+
else:
660+
return
661+
662+
if not (left_operand == target_assignation):
663+
return
664+
665+
if len(node.test.ops) > 1:
666+
return
667+
668+
if not isinstance(body.value, (astroid.Name, astroid.Const)):
669+
return
670+
671+
operator, right_statement = node.test.ops[0]
672+
if isinstance(body.value, astroid.Name):
673+
body_value = body.value.name
674+
else:
675+
body_value = body.value.value
676+
677+
if isinstance(right_statement, astroid.Name):
678+
right_statement_value = right_statement.name
679+
else:
680+
right_statement_value = right_statement.value
681+
682+
# Verify the right part of the statement is the same.
683+
if right_statement_value != body_value:
684+
return
685+
686+
if operator in ("<", "<="):
687+
reduced_to = "{target} = max({target}, {item})".format(
688+
target=target_assignation, item=body_value
689+
)
690+
self.add_message(
691+
"consider-using-max-builtin", node=node, args=(reduced_to,)
692+
)
693+
elif operator in (">", ">="):
694+
reduced_to = "{target} = min({target}, {item})".format(
695+
target=target_assignation, item=body_value
696+
)
697+
self.add_message(
698+
"consider-using-min-builtin", node=node, args=(reduced_to,)
699+
)
612700

613701
@utils.check_messages("simplifiable-if-expression")
614702
def visit_ifexp(self, node):
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
# pylint: disable=missing-docstring, invalid-name, too-few-public-methods, redefined-outer-name
2+
3+
value = 10
4+
value2 = 0
5+
value3 = 3
6+
7+
# Positive
8+
if value < 10: # [consider-using-max-builtin]
9+
value = 10
10+
11+
if value >= 10: # [consider-using-min-builtin]
12+
value = 10
13+
14+
if value <= 10: # [consider-using-max-builtin]
15+
value = 10
16+
17+
if value > 10: # [consider-using-min-builtin]
18+
value = 10
19+
20+
if value < value2: # [consider-using-max-builtin]
21+
value = value2
22+
23+
if value > value2: # [consider-using-min-builtin]
24+
value = value2
25+
26+
27+
class A:
28+
def __init__(self):
29+
self.value = 13
30+
31+
32+
A1 = A()
33+
if A1.value > 10: # [consider-using-min-builtin]
34+
A1.value = 10
35+
36+
37+
class AA:
38+
def __init__(self, value):
39+
self.value = value
40+
41+
def __gt__(self, b):
42+
return self.value > b
43+
44+
def __ge__(self, b):
45+
return self.value >= b
46+
47+
def __lt__(self, b):
48+
return self.value < b
49+
50+
def __le__(self, b):
51+
return self.value <= b
52+
53+
54+
A1 = AA(0)
55+
A2 = AA(3)
56+
57+
if A1 > A2: # [consider-using-min-builtin]
58+
A1 = A2
59+
60+
if A2 < A1: # [consider-using-max-builtin]
61+
A2 = A1
62+
63+
if A1 >= A2: # [consider-using-min-builtin]
64+
A1 = A2
65+
66+
if A2 <= A1: # [consider-using-max-builtin]
67+
A2 = A1
68+
69+
# Negative
70+
if value > 10:
71+
value = 2
72+
73+
if value > 10:
74+
value = 2
75+
value2 = 3
76+
77+
if value > value2:
78+
value = value3
79+
80+
if value > 5:
81+
value = value3
82+
83+
if 2 < value <= 3:
84+
value = 1
85+
86+
if value <= 3:
87+
value = 5
88+
89+
if value <= 3:
90+
value = 5
91+
elif value == 3:
92+
value = 2
93+
94+
if value > 10:
95+
value = 10
96+
else:
97+
value = 3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
consider-using-max-builtin:8:0::"Consider using 'value = max(value, 10)' instead of unnecessary if block"
2+
consider-using-min-builtin:11:0::"Consider using 'value = min(value, 10)' instead of unnecessary if block"
3+
consider-using-max-builtin:14:0::"Consider using 'value = max(value, 10)' instead of unnecessary if block"
4+
consider-using-min-builtin:17:0::"Consider using 'value = min(value, 10)' instead of unnecessary if block"
5+
consider-using-max-builtin:20:0::"Consider using 'value = max(value, value2)' instead of unnecessary if block"
6+
consider-using-min-builtin:23:0::"Consider using 'value = min(value, value2)' instead of unnecessary if block"
7+
consider-using-min-builtin:33:0::"Consider using 'value = min(value, 10)' instead of unnecessary if block"
8+
consider-using-min-builtin:57:0::"Consider using 'A1 = min(A1, A2)' instead of unnecessary if block"
9+
consider-using-max-builtin:60:0::"Consider using 'A2 = max(A2, A1)' instead of unnecessary if block"
10+
consider-using-min-builtin:63:0::"Consider using 'A1 = min(A1, A2)' instead of unnecessary if block"
11+
consider-using-max-builtin:66:0::"Consider using 'A2 = max(A2, A1)' instead of unnecessary if block"

0 commit comments

Comments
 (0)