Skip to content

Commit

Permalink
fix: handle blank lines after class definition properly (#142)
Browse files Browse the repository at this point in the history
* fix: no longer remove blank lines when no class docstring

* test: add tests for class and blank lines
  • Loading branch information
weibullguy authored Dec 29, 2022
1 parent ee4bfe3 commit 2e543b4
Show file tree
Hide file tree
Showing 3 changed files with 167 additions and 7 deletions.
67 changes: 67 additions & 0 deletions .sourcery.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# 🪄 This is your project's Sourcery configuration file.

# You can use it to get Sourcery working in the way you want, such as
# ignoring specific refactorings, skipping directories in your project,
# or writing custom rules.

# 📚 For a complete reference to this file, see the documentation at
# https://docs.sourcery.ai/Configuration/Project-Settings/

# This file was auto-generated by Sourcery on 2022-12-28 at 22:39.

version: '1' # The schema version of this config file

ignore: # A list of paths or files which Sourcery will ignore.
- .git
- venv
- .venv
- env
- .env
- .tox

# rule_settings:
# enable:
# - default
# disable: [] # A list of rule IDs Sourcery will never suggest.
# rule_types:
# - refactoring
# - suggestion
# - comment
# python_version: '3.9' # A string specifying the lowest Python version your project supports. Sourcery will not suggest refactorings requiring a higher Python version.

# rules: # A list of custom rules Sourcery will include in its analysis.
# - id: no-print-statements
# description: Do not use print statements in the test directory.
# pattern: print(...)
# replacement:
# condition:
# explanation:
# paths:
# include:
# - test
# exclude:
# - conftest.py
# tests: []
# tags: []

# rule_tags: {} # Additional rule tags.

# metrics:
# quality_threshold: 25.0

# github:
# labels: []
# ignore_labels:
# - sourcery-ignore
# request_review: author
# sourcery_branch: sourcery/{base_branch}

# clone_detection:
# min_lines: 3
# min_duplicates: 2
# identical_clones_only: false

# proxy:
# url:
# ssl_certs_file:
# no_ssl_verify: false
41 changes: 35 additions & 6 deletions src/docformatter/format.py
Original file line number Diff line number Diff line change
Expand Up @@ -313,24 +313,26 @@ def _format_code(

# If the current token is a newline, the previous token was a
# newline or a comment, and these two sequential newlines
# follow a function definition, ignore the blank line.
# follow a function or method definition, ignore the blank
# line before the docstring.
if (
len(modified_tokens) <= 2
or token_type not in {tokenize.NL, tokenize.NEWLINE}
or modified_tokens[-1][0]
not in {tokenize.NL, tokenize.NEWLINE}
or modified_tokens[-2][1] != ":"
and modified_tokens[-2][0] != tokenize.COMMENT
or not modified_tokens[-2][4]
.lstrip()
.startswith(("def", "class"))
or not modified_tokens[-2][4].lstrip().startswith(("def"))
):
modified_tokens.append(
(token_type, token_string, start, end, line)
)
modified_tokens = self._do_remove_blank_lines_after_method(
modified_tokens
)
modified_tokens = self._do_remove_blank_lines_before_class(
modified_tokens
)

return untokenize.untokenize(modified_tokens)
except tokenize.TokenError:
Expand Down Expand Up @@ -450,7 +452,8 @@ def _do_format_docstring(
).strip()
return f"{beginning}{summary_wrapped}{ending}"

def _do_remove_blank_lines_after_method(self, modified_tokens):
@staticmethod
def _do_remove_blank_lines_after_method(modified_tokens):
"""Remove blank lines after method docstring.
Parameters
Expand All @@ -467,12 +470,38 @@ def _do_remove_blank_lines_after_method(self, modified_tokens):
with contextlib.suppress(IndexError):
if (
modified_tokens[-1][4] == "\n"
and modified_tokens[-2][4].strip() == '"""'
and modified_tokens[-2][4].lstrip().startswith('"""')
and modified_tokens[-5][4].lstrip().startswith("def")
):
modified_tokens.pop(-1)
return modified_tokens

@staticmethod
def _do_remove_blank_lines_before_class(modified_tokens):
"""Remove blank lines before class docstring.
If there is no class docstring, leave any blank lines as is.
Parameters
----------
modified_tokens: list
The list of tokens created from the docstring.
Returns
-------
modified_tokens: list
The list of tokens with any blank lines following a method
docstring removed.
"""
with contextlib.suppress(IndexError):
if (
modified_tokens[-3][4] == "\n"
and modified_tokens[-2][4].lstrip().startswith('"""')
and modified_tokens[-6][4].lstrip().startswith("class")
):
modified_tokens.pop(-3)
return modified_tokens

def _do_strip_docstring(self, docstring: str) -> Tuple[str, str]:
"""Return contents of docstring and opening quote type.
Expand Down
66 changes: 65 additions & 1 deletion tests/test_format_code.py
Original file line number Diff line number Diff line change
Expand Up @@ -944,6 +944,70 @@ class TestClass:
'''
)

@pytest.mark.unit
@pytest.mark.parametrize("args", [[""]])
def test_format_code_class_docstring_remove_blank_line(
self, test_args, args
):
"""Remove blank line before class docstring.
See issue #139.
"""
uut = Formatter(
test_args,
sys.stderr,
sys.stdin,
sys.stdout,
)

docstring = '''\
class TestClass:
"""This is a class docstring.
:cvar test_int: a class attribute.
..py.method: big_method()
"""
'''
assert docstring == uut._do_format_code(
'''\
class TestClass:
"""This is a class docstring.
:cvar test_int: a class attribute.
..py.method: big_method()
"""
'''
)

@pytest.mark.unit
@pytest.mark.parametrize("args", [[""]])
def test_format_code_class_docstring_keep_blank_line(
self, test_args, args
):
"""Keep blank line after class definition if there is no docstring.
See issue #139.
"""
uut = Formatter(
test_args,
sys.stderr,
sys.stdin,
sys.stdout,
)

docstring = """\
class TestClass:
variable = 1
"""
assert docstring == uut._do_format_code(
"""\
class TestClass:
variable = 1
"""
)

@pytest.mark.unit
@pytest.mark.parametrize("args", [[""]])
def test_format_code_strip_blank_line_after_method_docstring(
Expand Down Expand Up @@ -989,7 +1053,7 @@ def test_method(self):
pass
'''
)
)


class TestFormatCodeRanges:
Expand Down

0 comments on commit 2e543b4

Please sign in to comment.