Skip to content

docformatter incorrectly reformats non-docstring string literals inside function call arguments #344

@736-c41-2c1-e464fc974

Description

@736-c41-2c1-e464fc974

Version: docformatter 1.7.8 (not present in: 1.7.7)

Config (pyproject.toml):

[tool.docformatter]
recursive = true
in-place = true
black = true

Description:

docformatter modifies string literals that are not docstrings when they appear as arguments to function calls (e.g. textwrap.dedent()). This corrupts valid, black-formatted code.

Steps to reproduce:

Start with the following black-formatted file (bug.py):

from dataclasses import dataclass
from textwrap import dedent
import pytest


@dataclass
class Finding:
    line_number: int
    secret: str


@pytest.mark.parametrize(
    "arg1, arg2, arg3",
    [
        (
            ".env",
            dedent("""KEY_ONE=value1
                   KEY_TWO=value2
                   KEY_THREE=value3."""),
            [
                Finding(line_number=2, secret="KEY_ONE=value1"),
                Finding(
                    line_number=3,
                    secret="KEY_TWO=value2",
                ),
            ],
        ),
        (
            "",
            dedent("""
                -----BEGIN EXAMPLE BLOCK-----
                c29tZWJhc2U2NGVuY29kZWRjb250ZW50
                -----END EXAMPLE BLOCK-----
                """),
            [
                Finding(line_number=2, secret="-----BEGIN EXAMPLE BLOCK-----"),
            ],
        ),
    ],
)
def test_fun(arg1, arg2, arg3):
    pass

Run:

docformatter --config pyproject.toml --diff bug.py

Actual output (docformatter rewrites the file to):

@dataclass
class Finding:
    line_number: int
    secret: str
@pytest.mark.parametrize(        # ← blank lines between class and decorator removed
    "arg1, arg2, arg3",
    [
        (
            ".env",
            dedent(\               # ← backslash continuation injected
                   """KEY_ONE=value1 KEY_TWO=value2 KEY_THREE=value3."""

                                                                        ),  # ← trailing blank line + mangled indentation
            ...
        ),
        (
            "",
            dedent(\               # ← same corruption on multiline string
                   """-----BEGIN EXAMPLE BLOCK-----
                c29tZWJhc2U2NGVuY29kZWRjb250ZW50
                -----END EXAMPLE BLOCK-----
                """

                   ),
            ...
        ),
    ],
)
def test_fun(arg1, arg2, arg3):
    pass

Expected behavior:

docformatter should not touch string literals that are not docstrings. Arguments passed to dedent() (or any other function) are data, not documentation, and must be left unchanged. The black-formatted input is the correct output.

Observed issues (summary):

  1. Blank lines eaten — the two blank lines between the @dataclass class body and the next decorator are removed, violating PEP 8 and black's output.
  2. Backslash continuation injecteddedent("""...""") is rewritten to dedent(\↵ """..."""), which changes the AST and breaks indentation-sensitive code like textwrap.dedent.
  3. Trailing blank lines added inside the argument — a spurious blank line is inserted before the closing ) of the dedent() call.
  4. Indentation of closing parenthesis mangled — the ) is placed at a column that matches neither black's style nor the original.

Issues 2–4 together change runtime behaviour: textwrap.dedent receives a differently-indented string, so the result of the call changes.

Workaround:

None found. The --skip-string-normalization flag does not apply here; there is no per-call-site escape hatch.

Metadata

Metadata

Assignees

No one assigned

    Labels

    C: conventionRelates to docstring format conventionP: bugPEP 257 violation or existing functionality that doesn't work as documentedU: high

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions