diff --git a/docs/intro.rst b/docs/intro.rst index a7187a27..3f0af034 100644 --- a/docs/intro.rst +++ b/docs/intro.rst @@ -292,6 +292,8 @@ This is the current list of error and warning codes: +------------+----------------------------------------------------------------------+ | E242 (*) | tab after ',' | +------------+----------------------------------------------------------------------+ +| E243 (*) | whitespace around attribute access | ++------------+----------------------------------------------------------------------+ +------------+----------------------------------------------------------------------+ | E251 | unexpected spaces around keyword / parameter equals | +------------+----------------------------------------------------------------------+ @@ -418,7 +420,7 @@ This is the current list of error and warning codes: **(*)** In the default configuration, the checks **E121**, **E123**, **E126**, **E133**, -**E226**, **E241**, **E242**, **E704**, **W503**, **W504** and **W505** are ignored +**E226**, **E241**, **E242**, **E243**, **E704**, **W503**, **W504** and **W505** are ignored because they are not rules unanimously accepted, and `PEP 8`_ does not enforce them. Please note that if the option ``--ignore=errors`` is used, the default configuration will be overridden and ignore only the check(s) you skip. diff --git a/pycodestyle.py b/pycodestyle.py index 868e79d5..bcc47eb8 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -803,6 +803,52 @@ def whitespace_before_parameters(logical_line, tokens): prev_end = end +def _is_attribute_access_operand(token_type, text): + return ( + ( + token_type in {tokenize.NAME, tokenize.NUMBER, tokenize.STRING} and + not keyword.iskeyword(text) and + not keyword.issoftkeyword(text) + ) or + text in '}])' + ) + + +@register_check +def whitespace_around_attribute_access(logical_line, tokens): + r"""Avoid extraneous whitespace around attribute access. + + Note: these checks are disabled by default + + Okay: spam.eggs + E243: spam .eggs + E243: spam. eggs + """ + prev_type, prev_text, __, prev_end, __ = tokens[0] + for index in range(1, len(tokens) - 1): + token_type, text, start, end, __ = tokens[index] + next_type, __, next_start, __, __ = tokens[index + 1] + is_attribute_dot = ( + token_type == tokenize.OP and + text == '.' and + start[0] == prev_end[0] and + _is_attribute_access_operand(prev_type, prev_text) + ) + if is_attribute_dot: + if start != prev_end: + yield prev_end, "E243 whitespace before '.'" + if ( + next_type == tokenize.NAME and + next_start[0] == end[0] and + next_start != end + ): + yield end, "E243 whitespace after '.'" + + prev_type = token_type + prev_text = text + prev_end = end + + @register_check def whitespace_around_operator(logical_line): r"""Avoid extraneous whitespace around an operator. diff --git a/testing/data/E24.py b/testing/data/E24.py index 36fb4aa7..9edddd98 100644 --- a/testing/data/E24.py +++ b/testing/data/E24.py @@ -11,3 +11,19 @@ more_spaces = [a, b, ef, +h, c, -d] +#: E243:1:5 +spam .eggs +#: E243:1:6 +spam. eggs +#: E243:1:5 E243:1:7 +spam . eggs +#: Okay +spam.eggs +#: Okay +from . import eggs +from ..spam import eggs +#: Okay +( + spam + .eggs +)