Skip to content
Snippets Groups Projects
Unverified Commit 53431001 authored by Ian Lee's avatar Ian Lee Committed by GitHub
Browse files

Merge pull request #502 from sigmavirus24/add-W504

Add W504 for line breaks before binary operators
parents 4cee4a5f 147c399c
No related branches found
No related tags found
No related merge requests found
Loading
Loading
@@ -6,6 +6,8 @@ UNRELEASED
 
New checks:
 
* Add W504 warning for checking that a break doesn't happen after a binary
operator. This check is ignored by default
* Add W605 warning for invalid escape sequences in string literals
 
2.3.1 (2017-01-31)
Loading
Loading
Loading
Loading
@@ -80,7 +80,7 @@ except ImportError:
__version__ = '2.3.1'
 
DEFAULT_EXCLUDE = '.svn,CVS,.bzr,.hg,.git,__pycache__,.tox'
DEFAULT_IGNORE = 'E121,E123,E126,E226,E24,E704,W503'
DEFAULT_IGNORE = 'E121,E123,E126,E226,E24,E704,W503,W504'
try:
if sys.platform == 'win32':
USER_CONFIG = os.path.expanduser(r'~\.pycodestyle')
Loading
Loading
@@ -1135,34 +1135,26 @@ def explicit_line_join(logical_line, tokens):
parens -= 1
 
 
@register_check
def break_around_binary_operator(logical_line, tokens):
r"""
Avoid breaks before binary operators.
def _is_binary_operator(token_type, text):
is_op_token = token_type == tokenize.OP
is_conjunction = text in ['and', 'or']
# NOTE(sigmavirus24): Previously the not_a_symbol check was executed
# conditionally. Since it is now *always* executed, text may be None.
# In that case we get a TypeError for `text not in str`.
not_a_symbol = text and text not in "()[]{},:.;@=%~"
# The % character is strictly speaking a binary operator, but the
# common usage seems to be to put it next to the format parameters,
# after a line break.
return ((is_op_token or is_conjunction) and not_a_symbol)
 
The preferred place to break around a binary operator is after the
operator, not before it.
 
W503: (width == 0\n + height == 0)
W503: (width == 0\n and height == 0)
def _break_around_binary_operators(tokens):
"""Private function to reduce duplication.
 
