Repository: PyCQA/docformatter
Pull request #96 (2022-08-07 03:42:45)

fix: remove empty line
Merging #53 caused docformatter to remove any blank lines following a line ending with a closing parenthesis ), ], or }.  This fixes that by looking back at the previous line to see if it is a function definition (i.e., starts with 'def' and ends with ':').

* Moved format_docstring() tests to test_format_doctstring.py and converted to pytest.
* Fixed some linting errors.


Patch:
diff --git a/docformatter.py b/docformatter.py
index 2637d0b..bd42169 100755
--- a/docformatter.py
+++ b/docformatter.py
@@ -166,16 +166,17 @@ def format_code(source, **kwargs):
         return source
 
 
-def _format_code(source,
-                 summary_wrap_length=79,
-                 description_wrap_length=72,
-                 pre_summary_newline=False,
-                 make_summary_multi_line=False,
-                 post_description_blank=False,
-                 force_wrap=False,
-                 line_range=None,
-    length_range=None,
+def _format_code(
+    source,
+    summary_wrap_length=79,
+    description_wrap_length=72,
+    pre_summary_newline=False,
     pre_summary_space=False,
+    make_summary_multi_line=False,
+    post_description_blank=False,
+    force_wrap=False,
+    line_range=None,
+    length_range=None,
     strict=True,
 ):
     """Return source code with docstrings formatted."""
@@ -202,7 +203,6 @@ def _format_code(source,
         end,
         line,
     ) in tokenize.generate_tokens(sio.readline):
-
         if (
             token_type == tokenize.STRING
             and token_string.startswith(QUOTE_TYPES)
@@ -220,10 +220,10 @@ def _format_code(source,
                 summary_wrap_length=summary_wrap_length,
                 description_wrap_length=description_wrap_length,
                 pre_summary_newline=pre_summary_newline,
+                pre_summary_space=pre_summary_space,
                 make_summary_multi_line=make_summary_multi_line,
                 post_description_blank=post_description_blank,
                 force_wrap=force_wrap,
-                pre_summary_space=pre_summary_space,
                 strict=strict,
             )
 
@@ -233,27 +233,31 @@ def _format_code(source,
         previous_token_string = token_string
         previous_token_type = token_type
 
-        # prevent empty line between func def and docstring
-        new_line_tokens = {tokenize.NL, tokenize.NEWLINE}
-        if len(modified_tokens) > 2 and modified_tokens[-2][0] == tokenize.OP \
-                and modified_tokens[-1][0] in new_line_tokens \
-                and token_type in new_line_tokens:
+        if (
+            len(modified_tokens) > 2
+            and token_type in {tokenize.NL, tokenize.NEWLINE}
+            and modified_tokens[-2][1] == ":"
+            and modified_tokens[-2][4][:3] == "def"
+        ):
             pass
         else:
             modified_tokens.append(
-                (token_type, token_string, start, end, line))
+                (token_type, token_string, start, end, line)
+            )
 
     return untokenize.untokenize(modified_tokens)
 
 
-def format_docstring(indentation, docstring,
-                     summary_wrap_length=0,
-                     description_wrap_length=0,
-                     pre_summary_newline=False,
-                     make_summary_multi_line=False,
-                     post_description_blank=False,
-                     force_wrap=False,
-                     pre_summary_space=False,
+def format_docstring(
+    indentation,
+    docstring,
+    summary_wrap_length=0,
+    description_wrap_length=0,
+    pre_summary_newline=False,
+    pre_summary_space=False,
+    make_summary_multi_line=False,
+    post_description_blank=False,
+    force_wrap=False,
     strict=True,
 ):
     """Return formatted version of docstring.
@@ -270,6 +274,7 @@ def format_docstring(indentation, docstring,
           on a line by themselves.
     """
     contents, open_quote = strip_docstring(docstring)
+    open_quote = open_quote + " " if pre_summary_space else open_quote
 
     # Skip if there are nested triple double quotes
     if contents.count(QUOTE_TYPES[0]):
@@ -295,32 +300,27 @@ def format_docstring(indentation, docstring,
         initial_indent = (
             indentation if pre_summary_newline else 3 * " " + indentation
         )
-        return '''\
+        pre_summary = "\n" + indentation if pre_summary_newline else ""
+        summary = wrap_summary(
+            normalize_summary(summary),
+            wrap_length=summary_wrap_length,
+            initial_indent=initial_indent,
+            subsequent_indent=indentation,
+        ).lstrip()
+        description = wrap_description(
+            description,
+            indentation=indentation,
+            wrap_length=description_wrap_length,
+            force_wrap=force_wrap,
+            strict=strict,
+        )
+        post_description = "\n" if post_description_blank else ""
+        return f'''\
 {open_quote}{pre_summary}{summary}
 
 {description}{post_description}
 {indentation}"""\
-'''.format(
-            open_quote=open_quote,
-            pre_summary=('\n' + indentation if pre_summary_newline
-                         else ' ' if pre_summary_space 
-                         else ''),
-            summary=wrap_summary(
-                normalize_summary(summary),
-                wrap_length=summary_wrap_length,
-                initial_indent=initial_indent,
-                subsequent_indent=indentation,
-            ).lstrip(),
-            description=wrap_description(
-                description,
-                indentation=indentation,
-                wrap_length=description_wrap_length,
-                force_wrap=force_wrap,
-                strict=strict,
-            ),
-            post_description=("\n" if post_description_blank else ""),
-            indentation=indentation,
-        )
+'''
     else:
         if not make_summary_multi_line:
             return wrap_summary(
@@ -329,18 +329,16 @@ def format_docstring(indentation, docstring,
                 initial_indent=indentation,
                 subsequent_indent=indentation,
             ).strip()
-
-        beginning = '""" ' if pre_summary_space else '"""'
-        ending = "\n" + indentation + '"""'
+        beginning = f"{open_quote}\n{indentation}"
+        ending = f'\n{indentation}"""'
         summary_wrapped = wrap_summary(
             normalize_summary(contents),
             wrap_length=summary_wrap_length,
             initial_indent=indentation,
             subsequent_indent=indentation,
         ).strip()
-        return "{beginning}{summary}{ending}".format(
-            beginning=beginning, summary=summary_wrapped, ending=ending
-        )
+        return f"{beginning}{summary_wrapped}{ending}"
+
 
 def is_probably_beginning_of_sentence(line):
     """Return True if this line begins a new sentence."""
@@ -528,7 +526,6 @@ def strip_docstring(docstring: str) -> Tuple[str, str]:
     (docstring, open_quote) : tuple
         The docstring with the triple quotes removed.
         The opening quote type with single quotes replaced by double quotes.
-
     """
     docstring = docstring.strip()
 
@@ -627,7 +624,6 @@ def remove_section_header(text):
 
     >>> remove_section_header('===\nfoo\nbar\n')
     'foo\nbar\n'
-
     """
     stripped = text.lstrip()
     if not stripped:
@@ -652,6 +648,7 @@ def strip_leading_blank_lines(text):
     found = next(
         (index for index, line in enumerate(split) if line.strip()), 0
     )
+
     return "\n".join(split[found:])
 
 
@@ -674,7 +671,6 @@ def detect_encoding(filename):
             # Check for correctness of encoding.
             with open_with_encoding(filename, encoding) as check_file:
                 check_file.read()
-
         return encoding
     except (SyntaxError, LookupError, UnicodeDecodeError):
         return "latin-1"
@@ -709,7 +705,6 @@ def format_file(filename, args, standard_out):
                 f"after/{filename}",
                 lineterm="",
             )
-
             standard_out.write("\n".join(list(diff) + [""]))
 
     return FormatResult.ok
@@ -722,11 +717,12 @@ def _format_code_with_args(source, args):
         summary_wrap_length=args.wrap_summaries,
         description_wrap_length=args.wrap_descriptions,
         pre_summary_newline=args.pre_summary_newline,
+        pre_summary_space=args.pre_summary_space,
         make_summary_multi_line=args.make_summary_multi_line,
         post_description_blank=args.post_description_blank,
         force_wrap=args.force_wrap,
         line_range=args.line_range,
-        strict=args.non_strict,
+        strict=not args.non_strict,
     )
 
 
@@ -830,9 +826,13 @@ def _main(argv, standard_out, standard_error, standard_in):
         default=bool(flargs.get("pre-summary-newline", False)),
         help="add a newline before the summary of a multi-line docstring",
     )
-    parser.add_argument('--pre-summary-space',
-                        action='store_true',
-                        help='add a space before one-line or the summary of a multi-line docstring')
+    parser.add_argument(
+        "--pre-summary-space",
+        action="store_true",
+        default=bool(flargs.get("pre-summary-space", False)),
+        help="add a space before one-line or the summary of a multi-line "
+        "docstring",
+    )
     parser.add_argument(
         "--make-summary-multi-line",
         action="store_true",
@@ -871,7 +871,7 @@ def _main(argv, standard_out, standard_error, standard_in):
         action="store_true",
         default=bool(flargs.get("non-strict", False)),
         help="don't strictly follow reST syntax to identify lists (see issue "
-             "#67)",
+        "#67)",
     )
     parser.add_argument(
         "--version", action="version", version=f"%(prog)s {__version__}"
@@ -929,12 +929,12 @@ def _format_standard_in(args, parser, standard_out, standard_in):
 
     encoding = None
     source = standard_in.read()
-
     if not isinstance(source, unicode):
         encoding = standard_in.encoding or _get_encoding()
         source = source.decode(encoding)
 
     formatted_source = _format_code_with_args(source, args=args)
+
     if encoding:
         formatted_source = formatted_source.encode(encoding)
 
@@ -1024,6 +1024,7 @@ def _format_files(args, standard_out, standard_error):
         FormatResult.check_failed,
         FormatResult.ok,
     ]
+
     for code in return_codes:
         if outcomes[code]:
             return code
diff --git a/pyproject.toml b/pyproject.toml
index facfdc9..7f445f4 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -122,7 +122,7 @@ output = 'coverage.xml'
 
 [tool.black]
 line-length = 79
-target-version = ['py37', 'py38']
+target-version = ['py36', 'py37', 'py38', 'py39', 'py310']
 include = '\.pyi?$'
 exclude = '''
 /(


Test patch:
diff --git a/test_docformatter.py b/test_docformatter.py
index 982d355..4359df1 100755
--- a/test_docformatter.py
+++ b/test_docformatter.py
@@ -47,329 +47,6 @@ else:
 
 class TestUnits(unittest.TestCase):
 
-    def test_format_docstring(self):
-        self.assertEqual('"""Hello."""',
-                         docformatter.format_docstring('    ', '''
-"""
-
-Hello.
-"""
-'''.strip()))
-
-    def test_format_docstring_with_summary_that_ends_in_quote(self):
-        self.assertEqual('''""""Hello"."""''',
-                         docformatter.format_docstring('    ', '''
-"""
-
-"Hello"
-"""
-'''.strip()))
-
-    def test_format_docstring_with_bad_indentation(self):
-        self.assertEqual('''"""Hello.
-
-    This should be indented but it is not. The
-    next line should be indented too. And
-    this too.
-    """''',
-                         docformatter.format_docstring('    ', '''
-"""Hello.
-
- This should be indented but it is not. The
- next line should be indented too. And
- this too.
-    """
-'''.strip()))
-
-    def test_format_docstring_with_too_much_indentation(self):
-        self.assertEqual('''"""Hello.
-
-    This should be dedented.
-
-    1. This too.
-    2. And this.
-    3. And this.
-    """''',
-                         docformatter.format_docstring('    ', '''
-"""Hello.
-
-        This should be dedented.
-
-        1. This too.
-        2. And this.
-        3. And this.
-
-    """
-'''.strip()))
-
-    def test_format_docstring_with_description_wrapping(self):
-        self.assertEqual('''"""Hello.
-
-    This should be indented but it is not. The next line should be
-    indented too. But this is okay.
-    """''',
-                         docformatter.format_docstring('    ', '''
-"""Hello.
-
-    This should be indented but it is not. The
-    next line should be indented too. But
-    this is okay.
-
-    """
-'''.strip(), description_wrap_length=72))
-
-    def test_format_docstring_should_ignore_doctests(self):
-        docstring = '''"""Hello.
-
-    >>> 4
-    4
-    """'''
-        self.assertEqual(
-            docstring,
-            docformatter.format_docstring('    ',
-                                          docstring,
-                                          description_wrap_length=72))
-
-    def test_format_docstring_should_ignore_doctests_in_summary(self):
-        docstring = '''"""
-    >>> 4
-    4
-
-    """'''
-        self.assertEqual(
-            docstring,
-            docformatter.format_docstring('    ',
-                                          docstring,
-                                          description_wrap_length=72))
-
-    def test_format_docstring_should_maintain_indentation_of_doctest(self):
-        self.assertEqual(
-            '''"""Foo bar bing bang.
-
-        >>> tests = DocTestFinder().find(_TestClass)
-        >>> runner = DocTestRunner(verbose=False)
-        >>> tests.sort(key = lambda test: test.name)
-    """''',
-            docformatter.format_docstring(
-                '    ',
-                docstring='''"""Foo bar bing bang.
-
-        >>> tests = DocTestFinder().find(_TestClass)
-        >>> runner = DocTestRunner(verbose=False)
-        >>> tests.sort(key = lambda test: test.name)
-
-    """''',
-                description_wrap_length=72))
-
-    def test_format_docstring_should_ignore_numbered_lists(self):
-        docstring = '''"""Hello.
-
-    1. This should be indented but it is not. The
-    next line should be indented too. But
-    this is okay.
-    """'''
-        self.assertEqual(
-            docstring,
-            docformatter.format_docstring('    ',
-                                          docstring,
-                                          description_wrap_length=72))
-
-    def test_format_docstring_should_ignore_parameter_lists(self):
-        docstring = '''"""Hello.
-
-    foo - This is a foo. This is a foo. This is a foo. This is a foo. This is.
-    bar - This is a bar. This is a bar. This is a bar. This is a bar. This is.
-    """'''
-        self.assertEqual(
-            docstring,
-            docformatter.format_docstring('    ',
-                                          docstring,
-                                          description_wrap_length=72))
-
-    def test_format_docstring_should_ignore__colon_parameter_lists(self):
-        docstring = '''"""Hello.
-
-    foo: This is a foo. This is a foo. This is a foo. This is a foo. This is.
-    bar: This is a bar. This is a bar. This is a bar. This is a bar. This is.
-    """'''
-        self.assertEqual(
-            docstring,
-            docformatter.format_docstring('    ',
-                                          docstring,
-                                          description_wrap_length=72))
-
-    def test_format_docstring_should_ignore_multi_paragraph(self):
-        docstring = '''"""Hello.
-
-    This should be indented but it is not. The
-    next line should be indented too. But
-    this is okay.
-
-    This should be indented but it is not. The
-    next line should be indented too. But
-    this is okay.
-    """'''
-        self.assertEqual(
-            docstring,
-            docformatter.format_docstring('    ',
-                                          docstring,
-                                          description_wrap_length=72))
-
-    def test_format_docstring_with_trailing_whitespace(self):
-        self.assertEqual('''"""Hello.
-
-    This should be not have trailing whitespace. The
-    next line should not have trailing whitespace either.
-    """''',
-                         docformatter.format_docstring('    ', '''
-"""Hello.\t
-\t
-    This should be not have trailing whitespace. The\t\t\t
-    next line should not have trailing whitespace either.\t
-\t
-    """
-'''.strip()))
-
-    def test_format_docstring_with_no_post_description_blank(self):
-        self.assertEqual('''"""Hello.
-
-    Description.
-    """''',
-                         docformatter.format_docstring('    ', '''
-"""
-
-Hello.
-
-    Description.
-
-
-    """
-'''.strip(), post_description_blank=False))
-
-    def test_format_docstring_with_pre_summary_newline(self):
-        self.assertEqual('''"""
-    Hello.
-
-    Description.
-    """''',
-                         docformatter.format_docstring('    ', '''
-"""
-
-Hello.
-
-    Description.
-
-
-    """
-'''.strip(), pre_summary_newline=True))
-
-    def test_format_docstring_with_empty_docstring(self):
-        self.assertEqual('""""""',
-                         docformatter.format_docstring('    ', '""""""'))
-
-    def test_format_docstring_with_no_period(self):
-        self.assertEqual('"""Hello."""',
-                         docformatter.format_docstring('    ', '''
-"""
-
-Hello
-"""
-'''.strip()))
-
-    def test_format_docstring_with_single_quotes(self):
-        self.assertEqual('"""Hello."""',
-                         docformatter.format_docstring('    ', """
-'''
-
-Hello.
-'''
-""".strip()))
-
-    def test_format_docstring_with_single_quotes_multi_line(self):
-        self.assertEqual('''
-    """Return x factorial.
-
-    This uses math.factorial.
-    """
-'''.strip(),
-            docformatter.format_docstring('    ', """
-    '''
-    Return x factorial.
-
-    This uses math.factorial.
-    '''
-""".strip()))
-
-    def test_format_docstring_with_wrap(self):
-        # This function uses `random` so make sure each run of this test is
-        # repeatable.
-        random.seed(0)
-
-        min_line_length = 50
-        for max_length in range(min_line_length, 100):
-            for num_indents in range(20):
-                indentation = ' ' * num_indents
-                formatted_text = indentation + docformatter.format_docstring(
-                    indentation=indentation,
-                    docstring=generate_random_docstring(
-                        max_word_length=min_line_length // 2),
-                    summary_wrap_length=max_length)
-
-                for line in formatted_text.split('\n'):
-                    # It is not the formatter's fault if a word is too long to
-                    # wrap.
-                    if len(line.split()) > 1:
-                        self.assertLessEqual(len(line), max_length)
-
-    def test_format_docstring_with_weird_indentation_and_punctuation(self):
-        self.assertEqual('''
-    """Creates and returns four was awakens to was created tracked ammonites
-    was the fifty, arithmetical four was pyrotechnic to pyrotechnic physicists.
-
-    `four' falsified x falsified ammonites
-    to awakens to. `created' to ancestor was four to x dynamo to was
-    four ancestor to physicists().
-    """
-'''.strip(),
-            docformatter.format_docstring('    ', '''
-    """Creates and returns four was awakens to was created tracked
-       ammonites was the fifty, arithmetical four was pyrotechnic to
-       pyrotechnic physicists. `four' falsified x falsified ammonites
-       to awakens to. `created' to ancestor was four to x dynamo to was
-       four ancestor to physicists().
-    """
-'''.strip(), summary_wrap_length=79))
-
-    def test_format_docstring_should_leave_list_alone(self):
-        docstring = '''"""
-    one
-    two
-    three
-    four
-    five
-    six
-    seven
-    eight
-    nine
-    ten
-    eleven
-    """'''
-        self.assertEqual(
-            docstring,
-            docformatter.format_docstring('    ', docstring, strict=False))
-
-    def test_format_docstring_should_underlined_summaries_alone(self):
-        docstring = '''"""
-    Foo bar
-    -------
-
-    This is more.
-
-    """'''
-        self.assertEqual(
-            docstring,
-            docformatter.format_docstring('    ', docstring))
-
     def test_format_code(self):
         self.assertEqual(
             '''\
@@ -944,33 +621,6 @@ def foo():\r
 ''',
             docformatter.format_code(input))
 
-    def test_unwrap_summary(self):
-        self.assertEqual(
-            'This is a sentence.',
-            docformatter.unwrap_summary('This \n\t is\na sentence.'))
-
-    def test_force_wrap(self):
-        self.assertEqual(('''\
-"""num_iterations is the number of updates -
-    instead of a better definition of
-    convergence."""\
-'''),
-                         docformatter.format_docstring('    ', '''\
-"""
-num_iterations is the number of updates - instead of a better definition of convergence.
-"""\
-''', description_wrap_length=50, summary_wrap_length=50, force_wrap=True))
-
-    def test_format_docstring_make_summary_multi_line(self):
-        self.assertEqual(('''\
-"""
-    This one-line docstring will be multi-line.
-    """\
-'''),
-                         docformatter.format_docstring('    ', '''\
-"""This one-line docstring will be multi-line"""\
-''', make_summary_multi_line=True))
-
     def test__format_code_additional_empty_line_before_doc(self):
         args = {'summary_wrap_length': 79,
                 'description_wrap_length': 72,
@@ -1019,12 +669,6 @@ num_iterations is the number of updates - instead of a better definition of conv
             test_exclude_nothing = list(docformatter.find_py_files(sources, True))
             self.assertEqual(test_exclude_nothing, ['/root/folder_one/one.py', '/root/folder_one/folder_three/three.py',
                                                     '/root/folder_two/two.py'])
-    def test_format_docstring_pre_summary_space(self):
-        self.assertEqual(('''""" This one-line docstring will have a leading space."""'''),
-                         docformatter.format_docstring('    ', '''\
-"""This one-line docstring will have a leading space."""\
-''', pre_summary_space=True))
-
 
 class TestSystem(unittest.TestCase):
 
diff --git a/tests/__init__.py b/tests/__init__.py
index e69de29..9f49e72 100644
--- a/tests/__init__.py
+++ b/tests/__init__.py
@@ -0,0 +1,53 @@
+# pylint: skip-file
+# type: ignore
+#
+#       tests._init__.py is part of the docformatter project
+#
+# Copyright (C) 2012-2019 Steven Myint
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+# Standard Library Imports
+import random
+import string
+
+
+def generate_random_docstring(
+    max_indentation_length=32,
+    max_word_length=20,
+    max_words=50,
+):
+    """Generate single-line docstring."""
+    if random.randint(0, 1):
+        words = []
+    else:
+        words = [
+            generate_random_word(random.randint(0, max_word_length))
+            for _ in range(random.randint(0, max_words))
+        ]
+
+    indentation = random.randint(0, max_indentation_length) * " "
+    quote = '"""' if random.randint(0, 1) else "'''"
+    return quote + indentation + " ".join(words) + quote
+
+
+def generate_random_word(word_length):
+    return "".join(random.sample(string.ascii_letters, word_length))
diff --git a/tests/test_format_docstring.py b/tests/test_format_docstring.py
new file mode 100644
index 0000000..4839251
--- /dev/null
+++ b/tests/test_format_docstring.py
@@ -0,0 +1,499 @@
+# pylint: skip-file
+# type: ignore
+#
+#       tests.test_format_docstring.py is part of the docformatter project
+#
+# Copyright (C) 2012-2019 Steven Myint
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+"""Module for testing the format_docstring() function."""
+
+# Standard Library Imports
+import random
+
+# Third Party Imports
+import pytest
+
+# docformatter Package Imports
+import docformatter
+
+# docformatter Local Imports
+from . import generate_random_docstring
+
+
+class TestFormatDocstring:
+    """Class for testing format_docstring() with no arguments."""
+
+    @pytest.mark.unit
+    def test_format_docstring(self):
+        """Return one-line docstring."""
+        assert '"""Hello."""' == docformatter.format_docstring(
+            "    ",
+            '''
+"""
+
+Hello.
+"""
+'''.strip(),
+        )
+
+    @pytest.mark.unit
+    def test_format_docstring_with_summary_that_ends_in_quote(self):
+        """Return one-line docstring with period after quote."""
+        assert '''""""Hello"."""''' == docformatter.format_docstring(
+            "    ",
+            '''
+"""
+
+"Hello"
+"""
+'''.strip(),
+        )
+
+    @pytest.mark.unit
+    def test_format_docstring_with_bad_indentation(self):
+        """Add spaces to indentation when too few."""
+        assert '''"""Hello.
+
+    This should be indented but it is not. The
+    next line should be indented too. And
+    this too.
+    """''' == docformatter.format_docstring(
+            "    ",
+            '''
+"""Hello.
+
+ This should be indented but it is not. The
+ next line should be indented too. And
+ this too.
+    """
+'''.strip(),
+        )
+
+    @pytest.mark.unit
+    def test_format_docstring_with_too_much_indentation(self):
+        """Remove spaces from indentation when too many."""
+        assert '''"""Hello.
+
+    This should be dedented.
+
+    1. This too.
+    2. And this.
+    3. And this.
+    """''' == docformatter.format_docstring(
+            "    ",
+            '''
+"""Hello.
+
+        This should be dedented.
+
+        1. This too.
+        2. And this.
+        3. And this.
+
+    """
+'''.strip(),
+        )
+
+    @pytest.mark.unit
+    def test_format_docstring_with_trailing_whitespace(self):
+        """Remove trailing white space."""
+        assert '''"""Hello.
+
+    This should be not have trailing whitespace. The
+    next line should not have trailing whitespace either.
+    """''' == docformatter.format_docstring(
+            "    ",
+            '''
+"""Hello.\t
+\t
+    This should be not have trailing whitespace. The\t\t\t
+    next line should not have trailing whitespace either.\t
+\t
+    """
+'''.strip(),
+        )
+
+    @pytest.mark.unit
+    def test_format_docstring_with_empty_docstring(self):
+        """Do nothing with empty docstring."""
+        assert '""""""' == docformatter.format_docstring("    ", '""""""')
+
+    @pytest.mark.unit
+    def test_format_docstring_with_no_period(self):
+        """Add period to end of one-line and summary line."""
+        assert '"""Hello."""' == docformatter.format_docstring(
+            "    ",
+            '''
+"""
+
+Hello
+"""
+'''.strip(),
+        )
+
+    @pytest.mark.unit
+    def test_format_docstring_with_single_quotes(self):
+        """Replace single triple quotes with triple double quotes."""
+        assert '"""Hello."""' == docformatter.format_docstring(
+            "    ",
+            """
+'''
+
+Hello.
+'''
+""".strip(),
+        )
+
+    @pytest.mark.unit
+    def test_format_docstring_with_single_quotes_multi_line(self):
+        """Replace single triple quotes with triple double quotes."""
+        assert '''
+    """Return x factorial.
+
+    This uses math.factorial.
+    """
+'''.strip() == docformatter.format_docstring(
+            "    ",
+            """
+    '''
+    Return x factorial.
+
+    This uses math.factorial.
+    '''
+""".strip(),
+        )
+
+    @pytest.mark.unit
+    def test_format_docstring_leave_underlined_summaries_alone(self):
+        docstring = '''"""
+    Foo bar
+    -------
+
+    This is more.
+
+    """'''
+        assert docstring == docformatter.format_docstring("    ", docstring)
+
+
+class TestFormatLists:
+    """Class for testing format_docstring() with lists in the docstring."""
+
+    @pytest.mark.unit
+    def test_format_docstring_should_ignore_numbered_lists(self):
+        """Ignore lists beginning with numbers."""
+        docstring = '''"""Hello.
+
+    1. This should be indented but it is not. The
+    next line should be indented too. But
+    this is okay.
+    """'''
+        assert docstring == docformatter.format_docstring(
+            "    ", docstring, description_wrap_length=72
+        )
+
+    @pytest.mark.unit
+    def test_format_docstring_should_ignore_parameter_lists(self):
+        """Ignore lists beginning with <word> -."""
+        docstring = '''"""Hello.
+
+    foo - This is a foo. This is a foo. This is a foo. This is a foo. This is.
+    bar - This is a bar. This is a bar. This is a bar. This is a bar. This is.
+    """'''
+        assert docstring == docformatter.format_docstring(
+            "    ", docstring, description_wrap_length=72
+        )
+
+    @pytest.mark.unit
+    def test_format_docstring_should_ignore_colon_parameter_lists(self):
+        """Ignore lists beginning with <word>:"""
+        docstring = '''"""Hello.
+
+    foo: This is a foo. This is a foo. This is a foo. This is a foo. This is.
+    bar: This is a bar. This is a bar. This is a bar. This is a bar. This is.
+    """'''
+        assert docstring == docformatter.format_docstring(
+            "    ", docstring, description_wrap_length=72
+        )
+
+    @pytest.mark.unit
+    def test_format_docstring_should_leave_list_alone(self):
+        docstring = '''"""
+    one
+    two
+    three
+    four
+    five
+    six
+    seven
+    eight
+    nine
+    ten
+    eleven
+    """'''
+        assert docstring == docformatter.format_docstring(
+            "    ", docstring, strict=False
+        )
+
+
+class TestFormatWrap:
+    """Class for testing format_docstring() when requesting line wrapping."""
+
+    @pytest.mark.unit
+    def test_unwrap_summary(self):
+        """Remove newline and tab characters."""
+        assert "This is a sentence." == docformatter.unwrap_summary(
+            "This \n\tis\na sentence."
+        )
+
+    @pytest.mark.unit
+    def test_format_docstring_with_wrap(self):
+        """Wrap docstring."""
+        # This function uses `random` so make sure each run of this test is
+        # repeatable.
+        random.seed(0)
+
+        min_line_length = 50
+        for max_length in range(min_line_length, 100):
+            for num_indents in range(20):
+                indentation = " " * num_indents
+                formatted_text = indentation + docformatter.format_docstring(
+                    indentation=indentation,
+                    docstring=generate_random_docstring(
+                        max_word_length=min_line_length // 2
+                    ),
+                    summary_wrap_length=max_length,
+                )
+
+                for line in formatted_text.split("\n"):
+                    # It is not the formatter's fault if a word is too long to
+                    # wrap.
+                    if len(line.split()) > 1:
+                        assert len(line) <= max_length
+
+    @pytest.mark.unit
+    def test_format_docstring_with_weird_indentation_and_punctuation(self):
+        """"""
+        assert '''
+    """Creates and returns four was awakens to was created tracked ammonites
+    was the fifty, arithmetical four was pyrotechnic to pyrotechnic physicists.
+
+    `four' falsified x falsified ammonites
+    to awakens to. `created' to ancestor was four to x dynamo to was
+    four ancestor to physicists().
+    """
+        '''.strip() == docformatter.format_docstring(
+            "    ",
+            '''
+            """Creates and returns four was awakens to was created tracked
+               ammonites was the fifty, arithmetical four was pyrotechnic to
+               pyrotechnic physicists. `four' falsified x falsified ammonites
+               to awakens to. `created' to ancestor was four to x dynamo to was
+               four ancestor to physicists().
+            """
+        '''.strip(),
+            summary_wrap_length=79,
+        )
+
+    @pytest.mark.unit
+    def test_format_docstring_with_description_wrapping(self):
+        """Wrap description at 72 characters."""
+        assert '''"""Hello.
+
+    This should be indented but it is not. The next line should be
+    indented too. But this is okay.
+    """''' == docformatter.format_docstring(
+            "    ",
+            '''
+"""Hello.
+
+    This should be indented but it is not. The
+    next line should be indented too. But
+    this is okay.
+
+    """
+'''.strip(),
+            description_wrap_length=72,
+        )
+
+    @pytest.mark.unit
+    def test_format_docstring_should_ignore_multi_paragraph(self):
+        """Ignore multiple paragraphs in elaborate description."""
+        docstring = '''"""Hello.
+
+    This should be indented but it is not. The
+    next line should be indented too. But
+    this is okay.
+
+    This should be indented but it is not. The
+    next line should be indented too. But
+    this is okay.
+    """'''
+        assert docstring == docformatter.format_docstring(
+            "    ", docstring, description_wrap_length=72
+        )
+
+    @pytest.mark.unit
+    def test_format_docstring_should_ignore_doctests(self):
+        """Leave doctests alone."""
+        docstring = '''"""Hello.
+
+    >>> 4
+    4
+    """'''
+        assert docstring == docformatter.format_docstring(
+            "    ", docstring, description_wrap_length=72
+        )
+
+    @pytest.mark.unit
+    def test_format_docstring_should_ignore_doctests_in_summary(self):
+        """Leave doctests alone if they're in the summary."""
+        docstring = '''"""
+    >>> 4
+    4
+
+    """'''
+        assert docstring == docformatter.format_docstring(
+            "    ", docstring, description_wrap_length=72
+        )
+
+    @pytest.mark.unit
+    def test_format_docstring_should_maintain_indentation_of_doctest(self):
+        """Don't change indentation of doctest lines."""
+        assert '''"""Foo bar bing bang.
+
+        >>> tests = DocTestFinder().find(_TestClass)
+        >>> runner = DocTestRunner(verbose=False)
+        >>> tests.sort(key = lambda test: test.name)
+    """''' == docformatter.format_docstring(
+            "    ",
+            docstring='''"""Foo bar bing bang.
+
+        >>> tests = DocTestFinder().find(_TestClass)
+        >>> runner = DocTestRunner(verbose=False)
+        >>> tests.sort(key = lambda test: test.name)
+
+    """''',
+            description_wrap_length=72,
+        )
+
+    @pytest.mark.unit
+    def test_force_wrap(self):
+        """Force even lists to be wrapped."""
+        assert (
+            (
+                '''\
+"""num_iterations is the number of updates -
+    instead of a better definition of
+    convergence."""\
+'''
+            )
+            == docformatter.format_docstring(
+                "    ",
+                '''\
+"""
+num_iterations is the number of updates - instead of a better definition of convergence.
+"""\
+''',
+                description_wrap_length=50,
+                summary_wrap_length=50,
+                force_wrap=True,
+            )
+        )
+
+
+class TestFormatStyleOptions:
+    """Class for testing format_docstring() when requesting style options."""
+
+    @pytest.mark.unit
+    def test_format_docstring_with_no_post_description_blank(self):
+        """Remove blank lines before closing triple quotes."""
+        assert '''"""Hello.
+
+    Description.
+    """''' == docformatter.format_docstring(
+            "    ",
+            '''
+"""
+
+Hello.
+
+    Description.
+
+
+    """
+'''.strip(),
+            post_description_blank=False,
+        )
+
+    @pytest.mark.unit
+    def test_format_docstring_with_pre_summary_newline(self):
+        """Remove blank line before summary."""
+        assert '''"""
+    Hello.
+
+    Description.
+    """''' == docformatter.format_docstring(
+            "    ",
+            '''
+"""
+
+Hello.
+
+    Description.
+
+
+    """
+'''.strip(),
+            pre_summary_newline=True,
+        )
+
+    @pytest.mark.unit
+    def test_format_docstring_make_summary_multi_line(self):
+        assert (
+            (
+                '''\
+"""
+    This one-line docstring will be multi-line.
+    """\
+'''
+            )
+            == docformatter.format_docstring(
+                "    ",
+                '''\
+"""This one-line docstring will be multi-line"""\
+''',
+                make_summary_multi_line=True,
+            )
+        )
+
+    @pytest.mark.unit
+    def test_format_docstring_pre_summary_space(self):
+        """"""
+        assert (
+            '''""" This one-line docstring will have a leading space."""'''
+        ) == docformatter.format_docstring(
+            "    ",
+            '''\
+"""This one-line docstring will have a leading space."""\
+''',
+            pre_summary_space=True,
+        )