Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added a check to raise error on missing whitespace around arithmetic … #1143

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CONTRIBUTORS.txt
Original file line number Diff line number Diff line change
Expand Up @@ -110,3 +110,5 @@ Order doesn't matter (not that much, at least ;)
* Alexander Todorov: added new errro conditions to 'bad-super-call'.

* Roy Williams (Lyft): added check for implementing __eq__ without implementing __hash__.

* Aswin Murugesh: Added checks for spaces around arithmetic operators
3 changes: 3 additions & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,9 @@ Release date: tba
* Added a new Python 3 warning around implementing '__div__' or '__idiv__'
as those methods are phased out in Python 3.

* Added check for whitespace around arithmetic operators

Closes #1110

What's new in Pylint 1.6.3?
===========================
Expand Down
23 changes: 23 additions & 0 deletions pylint/checkers/format.py
Original file line number Diff line number Diff line change
Expand Up @@ -660,6 +660,8 @@ def _name_construct(token):
return 'bracket'
elif token[1] in ('<', '>', '<=', '>=', '!=', '=='):
return 'comparison'
elif token[1] in ['+', '-', '/', '*', '**']:
return 'arithmetic operator'
else:
if self._inside_brackets('('):
return 'keyword argument assignment'
Expand Down Expand Up @@ -695,6 +697,25 @@ def _name_construct(token):
args=(count, state, position, construct,
_underline_token(token)))

def _check_arithmetic_operators(self, tokens, i):
"Check that the arithmetic operators are surrounded by spaces wherever appropriate"
if tokens[i][1] in ['+', '-']:
if tokens[i-1][1] not in [tokenize.NUMBER, tokenize.NAME]:
self._check_space(tokens, i, (_MUST, _MUST_NOT))
else:
self._check_space(tokens, i, (_MUST, _MUST))
elif tokens[i][1] in ['*', '**']:
if tokens[i-1][1] in ['('] or tokens[i-1][0] == tokenize.NL:
self._check_space(tokens, i, (_MUST_NOT, _MUST_NOT))
elif tokens[i-1][1] in [',', 'lambda']:
self._check_space(tokens, i, (_MUST, _MUST_NOT))
else:
self._check_space(tokens, i, (_MUST, _MUST))
else:
self._check_space(tokens, i, (_MUST, _MUST))



def _inside_brackets(self, left):
return self._bracket_stack[-1] == left

Expand All @@ -717,6 +738,8 @@ def _prepare_token_dispatcher(self):

(['lambda'], self._open_lambda),

(['*', '+', '-', '*', '/', '**'], self._check_arithmetic_operators),

]

dispatch = {}
Expand Down
8 changes: 8 additions & 0 deletions pylint/test/functional/arithmetic_operators.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# pylint: disable=missing-docstring,invalid-name,unused-argument
def sample_function(*args, **kwargs):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How does this behave to operator with different priorities? For instance, PEP 8 recommends something as x = x*2 - 1 instead of x = x * 2 - 1.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pep8 throws an E226 error on a = a*4 + 2

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FWIW pycodestyle (which is what pep8 was renamed to) has E226 disabled by default. I think having it enabled by default in pylint would be a mistake.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In that case, should we have a separate error message for this which is disabled by default?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this could be made to work with some tweaking. I think this makes sense in the cases where both operands are not BinOps and neither is the parent (with the exception of being the RHS in a AssignTypeMixin). This would catch x = a*4 but allow a = a*4 + 2. What do you think @PCManticore @The-Compiler ?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rowillia sure I will try it out.. Can you please explain a bit?

pass

a = 5*4 # [bad-whitespace]
b = a* 20 # [bad-whitespace]
b = 4 /20 # [bad-whitespace]
c = 4 + 30
9 changes: 9 additions & 0 deletions pylint/test/functional/arithmetic_operators.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
bad-whitespace:5::"Exactly one space required around arithmetic operator
a = 5*4 # [bad-whitespace]
^"
bad-whitespace:6::"Exactly one space required before arithmetic operator
b = a* 20 # [bad-whitespace]
^"
bad-whitespace:7::"Exactly one space required after arithmetic operator
b = 4 /20 # [bad-whitespace]
^"
4 changes: 2 additions & 2 deletions pylint/test/functional/bad_continuation.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,8 @@
'long continuation',
'key2': 'breaking'
'wrong', # [bad-continuation]
'key3': 2*(
2+2),
'key3': 2 * (
2 + 2),
'key4': ('parenthesis',
'continuation') # No comma here
}
Expand Down
2 changes: 1 addition & 1 deletion pylint/test/functional/cellvar_escaping_loop.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ def bad_case4():
for i in range(10):
def nested():
"""Nested function."""
return i**2 # [cell-var-from-loop]
return i ** 2 # [cell-var-from-loop]
lst.append(nested)
return lst

Expand Down
2 changes: 1 addition & 1 deletion pylint/test/functional/deprecated_lambda.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import functools

# Don't do this, use a comprehension instead.
assert map(lambda x: x*2, [1, 2, 3]) == [2, 4, 6] # [deprecated-lambda]
assert map(lambda x: x * 2, [1, 2, 3]) == [2, 4, 6] # [deprecated-lambda]

assert filter(lambda x: x != 1, [1, 2, 3]) == [2, 3] # [deprecated-lambda]

Expand Down
2 changes: 1 addition & 1 deletion pylint/test/functional/genexp_in_class_scope.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@

class MyClass(object):
var1 = []
var2 = list(value*2 for value in var1)
var2 = list(value * 2 for value in var1)
4 changes: 2 additions & 2 deletions pylint/test/functional/lost_exception.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ def insidious_break_and_return():
my_var = 0

try:
my_var += 1.0/i
my_var += 1.0 / i
if i < -3:
break
else:
Expand All @@ -24,7 +24,7 @@ def break_and_return():
if i:
break
try:
my_var += 1.0/i
my_var += 1.0 / i
finally:
for _ in range(2):
if True:
Expand Down
2 changes: 1 addition & 1 deletion pylint/test/functional/misplaced_bare_raise.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ class A(object):
# not to rely on it.
exc = None
try:
1/0
1 / 0
except ZeroDivisionError as exc:
pass
if exc:
Expand Down
8 changes: 4 additions & 4 deletions pylint/test/input/func_bad_cont_dictcomp_py27.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,15 @@
range(3)}

# Dictionary comprehensions with multiple loops broken in different places
C3 = {x*y: (x, y) for x in range(3) for y in range(3)}
C3 = {x * y: (x, y) for x in range(3) for y in range(3)}

C4 = {x*y: (x, y)
C4 = {x * y: (x, y)
for x in range(3) for y in range(3)}

C5 = {x*y: (x, y) for x
C5 = {x * y: (x, y) for x
in range(3) for y in range(3)}

C6 = {x*y: (x, y) for x in range(3)
C6 = {x * y: (x, y) for x in range(3)
for y in range(3)}

C7 = {key:
Expand Down
4 changes: 2 additions & 2 deletions pylint/test/input/func_w0613.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ def selected(cls, *args, **kwargs):

def using_inner_function(self, etype, size=1):
"""return a fake result set for a particular entity type"""
rset = AAAA([('A',)]*size, '%s X' % etype,
description=[(etype,)]*size)
rset = AAAA([('A',)] * size, '%s X' % etype,
description=[(etype,)] * size)
def inner(row, col=0, etype=etype, req=self, rset=rset):
"""inner using all its argument"""
# pylint: disable = E1103
Expand Down