Skip to content

Commit c2f607e

Browse files
sigmavirus24IanLee1521
authored andcommitted
Add W504 for line breaks before binary operators
This flips the W503 rule to enforce line breaks before binary operators. Related #498
1 parent 4438622 commit c2f607e

File tree

3 files changed

+99
-49
lines changed

3 files changed

+99
-49
lines changed

pycodestyle.py

Lines changed: 80 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@
6868
__version__ = '2.1.0.dev0'
6969

7070
DEFAULT_EXCLUDE = '.svn,CVS,.bzr,.hg,.git,__pycache__,.tox'
71-
DEFAULT_IGNORE = 'E121,E123,E126,E226,E24,E704,W503'
71+
DEFAULT_IGNORE = 'E121,E123,E126,E226,E24,E704,W503,W504'
7272
try:
7373
if sys.platform == 'win32':
7474
USER_CONFIG = os.path.expanduser(r'~\.pycodestyle')
@@ -1033,7 +1033,46 @@ def explicit_line_join(logical_line, tokens):
10331033
parens -= 1
10341034

10351035

1036-
def break_around_binary_operator(logical_line, tokens):
1036+
def _is_binary_operator(token_type, text):
1037+
is_op_token = token_type == tokenize.OP
1038+
is_conjunction = text in ['and', 'or']
1039+
# NOTE(sigmavirus24): Previously the not_a_symbol check was executed
1040+
# conditionally. Since it is now *always* executed, text may be None.
1041+
# In that case we get a TypeError for `text not in str`.
1042+
not_a_symbol = text and text not in "()[]{},:.;@=%~"
1043+
# The % character is strictly speaking a binary operator, but the
1044+
# common usage seems to be to put it next to the format parameters,
1045+
# after a line break.
1046+
return ((is_op_token or is_conjunction) and not_a_symbol)
1047+
1048+
1049+
def _break_around_binary_operators(tokens):
1050+
"""Private function to reduce duplication.
1051+
1052+
This factors out the shared details between
1053+
:func:`break_before_binary_operator` and
1054+
:func:`break_after_binary_operator`.
1055+
"""
1056+
line_break = False
1057+
unary_context = True
1058+
# Previous non-newline token types and text
1059+
previous_token_type = None
1060+
previous_text = None
1061+
for token_type, text, start, end, line in tokens:
1062+
if token_type == tokenize.COMMENT:
1063+
continue
1064+
if ('\n' in text or '\r' in text) and token_type != tokenize.STRING:
1065+
line_break = True
1066+
else:
1067+
yield (token_type, text, previous_token_type, previous_text,
1068+
line_break, unary_context, start)
1069+
unary_context = text in '([{,;'
1070+
line_break = False
1071+
previous_token_type = token_type
1072+
previous_text = text
1073+
1074+
1075+
def break_before_binary_operator(logical_line, tokens):
10371076
r"""
10381077
Avoid breaks before binary operators.
10391078
@@ -1053,33 +1092,46 @@ def break_around_binary_operator(logical_line, tokens):
10531092
Okay: var = (1 /\n -2)
10541093
Okay: var = (1 +\n -1 +\n -2)
10551094
"""
1056-
def is_binary_operator(token_type, text):
1057-
# The % character is strictly speaking a binary operator, but the
1058-
# common usage seems to be to put it next to the format parameters,
1059-
# after a line break.
1060-
return ((token_type == tokenize.OP or text in ['and', 'or']) and
1061-
text not in "()[]{},:.;@=%~")
1095+
for context in _break_around_binary_operators(tokens):
1096+
(token_type, text, previous_token_type, previous_text,
1097+
line_break, unary_context, start) = context
1098+
if (_is_binary_operator(token_type, text) and line_break and
1099+
not unary_context and
1100+
not _is_binary_operator(previous_token_type,
1101+
previous_text)):
1102+
yield start, "W503 line break before binary operator"
10621103

