Skip to content
Open
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
34 changes: 29 additions & 5 deletions src/docformatter/classify.py
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,7 @@ def is_code_line(token: tokenize.TokenInfo) -> bool:
bool
True if the token is a code line, False otherwise.
"""
if token.type == tokenize.NAME and not (
if (token.type == tokenize.NAME or token.string == "...") and not (
token.line.strip().startswith("def ")
or token.line.strip().startswith("async ")
or token.line.strip().startswith("class ")
Expand Down Expand Up @@ -317,6 +317,15 @@ def is_f_string(token: tokenize.TokenInfo, prev_token: tokenize.TokenInfo) -> bo
if PY312:
if tokenize.FSTRING_MIDDLE in [token.type, prev_token.type]:
return True
elif any(
[
token.string.startswith('f"""'),
prev_token.string.startswith('f"""'),
token.string.startswith("f'''"),
prev_token.string.startswith("f'''"),
]
):
return True

return False

Expand Down Expand Up @@ -432,7 +441,7 @@ def is_newline_continuation(
if (
token.type in (tokenize.NEWLINE, tokenize.NL)
and token.line.strip() in prev_token.line.strip()
and token.line != "\n"
and token.line not in {"\n", "\r\n"}
):
return True

Expand Down Expand Up @@ -460,14 +469,29 @@ def is_string_variable(
# TODO: The AWAIT token is removed in Python 3.13 and later. Only Python 3.9
# seems to generate the AWAIT token, so we can safely remove the check for it when
# support for Python 3.9 is dropped in April 2026.
try:
if sys.version_info <= (3, 12):
_token_types = (tokenize.AWAIT, tokenize.OP)
except AttributeError:
_token_types = (tokenize.OP,) # type: ignore
else:
_token_types = (tokenize.OP,)

if prev_token.type in _token_types and (
'= """' in token.line or token.line in prev_token.line
):
return True

return False


def is_docstring_at_end_of_file(tokens: list[tokenize.TokenInfo], index: int) -> bool:
"""Determine if the docstring is at the end of the file."""
for i in range(index + 1, len(tokens)):
tok = tokens[i]
if tok.type not in (
tokenize.NL,
tokenize.NEWLINE,
tokenize.DEDENT,
tokenize.ENDMARKER,
):
return False

return True
16 changes: 15 additions & 1 deletion src/docformatter/format.py
Original file line number Diff line number Diff line change
Expand Up @@ -260,11 +260,22 @@ def _get_class_docstring_newlines(
The number of newlines to insert after the docstring.
"""
j = index + 1
indention_level = tokens[index].start[1]

# The docstring is followed by a comment.
if tokens[j].string.startswith("#"):
return 0

while j < len(tokens):
if tokens[j].type in (tokenize.NL, tokenize.NEWLINE):
j += 1
continue

if tokens[j].start[1] < indention_level:
return 2

break

return 1


Expand Down Expand Up @@ -379,7 +390,10 @@ def _get_newlines_by_type(
int
The number of newlines to insert after the docstring.
"""
if _classify.is_module_docstring(tokens, index):
if _classify.is_docstring_at_end_of_file(tokens, index):
# print("End of file")
return 0
elif _classify.is_module_docstring(tokens, index):
# print("Module")
return _get_module_docstring_newlines(black)
elif _classify.is_class_docstring(tokens, index):
Expand Down
80 changes: 73 additions & 7 deletions tests/_data/string_files/do_format_code.toml
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ source='''
expected='''
CONST = 123
"""Docstring for CONST."""

'''

[class_docstring]
Expand All @@ -60,7 +59,6 @@ expected='''
:cvar test_int: a class attribute.
.. py:method:: big_method()
"""

'''

[newline_class_variable]
Expand All @@ -87,7 +85,6 @@ expected='''

test_var2 = 1
"""This is a second class variable docstring."""

'''

[class_attribute_wrap]
Expand All @@ -102,7 +99,6 @@ expected='''class TestClass:
test_int = 1
"""This is a very, very, very long docstring that should really be
reformatted nicely by docformatter."""

'''

[newline_outside_docstring]
Expand Down Expand Up @@ -364,7 +360,6 @@ expected='''class Foo:

More stuff.
"""

'''

[class_empty_lines_2]
Expand Down Expand Up @@ -688,7 +683,6 @@ class TestClass:
:cvar test_int: a class attribute.
..py:method:: big_method()
"""

'''

[issue_139_2]
Expand Down Expand Up @@ -1134,7 +1128,6 @@ expected='''
#!/usr/bin/env python

"""a.py."""

'''

[issue_203]
Expand Down Expand Up @@ -1167,3 +1160,76 @@ expected='''def foo(bar):
Description.
"""
'''

[two_lines_between_stub_classes]
source='''class Foo:
"""Foo class."""
class Bar:
"""Bar class."""
'''
expected='''class Foo:
"""Foo class."""


class Bar:
"""Bar class."""
'''

[two_lines_between_stub_classes_with_preceding_comment]
source='''class Foo:
"""Foo class."""

# A comment for class Bar
class Bar:
"""Bar class."""
'''
expected='''class Foo:
"""Foo class."""


# A comment for class Bar
class Bar:
"""Bar class."""
'''

[ellipses_is_code_line]
source='''class Foo:
def bar() -> str:
"""Bar."""

...

def baz() -> None:
"""Baz."""

...
'''
expected='''class Foo:
def bar() -> str:
"""Bar."""
...

def baz() -> None:
"""Baz."""
...
'''

[do_not_break_f_string_double_quotes]
source='''foo = f"""
bar
"""
'''
expected='''foo = f"""
bar
"""
'''

[do_not_break_f_string_single_quotes]
source="""foo = f'''
bar
'''
"""
expected="""foo = f'''
bar
'''
"""
6 changes: 3 additions & 3 deletions tests/_data/string_files/format_functions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -169,11 +169,11 @@ expected = 1

[get_newlines_by_type_module_docstring]
source = '"""Module docstring."""'
expected = 1
expected = 0

[get_newlines_by_type_module_docstring_black]
source = '"""Module docstring."""'
expected = 2
expected = 0

[get_newlines_by_type_class_docstring]
source = '''
Expand All @@ -195,7 +195,7 @@ expected = 0
source = '''x = 1
"""Docstring for x."""
'''
expected = 1
expected = 0

[get_num_rows_columns]
token = [5, " ", [3, 10], [3, 40], ''' This is
Expand Down
5 changes: 5 additions & 0 deletions tests/formatter/test_do_format_code.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,11 @@
("issue_187", NO_ARGS),
("issue_203", NO_ARGS),
("issue_243", NO_ARGS),
("two_lines_between_stub_classes", NO_ARGS),
("two_lines_between_stub_classes_with_preceding_comment", NO_ARGS),
("ellipses_is_code_line", NO_ARGS),
("do_not_break_f_string_double_quotes", NO_ARGS),
("do_not_break_f_string_single_quotes", NO_ARGS),
],
)
def test_do_format_code(test_key, test_args, args):
Expand Down
Loading