Repository: PyCQA/docformatter (2023-04-24 ~ 2023-05-18)

problem_statement: List index out of range when formatting docstring with URL
I'm on newly-released docformatter 1.6.3. When I run on a docstring of the following format, I get an `IndexError: list index out of range` at `File ".../lib/python3.11/site-packages/docformatter/syntax.py", line 341, in do_split_description
    if _lines[-1] == "":`

```python
def method():
    """This method doesn't do anything.

    https://example.com/this-is-just-a-long-url/designed-to-trigger/the-wrapping-of-the-description
    """
```
```bash
$ docformatter test.py
Traceback (most recent call last):
  File "/Users/nate/testenv/bin/docformatter", line 8, in <module>
    sys.exit(main())
             ^^^^^^
  File "/Users/nate/testenv/lib/python3.11/site-packages/docformatter/__main__.py", line 71, in main
    return _main(
           ^^^^^^
  File "/Users/nate/testenv/lib/python3.11/site-packages/docformatter/__main__.py", line 61, in _main
    return formator.do_format_files()
           ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/nate/testenv/lib/python3.11/site-packages/docformatter/format.py", line 158, in do_format_files
    result = self._do_format_file(filename)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/nate/testenv/lib/python3.11/site-packages/docformatter/format.py", line 191, in _do_format_file
    formatted_source = self._do_format_code(source)
                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/nate/testenv/lib/python3.11/site-packages/docformatter/format.py", line 236, in _do_format_code
    _code = self._format_code(source)
            ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/nate/testenv/lib/python3.11/site-packages/docformatter/format.py", line 301, in _format_code
    token_string = self._do_format_docstring(
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/nate/testenv/lib/python3.11/site-packages/docformatter/format.py", line 393, in _do_format_docstring
    return self._do_format_multiline_docstring(
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/nate/testenv/lib/python3.11/site-packages/docformatter/format.py", line 496, in _do_format_multiline_docstring
    description = _syntax.wrap_description(
                  ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/nate/testenv/lib/python3.11/site-packages/docformatter/syntax.py", line 519, in wrap_description
    lines = do_split_description(text, indentation, wrap_length)
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/nate/testenv/lib/python3.11/site-packages/docformatter/syntax.py", line 341, in do_split_description
    if _lines[-1] == "":
       ~~~~~~^^^^
IndexError: list index out of range
```

If I add any text between the summary line and the URL, then this goes away:

```python
def method():
    """This docstring does not trigger an error.

    a
    https://example.com/this-is-just-a-long-url/designed-to-trigger/the-wrapping-of-the-description
    """
```
date: 2023-04-24
patch: diff --git a/src/docformatter/syntax.py b/src/docformatter/syntax.py
index 9afbeb6..567a995 100644
--- a/src/docformatter/syntax.py
+++ b/src/docformatter/syntax.py
@@ -338,8 +338,9 @@ def do_split_description(
                 )
             )
 
-            if _lines[-1] == "":
-                _lines.pop(-1)
+            with contextlib.suppress(IndexError):
+                if _lines[-1] == "":
+                    _lines.pop(-1)
 
             # Add the URL.
             _lines.append(

test_patch: diff --git a/tests/test_format_docstring.py b/tests/test_format_docstring.py
index df1d764..92bc507 100644
--- a/tests/test_format_docstring.py
+++ b/tests/test_format_docstring.py
@@ -1298,6 +1298,43 @@ num_iterations is the number of updates - instead of a better definition of conv
             INDENTATION, docstring.strip()
         )
 
+    @pytest.mark.unit
+    @pytest.mark.parametrize(
+        "args",
+        [["--wrap-descriptions", "72", ""]],
+    )
+    def test_format_docstring_with_only_link_in_description(
+        self,
+        test_args,
+        args,
+    ):
+        """No index error when only link in long description.
+
+        See issue #189.
+        """
+        uut = Formatter(
+            test_args,
+            sys.stderr,
+            sys.stdin,
+            sys.stdout,
+        )
+
+        docstring = '''\
+    """This method doesn't do anything.
+
+    https://example.com/this-is-just-a-long-url/designed-to-trigger/the-wrapping-of-the-description
+    """
+'''
+
+        assert '''\
+"""This method doesn\'t do anything.
+
+    https://example.com/this-is-just-a-long-url/designed-to-trigger/the-wrapping-of-the-description
+    """\
+''' == uut._do_format_docstring(
+            INDENTATION, docstring.strip()
+        )
+
     @pytest.mark.unit
     @pytest.mark.parametrize(
         "args",


----

problem_statement: [regression] removing whitespace below shebang
```python
#!/usr/bin/env python

"""a.py"""
```

`docformatter==1.6.3` (also may possibly be `1.6.2` per https://github.com/PyCQA/docformatter/issues/186) is removing whitespace below a shebang.

`> docformatter a.py` leads to:

```python
#!/usr/bin/env python
"""a.py"""
```

`docformatter==1.5.1` didn't use to do that.
date: 2023-04-24
patch: diff --git a/src/docformatter/format.py b/src/docformatter/format.py
index d8bbbb9..88df426 100644
--- a/src/docformatter/format.py
+++ b/src/docformatter/format.py
@@ -528,14 +528,20 @@ class Formatter:
         """
         for _idx, _token in enumerate(modified_tokens):
             if _token[0] == 3:
-                # Remove newline between variable definition and docstring.
                 j = 1
-                while modified_tokens[_idx - j][
-                    4
-                ] == "\n" and not modified_tokens[_idx - j - 1][
-                    4
-                ].strip().endswith(
-                    '"""'
+
+                # Remove newline between variable definition and docstring
+                # unless is separating docstring from:
+                #     * A previous docstring.
+                #     * The file's shebang.
+                while (
+                    modified_tokens[_idx - j][4] == "\n"
+                    and not (
+                        modified_tokens[_idx - j - 1][4]
+                        .strip()
+                        .endswith('"""')
+                    )
+                    and not modified_tokens[_idx - j - 1][4].startswith("#!/")
                 ):
                     modified_tokens.pop(_idx - j)
                     j += 1
diff --git a/src/docformatter/strings.py b/src/docformatter/strings.py
index c42d2ee..1a4e076 100644
--- a/src/docformatter/strings.py
+++ b/src/docformatter/strings.py
@@ -104,8 +104,7 @@ def normalize_summary(summary: str) -> str:
     # Remove trailing whitespace
     summary = summary.rstrip()
 
-    # Add period at end of sentence and capitalize the first word of the
-    # summary.
+    # Add period at end of sentence.
     if (
         summary
         and (summary[-1].isalnum() or summary[-1] in ['"', "'"])
@@ -114,10 +113,11 @@ def normalize_summary(summary: str) -> str:
         summary += "."
 
     with contextlib.suppress(IndexError):
-        # Look for underscores in the first word, this would typically
-        # indicate the first word is a variable name or some other
-        # non-standard English word.
-        if "_" not in summary.split(" ", 1)[0]:
+        # Look for underscores, periods in the first word, this would typically
+        # indicate the first word is a variable name, file name, or some other
+        # non-standard English word.  If none of these exist capitalize the
+        # first word of the summary.
+        if all(char not in summary.split(" ", 1)[0] for char in ["_", "."]):
             summary = summary[0].upper() + summary[1:]
 
     return summary

test_patch: diff --git a/tests/test_format_code.py b/tests/test_format_code.py
index 132c304..73aa9f1 100644
--- a/tests/test_format_code.py
+++ b/tests/test_format_code.py
@@ -853,9 +853,9 @@ def foo():\r
         )
 
         assert (
-            '\n\n\ndef my_func():\n"""Summary of my function."""\npass'
+            'def my_func():\n"""Summary of my function."""\npass'
             == uut._do_format_code(
-                '\n\n\ndef my_func():\n\n"""Summary of my function."""\npass'
+                'def my_func():\n\n"""Summary of my function."""\npass'
             )
         )
 
@@ -1107,7 +1107,7 @@ class TestClass:
         test_args,
         args,
     ):
-        """Strip newlines between module variable defintiion and docstring."""
+        """Strip newlines between module variable definition and docstring."""
         uut = Formatter(
             test_args,
             sys.stderr,
@@ -1157,6 +1157,34 @@ def test_wps3_process_step_io_data_or_href():
 '''
         assert docstring == uut._do_format_code(docstring)
 
+    @pytest.mark.unit
+    @pytest.mark.parametrize("args", [[""]])
+    def test_format_code_keep_newline_after_shebang(
+        self,
+        test_args,
+        args,
+    ):
+        """Do not remove newlines following the shebang.
+
+        See issue #187.
+        """
+        uut = Formatter(
+            test_args,
+            sys.stderr,
+            sys.stdin,
+            sys.stdout,
+        )
+
+        docstring = '''\
+#!/usr/bin/env python
+
+"""a.py."""
+'''
+        assert docstring == uut._do_format_code('''\
+#!/usr/bin/env python
+
+"""a.py"""
+''')
 
 class TestFormatCodeRanges:
     """Class for testing _format_code() with the line_range or length_range


----

problem_statement: Short anonymous hyperlink references get messed up
docformatter doesn't correctly recognize the short form of anonymous hyperlink references (`__ URI`) as a special block, and merges them into the preceding paragraph:

```diff
     """[...]
 
     This graphics format generates terminal escape codes that transfer
-    PNG data to a TTY using the `kitty graphics protocol`__.
-
-    __ https://sw.kovidgoyal.net/kitty/graphics-protocol/
+    PNG data to a TTY using the `kitty graphics protocol
+    `__. __ https://sw.kovidgoyal.net/kitty/graphics-protocol/
     """
```

This breaks the reST, and makes Sphinx unable to correctly process the reference.

Note that this only affects this shorter form; the long form (`.. __: URI`) is unaffected. The following variation (correctly) remains unchanged by docformatter:

```
    """[...]

    This graphics format generates terminal escape codes that transfer
    PNG data to a TTY using the `kitty graphics protocol`__.

    .. __: https://sw.kovidgoyal.net/kitty/graphics-protocol/
    """
```

Bisecting brought up 7898b69f49d43a0a0ac63a8e92afac974a478383 as the first "bad" commit, which is exactly v1.6.0. The earlier 917fed92248d1fb91a1f4da61b5aaaec7367fe23 also changed the output already, but in a slightly different way and it was probably rewritten anyways.
date: 2023-05-18
patch: diff --git a/docs/source/conf.py b/docs/source/conf.py
--- a/docs/source/conf.py
+++ b/docs/source/conf.py
@@ -1,4 +1,5 @@
 # Configuration file for the Sphinx documentation builder.
+# noop comment for filter
 #
 # For the full list of built-in configuration values, see the documentation:
 # https://www.sphinx-doc.org/en/master/usage/configuration.html