Skip to content
Merged
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 CHANGES.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
113 changes: 83 additions & 30 deletions pycodestyle.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ def lru_cache(maxsize=128): # noqa as it's a fake implementation.
__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')
Expand Down Expand Up @@ -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
Expand All @@ -1174,17 +1166,78 @@ def is_binary_operator(token_type, text):
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".
Expand Down
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@ license_file = LICENSE

[pycodestyle]
select =
ignore = E226,E24
ignore = E226,E24,W504
max_line_length = 79
29 changes: 14 additions & 15 deletions testsuite/E12.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@
#: 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", (
Expand Down Expand Up @@ -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
#:
Expand Down Expand Up @@ -225,22 +225,21 @@ def qualify_by_address(
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 = {
Expand Down
40 changes: 17 additions & 23 deletions testsuite/E12not.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
if (
x == (
3
) or
y == 4):
) or y == 4):
pass

y = x == 2 \
Expand All @@ -17,15 +16,14 @@
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

Expand Down Expand Up @@ -109,7 +107,7 @@
'BBB' \
'iii' \
'CCC'

#: W504 W504
abricot = (3 +
4 +
5 + 6)
Expand Down Expand Up @@ -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))
Expand Down Expand Up @@ -184,23 +181,23 @@ def long_function_name(
"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"""):
Expand All @@ -224,22 +221,19 @@ def long_function_name(
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'
Expand Down
18 changes: 9 additions & 9 deletions testsuite/W19.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
#: W191
y = x == 2 \
or x == 3
#: E101 W191
#: E101 W191 W504
if (
x == (
3
Expand All @@ -26,11 +26,11 @@
pass
#:

#: E101 W191
#: E101 W191 W504
if (foo == bar and
baz == frop):
pass
#: E101 W191
#: E101 W191 W504
if (
foo == bar and
baz == frop
Expand All @@ -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))
Expand All @@ -65,23 +65,23 @@ def long_function_name(
"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"""):
Expand All @@ -93,7 +93,7 @@ def long_function_name(


#
#: 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))
Expand Down
Loading