1063-
line_break = False
1064-
unary_context = True
1065-
# Previous non-newline token types and text
1066-
previous_token_type = None
1067-
previous_text = None
1068-
for token_type, text, start, end, line in tokens:
1069-
if token_type == tokenize.COMMENT:
1070-
continue
1071-
if ('\n' in text or '\r' in text) and token_type != tokenize.STRING:
1072-
line_break = True
1073-
else:
1074-
if (is_binary_operator(token_type, text) and line_break and
1075-
not unary_context and
1076-
not is_binary_operator(previous_token_type,
1077-
previous_text)):
1078-
yield start, "W503 line break before binary operator"
1079-
unary_context = text in '([{,;'
1080-
line_break = False
1081-
previous_token_type = token_type
1082-
previous_text = text
1104+
1105+
def break_after_binary_operator(logical_line, tokens):
1106+
r"""
1107+
Avoid breaks after binary operators.
1108+
1109+
The preferred place to break around a binary operator is after the
1110+
operator, not before it.
1111+
1112+
W504: (width == 0 +\n height == 0)
1113+
W504: (width == 0 and\n height == 0)
1114+
1115+
Okay: (width == 0\n + height == 0)
1116+
Okay: foo(\n -x)
1117+
Okay: foo(x\n [])
1118+
Okay: x = '''\n''' + ''
1119+
Okay: x = '' + '''\n'''
1120+
Okay: foo(x,\n -y)
1121+
Okay: foo(x, # comment\n -y)
1122+
Okay: var = (1\n & ~2)
1123+
Okay: var = (1\n / -2)
1124+
Okay: var = (1\n + -1\n + -2)
1125+
"""
1126+
for context in _break_around_binary_operators(tokens):
1127+
(token_type, text, previous_token_type, previous_text,
1128+
line_break, unary_context, start) = context
1129+
if (_is_binary_operator(previous_token_type, previous_text)
1130+
and line_break
1131+
and not unary_context
1132+
and not _is_binary_operator(token_type, text)):
1133+
error_pos = (start[0] - 1, start[1])
1134+
yield error_pos, "W504 line break after binary operator"
10831135

10841136

10851137
def comparison_to_singleton(logical_line, noqa):

testsuite/E12.py

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,9 @@
2020
#: E124
2121
a = (123,
2222
)
23-
#: E129
24-
if (row < 0 or self.moduleCount <= row or
25-
col < 0 or self.moduleCount <= col):
23+
#: E129 W503
24+
if (row < 0 or self.moduleCount <= row
25+
or col < 0 or self.moduleCount <= col):
2626
raise Exception("%s,%s - %s" % (row, col, self.moduleCount))
2727
#: E126
2828
print "E126", (
@@ -195,9 +195,9 @@ def qualify_by_address(
195195
self, cr, uid, ids, context=None,
196196
params_to_check=frozenset(QUALIF_BY_ADDRESS_PARAM)):
197197
""" This gets called by the web server """
198-
#: E129
199-
if (a == 2 or
200-
b == "abc def ghi"
198+
#: E129 W503
199+
if (a == 2
200+
or b == "abc def ghi"
201201
"jkl mno"):
202202
return True
203203
#:
@@ -225,22 +225,21 @@ def qualify_by_address(
225225
eat_a_dict_a_day({
226226
"foo": "bar",
227227
})
228-
#: E126
228+
#: E126 W503
229229
if (
230230
x == (
231231
3
232-
) or
233-
y == 4):
232+
)
233+
or y == 4):
234234
pass
235-
#: E126
235+
#: E126 W503 W503
236236
if (
237237
x == (
238238
3
239-
) or
240-
x == (
241-
3
242-
) or
243-
y == 4):
239+
)
240+
or x == (
241+
3)
242+
or y == 4):
244243
pass
245244
#: E131
246245
troublesome_hash = {

testsuite/E12not.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
if (
22
x == (
33
3
4-
) or
5-
y == 4):
4+
) or y == 4):
65
pass
76

87
y = x == 2 \
@@ -19,13 +18,13 @@
1918
pass
2019

2120

22-
if (foo == bar and
23-
baz == frop):
21+
if (foo == bar
22+
and baz == frop):
2423
pass
2524

2625
if (
27-
foo == bar and
28-
baz == frop
26+
foo == bar
27+
and baz == frop
2928
):
3029
pass
3130

0 commit comments

Comments
 (0)