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
19 changes: 9 additions & 10 deletions src/docformatter/format.py
Original file line number Diff line number Diff line change
Expand Up @@ -1071,12 +1071,6 @@ def _do_format_docstring( # noqa PLR0911
# Something probably isn't right with the splitting.
return docstring

# Compensate for textwrap counting each tab in indentation as 1
# character.
tab_compensation = indentation.count("\t") * (self.args.tab_width - 1)
self.args.wrap_summaries -= tab_compensation
self.args.wrap_descriptions -= tab_compensation

if description:
return self._do_format_multiline_docstring(
indentation,
Expand Down Expand Up @@ -1114,12 +1108,16 @@ def _do_format_oneline_docstring(
str
The formatted docstring.
"""
# Keep tab compensation local to this docstring.
wrap_length = self.args.wrap_summaries - indentation.count("\t") * (
self.args.tab_width - 1
)
if self.args.make_summary_multi_line:
beginning = f"{open_quote}\n{indentation}"
ending = f'\n{indentation}"""'
summary_wrapped = _wrappers.do_wrap_summary(
_strings.do_normalize_summary(contents, self.args.non_cap),
wrap_length=self.args.wrap_summaries,
wrap_length=wrap_length,
initial_indent=indentation,
subsequent_indent=indentation,
).strip()
Expand All @@ -1129,7 +1127,7 @@ def _do_format_oneline_docstring(
open_quote
+ _strings.do_normalize_summary(contents, self.args.non_cap)
+ '"""',
wrap_length=self.args.wrap_summaries,
wrap_length=wrap_length,
initial_indent=indentation,
subsequent_indent=indentation,
).strip()
Expand Down Expand Up @@ -1165,6 +1163,7 @@ def _do_format_multiline_docstring(
str
The formatted docstring.
"""
tab_compensation = indentation.count("\t") * (self.args.tab_width - 1)
# Compensate for triple quotes by temporarily prepending 3 spaces.
# This temporary prepending is undone below.
initial_indent = (
Expand All @@ -1173,14 +1172,14 @@ def _do_format_multiline_docstring(
pre_summary = "\n" + indentation if self.args.pre_summary_newline else ""
summary = _wrappers.do_wrap_summary(
_strings.do_normalize_summary(summary, self.args.non_cap),
wrap_length=self.args.wrap_summaries,
wrap_length=self.args.wrap_summaries - tab_compensation,
initial_indent=initial_indent,
subsequent_indent=indentation,
).lstrip()
description = _wrappers.do_wrap_description(
description,
indentation=indentation,
wrap_length=self.args.wrap_descriptions,
wrap_length=self.args.wrap_descriptions - tab_compensation,
force_wrap=self.args.force_wrap,
strict=self.args.non_strict,
rest_sections=self.args.rest_section_adorns,
Expand Down
103 changes: 103 additions & 0 deletions tests/formatter/test_tab_wrapping_state.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
"""Keep tab-width compensation local to each formatted docstring."""

import ast
import copy
import sys

import pytest

from docformatter import Formatter


@pytest.mark.integration
@pytest.mark.parametrize(
"args",
[
["--tab-width", "4", "--wrap-summaries", "40", "--wrap-descriptions", "40", ""],
[
"--tab-width",
"8",
"--wrap-summaries",
"40",
"--wrap-descriptions",
"40",
"--make-summary-multi-line",
"",
],
[
"--tab-width",
"4",
"--wrap-summaries",
"40",
"--wrap-descriptions",
"40",
"--black",
"",
],
["--tab-width", "4", "--wrap-summaries", "0", "--wrap-descriptions", "40", ""],
["--tab-width", "4", "--wrap-summaries", "40", "--wrap-descriptions", "0", ""],
["--tab-width", "4", "--wrap-summaries", "0", "--wrap-descriptions", "0", ""],
],
)
@pytest.mark.parametrize("indentation", ["\t", "\t\t"])
@pytest.mark.parametrize("with_description", [False, True])
def test_tab_wrapping_is_independent_of_previous_docstrings(
args, test_args, indentation, with_description
):
"""Repeated docstrings and files must not consume the configured wrap widths."""
widths = (test_args.wrap_summaries, test_args.wrap_descriptions)
summary = "Return the requested value with several words in the summary."
docstring = '"""' + summary
if with_description:
docstring += (
"\n\n"
+ indentation
+ "Describe the returned value with enough words to require wrapping."
+ "\n"
+ indentation
)
docstring += '"""'
source = f"def function_0():\n{indentation}{docstring}\n{indentation}pass\n\n"
original_args = copy.copy(test_args)
formatter = Formatter(test_args, sys.stderr, sys.stdin, sys.stdout)

expected = formatter._do_format_code(source)
assert (test_args.wrap_summaries, test_args.wrap_descriptions) == widths
ast.parse(expected)
assert all(
line.startswith(indentation) for line in expected.splitlines()[1:] if line
)
if all(width > 0 for width in widths):
assert all(
len(line.expandtabs(test_args.tab_width)) <= min(widths)
for line in expected.splitlines()
)
if widths[0] == 0:
assert summary in expected
if with_description and widths[1] == 0:
assert (
"Describe the returned value with enough words to require wrapping."
in expected
)
assert formatter._do_format_code(expected) == expected
# The same Formatter is reused when processing multiple files.
assert formatter._do_format_code(source) == expected

repeated_source = "".join(
source.replace("function_0", f"function_{i}") for i in range(13)
)
repeated_expected = "".join(
expected.replace("function_0", f"function_{i}") for i in range(13)
)
formatted = formatter._do_format_code(repeated_source)
assert formatted == repeated_expected
ast.parse(formatted)
assert formatter._do_format_code(formatted) == formatted
assert (test_args.wrap_summaries, test_args.wrap_descriptions) == widths

# A preceding tab-indented file must not affect a space-indented file.
space_source = source.replace("\t", " ")
fresh_formatter = Formatter(original_args, sys.stderr, sys.stdin, sys.stdout)
assert formatter._do_format_code(space_source) == fresh_formatter._do_format_code(
space_source
)
Loading