Okay: (width == 0 +\n height == 0)
Okay: foo(\n -x)
Okay: foo(x\n [])
Okay: x = '''\n''' + ''
Okay: foo(x,\n -y)
Okay: foo(x, # comment\n -y)
Okay: var = (1 &\n ~2)
Okay: var = (1 /\n -2)
Okay: var = (1 +\n -1 +\n -2)
This factors out the shared details between
:func:`break_before_binary_operator` and
:func:`break_after_binary_operator`.
"""
def is_binary_operator(token_type, text):
# The % character is strictly speaking a binary operator, but the
# common usage seems to be to put it next to the format parameters,
# after a line break.
return ((token_type == tokenize.OP or text in ['and', 'or']) and
text not in "()[]{},:.;@=%~")
line_break = False
unary_context = True
# Previous non-newline token types and text
Loading
Loading
@@ -1174,17 +1166,78 @@ def break_around_binary_operator(logical_line, tokens):
if ('\n' in text or '\r' in text) and token_type != tokenize.STRING:
line_break = True
else:
if (is_binary_operator(token_type, text) and line_break and
not unary_context and
not is_binary_operator(previous_token_type,
previous_text)):
yield start, "W503 line break before binary operator"
yield (token_type, text, previous_token_type, previous_text,
line_break, unary_context, start)
unary_context = text in '([{,;'
line_break = False
previous_token_type = token_type
previous_text = text
 
 
@register_check
def break_before_binary_operator(logical_line, tokens):
r"""
Avoid breaks before binary operators.
The preferred place to break around a binary operator is after the
operator, not before it.
W503: (width == 0\n + height == 0)
W503: (width == 0\n and height == 0)
W503: var = (1\n & ~2)
W503: var = (1\n / -2)
W503: var = (1\n + -1\n + -2)
Okay: foo(\n -x)
Okay: foo(x\n [])
Okay: x = '''\n''' + ''
Okay: foo(x,\n -y)
Okay: foo(x, # comment\n -y)
"""
for context in _break_around_binary_operators(tokens):
(token_type, text, previous_token_type, previous_text,
line_break, unary_context, start) = context
if (_is_binary_operator(token_type, text) and line_break and
not unary_context and
not _is_binary_operator(previous_token_type,
previous_text)):
yield start, "W503 line break before binary operator"
@register_check
def break_after_binary_operator(logical_line, tokens):
r"""
Avoid breaks after binary operators.
The preferred place to break around a binary operator is before the
operator, not after it.
W504: (width == 0 +\n height == 0)
W504: (width == 0 and\n height == 0)
W504: var = (1 &\n ~2)
Okay: foo(\n -x)
Okay: foo(x\n [])
Okay: x = '''\n''' + ''
Okay: x = '' + '''\n'''
Okay: foo(x,\n -y)
Okay: foo(x, # comment\n -y)
The following should be W504 but unary_context is tricky with these
Okay: var = (1 /\n -2)
Okay: var = (1 +\n -1 +\n -2)
"""
for context in _break_around_binary_operators(tokens):
(token_type, text, previous_token_type, previous_text,
line_break, unary_context, start) = context
if (_is_binary_operator(previous_token_type, previous_text) and
line_break and
not unary_context and
not _is_binary_operator(token_type, text)):
error_pos = (start[0] - 1, start[1])
yield error_pos, "W504 line break after binary operator"
@register_check
def comparison_to_singleton(logical_line, noqa):
r"""Comparison to singletons should use "is" or "is not".
Loading
Loading
Loading
Loading
@@ -6,5 +6,5 @@ license_file = LICENSE
 
[pycodestyle]
select =
ignore = E226,E24
ignore = E226,E24,W504
max_line_length = 79
Loading
Loading
@@ -20,9 +20,9 @@ print "E124", ("visual",
#: E124
a = (123,
)
#: E129
if (row < 0 or self.moduleCount <= row or
col < 0 or self.moduleCount <= col):
#: E129 W503
if (row < 0 or self.moduleCount <= row
or col < 0 or self.moduleCount <= col):
raise Exception("%s,%s - %s" % (row, col, self.moduleCount))
#: E126
print "E126", (
Loading
Loading
@@ -195,9 +195,9 @@ def qualify_by_address(
self, cr, uid, ids, context=None,
params_to_check=frozenset(QUALIF_BY_ADDRESS_PARAM)):
""" This gets called by the web server """
#: E129
if (a == 2 or
b == "abc def ghi"
#: E129 W503
if (a == 2
or b == "abc def ghi"
"jkl mno"):
return True
#:
Loading
Loading
@@ -225,22 +225,21 @@ rv.update(dict.fromkeys((
eat_a_dict_a_day({
"foo": "bar",
})
#: E126
#: E126 W503
if (
x == (
3
) or
y == 4):
)
or y == 4):
pass
#: E126
#: E126 W503 W503
if (
x == (
3
) or
x == (
3
) or
y == 4):
)
or x == (
3)
or y == 4):
pass
#: E131
troublesome_hash = {
Loading
Loading
if (
x == (
3
) or
y == 4):
) or y == 4):
pass
 
y = x == 2 \
Loading
Loading
@@ -17,15 +16,14 @@ if x == 2 \
or y > 1 \
or x == 3:
pass
if (foo == bar and
baz == frop):
#: W503
if (foo == bar
and baz == frop):
pass
#: W503
if (
foo == bar and
baz == frop
foo == bar
and baz == frop
):
pass
 
Loading
Loading
@@ -109,7 +107,7 @@ sat = 'AAA' \
'BBB' \
'iii' \
'CCC'
#: W504 W504
abricot = (3 +
4 +
5 + 6)
Loading
Loading
@@ -138,8 +136,7 @@ def long_function_name(
var_one, var_two, var_three,
var_four):
print(var_one)
#: W504
if ((row < 0 or self.moduleCount <= row or
col < 0 or self.moduleCount <= col)):
raise Exception("%s,%s - %s" % (row, col, self.moduleCount))
Loading
Loading
@@ -184,23 +181,23 @@ if bar:
"to match that of the opening "
"bracket's line"
)
#
#: W504
# you want vertical alignment, so use a parens
if ((foo.bar("baz") and
foo.bar("frop")
)):
print "yes"
#: W504
# also ok, but starting to look like LISP
if ((foo.bar("baz") and
foo.bar("frop"))):
print "yes"
#: W504
if (a == 2 or
b == "abc def ghi"
"jkl mno"):
return True
#: W504
if (a == 2 or
b == """abc def ghi
jkl mno"""):
Loading
Loading
@@ -224,22 +221,19 @@ print 'l.{line}\t{pos}\t{name}\t{text}'.format(
print('%-7d %s per second (%d total)' % (
options.counters[key] / elapsed, key,
options.counters[key]))
#: W504
if os.path.exists(os.path.join(path, PEP8_BIN)):
cmd = ([os.path.join(path, PEP8_BIN)] +
self._pep8_options(targetfile))
#: W504
fixed = (re.sub(r'\t+', ' ', target[c::-1], 1)[::-1] +
target[c + 1:])
#: W504
fixed = (
re.sub(r'\t+', ' ', target[c::-1], 1)[::-1] +
target[c + 1:]
)
#: W504
if foo is None and bar is "frop" and \
blah == 'yeah':
blah = 'yeahnah'
Loading
Loading
Loading
Loading
@@ -7,7 +7,7 @@ if False:
#: W191
y = x == 2 \
or x == 3
#: E101 W191
#: E101 W191 W504
if (
x == (
3
Loading
Loading
@@ -26,11 +26,11 @@ if x == 2 \
pass
#:
 
#: E101 W191
#: E101 W191 W504
if (foo == bar and
baz == frop):
pass
#: E101 W191
#: E101 W191 W504
if (
foo == bar and
baz == frop
Loading
Loading
@@ -52,7 +52,7 @@ def long_function_name(
var_one, var_two, var_three,
var_four):
print(var_one)
#: E101 W191
#: E101 W191 W504
if ((row < 0 or self.moduleCount <= row or
col < 0 or self.moduleCount <= col)):
raise Exception("%s,%s - %s" % (row, col, self.moduleCount))
Loading
Loading
@@ -65,23 +65,23 @@ if bar:
"bracket's line"
)
#
#: E101 W191
#: E101 W191 W504
# you want vertical alignment, so use a parens
if ((foo.bar("baz") and
foo.bar("frop")
)):
print "yes"
#: E101 W191
#: E101 W191 W504
# also ok, but starting to look like LISP
if ((foo.bar("baz") and
foo.bar("frop"))):
print "yes"
#: E101 W191
#: E101 W191 W504
if (a == 2 or
b == "abc def ghi"
"jkl mno"):
return True
#: E101 W191
#: E101 W191 W504
if (a == 2 or
b == """abc def ghi
jkl mno"""):
Loading
Loading
@@ -93,7 +93,7 @@ if length > options.max_line_length:
 
 
#
#: E101 W191 W191
#: E101 W191 W191 W504
if os.path.exists(os.path.join(path, PEP8_BIN)):
cmd = ([os.path.join(path, PEP8_BIN)] +
self._pep8_options(targetfile))
Loading
Loading
Loading
Loading
@@ -43,7 +43,7 @@ class PycodestyleTestCase(unittest.TestCase):
os.path.join(ROOT_DIR, 'setup.py')]
report = self._style.init_report(pycodestyle.StandardReport)
report = self._style.check_files(files)
self.assertFalse(report.total_errors,
self.assertEqual(list(report.messages.keys()), ['W504'],
msg='Failures: %s' % report.messages)
 
 
Loading
Loading
Loading
Loading
@@ -166,7 +166,7 @@ class APITestCase(unittest.TestCase):
self.assertEqual(pep8style.options.filename, ['*.py'])
self.assertEqual(pep8style.options.format, 'default')
self.assertEqual(pep8style.options.select, ())
self.assertEqual(pep8style.options.ignore, ('E226', 'E24'))
self.assertEqual(pep8style.options.ignore, ('E226', 'E24', 'W504'))
self.assertEqual(pep8style.options.max_line_length, 79)
 
def test_styleguide_ignore_code(self):
Loading
Loading
@@ -182,7 +182,7 @@ class APITestCase(unittest.TestCase):
self.assertEqual(options.select, ())
self.assertEqual(
options.ignore,
('E121', 'E123', 'E126', 'E226', 'E24', 'E704', 'W503')
('E121', 'E123', 'E126', 'E226', 'E24', 'E704', 'W503', 'W504')
)
 
options = parse_argv('--doctest').options
Loading
Loading
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment