Attribute docstring + top-level def: 'two blank lines' rule (12.1.1) only fires in the first half of the token stream (buggy loop bound, ruff/Black ping-pong)
Nadie ha tomado este issue todavía.
Evaluación
- Dificultad
- 2/5
- Tiempo estimado
- 1-3 horas
- Aptitud para principiantes
- 88/100
Línea de trabajo
Comienza en src/docformatter/format.py, en _get_attribute_docstring_newlines, y compara su comportamiento con las reproducciones proporcionadas a.py y b.py. Ejecuta docformatter --diff en ambos archivos y, a continuación, verifica que el docstring del atributo conserve dos líneas en blanco antes del def de nivel superior independientemente del código precedente y que ya no entre en un ciclo con ruff format.
Escrito por el modelo de indexación a partir del texto del issue.
Descripción
Summary
docformatter collapses the two blank lines between a module-level attribute docstring and a following top-level def/class down to one blank line — but only when the docstring happens to sit in the second half of the file's token stream. Two structurally identical snippets are formatted differently purely based on how much code precedes them.
Since ruff format (and Black) always require two blank lines before a top-level definition, this also produces an infinite correction loop between the two tools.
Environment
- docformatter 1.7.8 (also reproduced on current
master, commitd5c7b77) - Python 3.14, Linux
- Default options (reproduced with plain
docformatter --diff file.py, no config needed)
Minimal reproduction
a.py (attribute docstring early in the token stream, two blank lines before def):
x = 1
"""Docstring."""
def f():
pass
b.py — identical structure, only preceded by filler statements:
_filler_1 = 1
_filler_2 = 2
_filler_3 = 3
_filler_4 = 4
_filler_5 = 5
_filler_6 = 6
_filler_7 = 7
_filler_8 = 8
_filler_9 = 9
_filler_10 = 10
_filler_11 = 11
_filler_12 = 12
x = 1
"""Docstring."""
def f():
pass
$ docformatter --diff a.py
# (no output - file left unchanged, two blank lines kept) ✔ expected
$ docformatter --diff b.py
--- before/b.py
+++ after/b.py
@@ -13,6 +13,5 @@
x = 1
"""Docstring."""
-
def f():
pass
And the ping-pong with ruff:
$ docformatter --in-place b.py # one blank line left
$ ruff format b.py # two blank lines restored
$ docformatter --in-place b.py # one blank line again ... forever
Actual vs. expected
-
Actual (
b.py): one blank line beforedef f():. -
Expected: two blank lines, i.e. the same result as
a.py, per docformatter's own rule in_get_attribute_docstring_newlines(src/docformatter/format.py,masterline 263):docformatter_12.1.1: Two blank lines if followed by top-level class or function definition.
This also matches PEP 8 and what ruff format/Black enforce.
Root cause
In _get_attribute_docstring_newlines (loop at master line 263 / v1.7.8 line 262):
_num_tokens = len(tokens)
_offset = 2
for i in range(index + 2, _num_tokens - index - 1):
if tokens[i].line == "\n":
_offset += 1
else:
break
if tokens[index + _offset].line.startswith("class") or tokens[
index + _offset
].line.startswith("def"):
return 2
return 1
The upper bound _num_tokens - index - 1 shrinks as index (the docstring's token index) grows. Once the docstring is past the middle of the token stream (index + 2 >= _num_tokens - index - 1), the range is empty, _offset stays 2, and the class/def check inspects tokens[index + 2] (a blank NL line) instead of the following definition line — so the function returns 1 instead of 2.
Concrete numbers for the repro above:
| file | total tokens | docstring index | range | result |
|---|---|---|---|---|
a.py |
19 | 4 | range(6, 14) valid |
returns 2 ✔ |
b.py |
67 | 52 | range(54, 14) empty |
returns 1 ✘ |
I verified locally that changing the bound to the full token list makes both files behave identically (two blank lines kept, and no more conflict with ruff format):
- for i in range(index + 2, _num_tokens - index - 1):
+ for i in range(index + 2, _num_tokens):
(This was tested by patching the installed v1.7.8 and a checkout of master; happy to turn it into a PR if the fix direction looks right.)
Related issues
Same symptom class (blank-line ping-pong with ruff/Black) but different code paths:
- #350 — module docstring + top-level
class(_get_module_docstring_newlines), closed - #354 — nested
def/classinside functions (_get_function_docstring_newlines)
- Lenguaje dominante
- Python
- Estrellas
- 598
- Forks
- 93
- Merge medio
- 12 d 10 h
- PR fusionados (30 d)
- 1
Preparar el entorno
Aún no hemos revisado los archivos de configuración de este proyecto. Empieza por su README y consulta nuestra guía para la primera contribución para los pasos generales.
Primeros pasos
- Lee el issue completo y luego la guía de contribución del proyecto.
- Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
- Haz un fork del repositorio y trabaja en una rama.
- Abre un pull request que haga referencia al número del issue.
Más de PyCQA/docformatter
-
fresh
Dificultad 3/5 1-2 días Aptitud para principiantes 78/100
PyCQA/docformatter#385 ·
-
C: convention P: bug U: high
Dificultad 3/5 1-2 días Aptitud para principiantes 74/100
PyCQA/docformatter#367 · 1 comentario ·
-
1.7.8 rewrites the contents of a non-docstring triple-quoted string, silently changing its valueAbiertoC: convention P: bug U: high
Dificultad 3/5 1-2 días Aptitud para principiantes 72/100
PyCQA/docformatter#366 ·
-
fresh
Dificultad 3/5 1-2 días Aptitud para principiantes 68/100
PyCQA/docformatter#354 · 3 reacciones ·
-
C: stakeholder P: enhancement U: low
Dificultad 3/5 1-2 días Aptitud para principiantes 65/100
PyCQA/docformatter#346 ·
Todos los issues de PyCQA/docformatter
Issues similares
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 78/100
Los mantenedores suelen responder en 1 día
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 85/100
kornia/kornia#5263 · 1 comentario ·
Los mantenedores suelen responder en 1 día
-
Metadata correction for W16-5400Abiertoapproved correction metadata
Dificultad 1/5 Menos de una hora Aptitud para principiantes 88/100
acl-org/acl-anthology#10133 · 1 comentario ·
Los mantenedores suelen responder en 1 día
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 78/100
BasedHardware/omi#20084 ·
Los mantenedores suelen responder en 1 día
-
bug needs-acceptance wg/evaluation-quality
Dificultad 2/5 1-3 horas Aptitud para principiantes 76/100
vllm-project/semantic-router#4424 ·
Los mantenedores suelen responder en 1 día