Hacktoberfest 2026:メンテナが10月に向けて印を付けた、オープンで初心者向けの issue。 Hacktoberfest の issue を見る

docformatter deadlock with ruff-format: blank lines around nested definitions

オープン
#354 コメント 0 件 リアクション 3 件 担当者 0 名 GitHub で見る

まだ誰も着手していません。

評価

難易度
3/5
見積もり時間
1〜2日
初心者へのやさしさ
68/100
issue の種類
バグ
明瞭さ
明確に書かれている
活発さ
静か
技術スタック
python
領域
tooling

調査の方向性

example.py と example_class.py にある最小限の再現例から始め、次に示されている設定で docformatter と ruff format を実行して、両方のループを再現します。ネストされた 1 行の docstring の後にある空行を docformatter がどのように分類するかを追跡し、2 つのケースの回帰テストを追加します。繰り返し実行しても収束し、どちらのツールももう一方の変更を元に戻さなければ完了です。

索引モデルが issue の本文から書いたものです。

説明

fresh

Summary

docformatter and ruff-format enter an infinite correction loop in pre-commit when
test functions contain nested def or class definitions whose only body is a one-line
docstring. The tools disagree on whether a blank line should follow the nested definition,
so each tool's output is the other tool's input and pre-commit never converges.

Two distinct sub-issues are present:

  • Loop A — nested def with one-liner docstring: docformatter removes the blank line
    after it; ruff-format adds it back.
  • Loop B — nested class with one-liner docstring (blank = true in config): docformatter
    adds an extra blank line; ruff-format removes it.

Versions

Tool Version
docformatter (pip / pre-commit hook) 1.7.8
ruff-format (pre-commit hook astral-sh/ruff-pre-commit) v0.15.9
Python 3.10.11

Configuration

pyproject.toml:

[tool.docformatter]
recursive = true
wrap-summaries = 120
wrap-descriptions = 120
blank = true

.pre-commit-config.yaml (relevant hooks):

- repo: https://github.com/PyCQA/docformatter
  rev: v1.7.8
  hooks:
    - id: docformatter
      language_version: python3.10
      additional_dependencies: [tomli]
      args: ["--in-place"]

- repo: https://github.com/astral-sh/ruff-pre-commit
  rev: v0.15.9
  hooks:
    - id: ruff-check
      args: ["--fix"]
    - id: ruff-format

Minimal reproduction — Loop A

Create example.py:

def outer() -> None:
    """Outer function."""

    def inner() -> None:
        """One-liner docstring."""

    next_statement = 1

Run docformatter:

docformatter --in-place --blank example.py

Result — blank line removed:

def outer() -> None:
    """Outer function."""

    def inner() -> None:
        """One-liner docstring."""

    next_statement = 1  # ← blank line gone

Run ruff-format on that output:

ruff format example.py

Result — blank line restored (back to original):

def outer() -> None:
    """Outer function."""

    def inner() -> None:
        """One-liner docstring."""

    next_statement = 1  # ← blank line back

Running both again repeats the cycle indefinitely.

Minimal reproduction — Loop B (blank = true)

Create example_class.py:

def outer() -> None:
    """Outer function."""

    class Inner:
        """One-liner class docstring."""

    @some_decorator
    class Another:
        """Another class."""

With blank = true, docformatter inserts an extra blank line after the one-liner
class docstring (before @some_decorator), producing two consecutive blank lines.
ruff-format then removes the extra one. Each tool undoes the other.

Root cause

Loop A

docformatter interprets the blank line between the closing """ of inner()'s docstring
and next_statement as being inside inner()'s function body and removes it as a
PEP 257 D202 violation ("no blank lines allowed after function docstring").

The blank line is not inside inner() — it is in the outer scope, separating two
statements. docformatter misattributes it because the last token of inner()'s body is
the closing """ of a one-liner docstring on the same line as the opening """, with no
other body statements.

ruff-format (Black-compatible) correctly requires the blank line between the nested
definition and the following statement per E301 / PEP 8.

Loop B

blank = true causes docformatter to insert a blank line at the end of one-liner class
docstrings when they are followed by another definition. Combined with the blank line
already present, this produces two blank lines (E303), which ruff-format then reduces
back to one.

Expected behaviour

docformatter should not remove the blank line that follows a nested function definition
whose only body is a one-liner docstring. That blank line belongs to the enclosing
scope, not to the nested function.

Workaround

Remove blank = true from [tool.docformatter] to mitigate Loop B.
Loop A has no configuration-level workaround; the only option is to exclude the affected
files from docformatter or avoid the nested-def-with-one-liner-docstring pattern in test
code.

主要言語
Python
スター
598
フォーク
93
平均マージ
12日 10時間
マージ済み PR(30日)
1

環境構築

このプロジェクトの環境構築ファイルはまだ確認していません。まず README を読み、一般的な手順ははじめてのコントリビューションガイドを参照してください。

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

PyCQA/docformatter のほかの issue

PyCQA/docformatter の issue をすべて見る

似ている issue

Python の issue をもっと見る

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。