Repository: PyCQA/docformatter (2022-12-28 ~ 2023-04-22)

problem_statement: Major change in 1.5.1 removes empty lines after class definitions
I updated to docformatter 1.5.1 from 1.5.0 expecting no major changes to take place. However, I found that running `docformatter` removes *all* empty lines following class definitions, *even when there is no docstring present*.

I'm not sure if this is intentional, but I expected docformatter to only modify places where there is a docstring present.

(I ran into this face-first because Django generates its migration files with an empty line after class definition, so a patch version upgrade resulted in `171 files changed, 113 insertions(+), 286 deletions(-)` for me. Half of that is this issue, the other half that apparently the max line length was slightly shortened?)

### Reproduction:

Format this file with `docformatter==1.5.0` and `docformatter==1.5.1`:

```python
class Test:

    default = True
```

1.5.0 will leave the file alone, while 1.5.1 will output

```
--- before/test.py
+++ after/test.py
@@ -1,3 +1,2 @@
 class Test:
-
     default = True
```

This change appears to be related to #130.
date: 2022-12-28
patch: diff --git a/.sourcery.yaml b/.sourcery.yaml
new file mode 100644
index 0000000..1a767dc
--- /dev/null
+++ b/.sourcery.yaml
@@ -0,0 +1,67 @@
+# 🪄 This is your project's Sourcery configuration file.
+
+# You can use it to get Sourcery working in the way you want, such as
+# ignoring specific refactorings, skipping directories in your project,
+# or writing custom rules.
+
+# 📚 For a complete reference to this file, see the documentation at
+# https://docs.sourcery.ai/Configuration/Project-Settings/
+
+# This file was auto-generated by Sourcery on 2022-12-28 at 22:39.
+
+version: '1'  # The schema version of this config file
+
+ignore: # A list of paths or files which Sourcery will ignore.
+- .git
+- venv
+- .venv
+- env
+- .env
+- .tox
+
+# rule_settings:
+#  enable:
+#  - default
+#  disable: []  # A list of rule IDs Sourcery will never suggest.
+#  rule_types:
+#  - refactoring
+#  - suggestion
+#  - comment
+#  python_version: '3.9' # A string specifying the lowest Python version your project supports. Sourcery will not suggest refactorings requiring a higher Python version.
+
+# rules:  # A list of custom rules Sourcery will include in its analysis.
+# - id: no-print-statements
+#   description: Do not use print statements in the test directory.
+#   pattern: print(...)
+#   replacement:
+#   condition:
+#   explanation:
+#   paths:
+#     include:
+#     - test
+#     exclude:
+#     - conftest.py
+#   tests: []
+#   tags: []
+
+# rule_tags: {} # Additional rule tags.
+
+# metrics:
+#   quality_threshold: 25.0
+
+# github:
+#   labels: []
+#   ignore_labels:
+#   - sourcery-ignore
+#   request_review: author
+#   sourcery_branch: sourcery/{base_branch}
+
+# clone_detection:
+#   min_lines: 3
+#   min_duplicates: 2
+#   identical_clones_only: false
+
+# proxy:
+#   url:
+#   ssl_certs_file:
+#   no_ssl_verify: false
diff --git a/src/docformatter/format.py b/src/docformatter/format.py
index c28d24c..5195b91 100644
--- a/src/docformatter/format.py
+++ b/src/docformatter/format.py
@@ -313,7 +313,8 @@ class Formatter:
 
                 # If the current token is a newline, the previous token was a
                 # newline or a comment, and these two sequential newlines
-                # follow a function definition, ignore the blank line.
+                # follow a function or method definition, ignore the blank
+                # line before the docstring.
                 if (
                     len(modified_tokens) <= 2
                     or token_type not in {tokenize.NL, tokenize.NEWLINE}
@@ -321,9 +322,7 @@ class Formatter:
                     not in {tokenize.NL, tokenize.NEWLINE}
                     or modified_tokens[-2][1] != ":"
                     and modified_tokens[-2][0] != tokenize.COMMENT
-                    or not modified_tokens[-2][4]
-                    .lstrip()
-                    .startswith(("def", "class"))
+                    or not modified_tokens[-2][4].lstrip().startswith(("def"))
                 ):
                     modified_tokens.append(
                         (token_type, token_string, start, end, line)
@@ -331,6 +330,9 @@ class Formatter:
                     modified_tokens = self._do_remove_blank_lines_after_method(
                         modified_tokens
                     )
+                    modified_tokens = self._do_remove_blank_lines_before_class(
+                        modified_tokens
+                    )
 
             return untokenize.untokenize(modified_tokens)
         except tokenize.TokenError:
@@ -450,7 +452,8 @@ class Formatter:
                 ).strip()
                 return f"{beginning}{summary_wrapped}{ending}"
 
-    def _do_remove_blank_lines_after_method(self, modified_tokens):
+    @staticmethod
+    def _do_remove_blank_lines_after_method(modified_tokens):
         """Remove blank lines after method docstring.
 
         Parameters
@@ -467,12 +470,38 @@ class Formatter:
         with contextlib.suppress(IndexError):
             if (
                 modified_tokens[-1][4] == "\n"
-                and modified_tokens[-2][4].strip() == '"""'
+                and modified_tokens[-2][4].lstrip().startswith('"""')
                 and modified_tokens[-5][4].lstrip().startswith("def")
             ):
                 modified_tokens.pop(-1)
         return modified_tokens
 
+    @staticmethod
+    def _do_remove_blank_lines_before_class(modified_tokens):
+        """Remove blank lines before class docstring.
+
+        If there is no class docstring, leave any blank lines as is.
+
+        Parameters
+        ----------
+        modified_tokens: list
+            The list of tokens created from the docstring.
+
+        Returns
+        -------
+        modified_tokens: list
+            The list of tokens with any blank lines following a method
+            docstring removed.
+        """
+        with contextlib.suppress(IndexError):
+            if (
+                modified_tokens[-3][4] == "\n"
+                and modified_tokens[-2][4].lstrip().startswith('"""')
+                and modified_tokens[-6][4].lstrip().startswith("class")
+            ):
+                modified_tokens.pop(-3)
+        return modified_tokens
+
     def _do_strip_docstring(self, docstring: str) -> Tuple[str, str]:
         """Return contents of docstring and opening quote type.
 

test_patch: diff --git a/tests/test_format_code.py b/tests/test_format_code.py
index cb4b5ff..f12931e 100644
--- a/tests/test_format_code.py
+++ b/tests/test_format_code.py
@@ -944,6 +944,70 @@ class TestClass:
 '''
         )
 
+    @pytest.mark.unit
+    @pytest.mark.parametrize("args", [[""]])
+    def test_format_code_class_docstring_remove_blank_line(
+        self, test_args, args
+    ):
+        """Remove blank line before class docstring.
+
+        See issue #139.
+        """
+        uut = Formatter(
+            test_args,
+            sys.stderr,
+            sys.stdin,
+            sys.stdout,
+        )
+
+        docstring = '''\
+class TestClass:
+    """This is a class docstring.
+
+    :cvar test_int: a class attribute.
+    ..py.method: big_method()
+    """
+'''
+        assert docstring == uut._do_format_code(
+            '''\
+class TestClass:
+
+    """This is a class docstring.
+    :cvar test_int: a class attribute.
+    ..py.method: big_method()
+    """
+'''
+        )
+
+    @pytest.mark.unit
+    @pytest.mark.parametrize("args", [[""]])
+    def test_format_code_class_docstring_keep_blank_line(
+        self, test_args, args
+    ):
+        """Keep blank line after class definition if there is no docstring.
+
+        See issue #139.
+        """
+        uut = Formatter(
+            test_args,
+            sys.stderr,
+            sys.stdin,
+            sys.stdout,
+        )
+
+        docstring = """\
+class TestClass:
+
+    variable = 1
+"""
+        assert docstring == uut._do_format_code(
+            """\
+class TestClass:
+
+    variable = 1
+"""
+        )
+
     @pytest.mark.unit
     @pytest.mark.parametrize("args", [[""]])
     def test_format_code_strip_blank_line_after_method_docstring(
@@ -989,7 +1053,7 @@ class TestClass:
 
         pass
 '''
-)
+        )
 
 
 class TestFormatCodeRanges:


----

problem_statement: Blank line removal incompatible with black
If a class docstring contains a line that is too long and cannot be reformatted, passing flake8 can be done by appending a `#noqa` to the docstring.
The following was not modified with` docformatter` 1.5.1 and `black` when triggered by `pre-commit`:
```python
class C:
    """Class.""" #noqa

    attr: int
    """Attr."""
```

With `docformatter` 1.6.1, this is changed to:
```python
class C:
    """Class.""" #noqa
    attr: int
    """Attr."""
```

and `pre-commit` always fails because `docformatter` and `black` both apply opposite changes.

This behavior is triggered by the comment appended to the docstring.
date: 2023-04-21
patch: diff --git a/.sourcery.yaml b/.sourcery.yaml
index 1a767dc..407b105 100644
--- a/.sourcery.yaml
+++ b/.sourcery.yaml
@@ -19,15 +19,15 @@ ignore: # A list of paths or files which Sourcery will ignore.
 - .env
 - .tox
 
-# rule_settings:
-#  enable:
-#  - default
-#  disable: []  # A list of rule IDs Sourcery will never suggest.
-#  rule_types:
-#  - refactoring
-#  - suggestion
-#  - comment
-#  python_version: '3.9' # A string specifying the lowest Python version your project supports. Sourcery will not suggest refactorings requiring a higher Python version.
+rule_settings:
+  enable:
+  - default
+  disable: []  # A list of rule IDs Sourcery will never suggest.
+  rule_types:
+  - refactoring
+  - suggestion
+  - comment
+  python_version: '3.7'
 
 # rules:  # A list of custom rules Sourcery will include in its analysis.
 # - id: no-print-statements
diff --git a/pyproject.toml b/pyproject.toml
index 3972d27..165f478 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
 [tool.poetry]
 name = "docformatter"
-version = "1.5.1"
+version = "1.6.1"
 description = "Formats docstrings to follow PEP 257"
 authors = ["Steven Myint"]
 maintainers = [
@@ -18,11 +18,11 @@ classifiers=[
           'Intended Audience :: Developers',
           'Environment :: Console',
           'Programming Language :: Python :: 3',
-          'Programming Language :: Python :: 3.6',
           'Programming Language :: Python :: 3.7',
           'Programming Language :: Python :: 3.8',
           'Programming Language :: Python :: 3.9',
           'Programming Language :: Python :: 3.10',
+          'Programming Language :: Python :: 3.11',
           'Programming Language :: Python :: Implementation',
           'Programming Language :: Python :: Implementation :: PyPy',
           'Programming Language :: Python :: Implementation :: CPython',
@@ -32,50 +32,33 @@ packages = [{include = "docformatter", from = "src"}]
 include = ["LICENSE"]
 
 [tool.poetry.dependencies]
-python = "^3.6"
-charset_normalizer = "^2.0.0"
-tomli = [
-    {version="<2.0.0", optional=true, python="<3.7"},
-    {version="^2.0.0", optional=true, python=">=3.7"},
-]
+python = "^3.7"
+charset_normalizer = "^3.0.0"
+tomli = {version = "^2.0.0", optional = true}
 untokenize = "^0.1.1"
 
 [tool.poetry.dev-dependencies]
-autopep8 = "^1.7.0"
-black = [
-    {version = "^22.0.0", python = ">=3.6.2"},
-]
-coverage = [
-    {extras = ["toml"], version = "^6.2.0", python = "<3.7"},
-    {extras = ["toml"], version = "^6.4.0", python = ">=3.7"},
-]
-isort = [
-    {version = "<5.10.0", python = "<3.6.1"},
-    {version = "^5.10.0", python = ">=3.6.1"},
-]
+autopep8 = "^2.0.0"
+black = "^22.0.0"
+coverage = {extras = ["toml"], version = "^6.4.0"}
+isort = "^5.10.0"
 mock = "^4.0.0"
-mypy = "0.971"
+mypy = "0.991"
 pycodestyle = "^2.8.0"
 pydocstyle = "^6.1.1"
 pylint = [
     {version = "^2.12.0", python = "<3.7.2"},
     {version = "^2.14.0", python = ">=3.7.2"},
 ]
-pytest = [
-    {version = "<7.1.0", python = "<3.7"},
-    {version = "^7.1.0", python = ">=3.7"},
-]
-pytest-cov = "^3.0.0"
-rstcheck = [
-    {version = "<6.0.0", python = "<3.7"},
-    {version = "^6.1.0", python = ">=3.7"},
-]
-tox = "^3.25.0"
-Sphinx = "^5.0.0"
-twine = [
-    {version="<4.0.0", python = "<3.7"},
-    {version="^4.0.0", python = ">=3.7"},
+pytest = "^7.1.0"
+pytest-cov = "^4.0.0"
+rstcheck = "^6.1.0"
+tox = "<4.0.0"
+Sphinx = [
+    {version = "5.3.0", python = "<3.8"},
+    {version = "^6.0.0", python = ">=3.8"},
 ]
+twine = "^4.0.0"
 
 [tool.poetry.extras]
 tomli = ["tomli"]
@@ -153,7 +136,7 @@ output = 'coverage.xml'
 
 [tool.black]
 line-length = 79
-target-version = ['py36', 'py37', 'py38', 'py39', 'py310']
+target-version = ['py37', 'py38', 'py39', 'py310', 'py311']
 include = '\.pyi?$'
 exclude = '''
 /(
@@ -188,11 +171,11 @@ line_length = 79
 legacy_tox_ini = """
 [tox]
 envlist =
-    py36
     py37
     py38
     py39
     py310
+    py311
     pypy3
     coverage
     style
@@ -202,12 +185,12 @@ skipsdist = true
 
 [gh-actions]
 python =
-    3.6: py36
     3.7: py37
     3.8: py38
     3.9: py39
     3.10: py310
-    pypy-3.6: pypy3
+    3.11: py311
+    pypy-3.8: pypy3
 
 [testenv]
 description = run the test suite using pytest under {basepython}
@@ -243,13 +226,12 @@ commands =
     coverage combine
     coverage report -m
     coverage xml -o {toxworkdir}/coverage.xml
-depends = py36, py37, py38, py39, py310, pypy3
+depends = py37, py38, py39, py310, py311, pypy3
 
 [testenv:style]
 description = run autoformatters and style checkers
 deps =
     charset_normalizer
-    docformatter
     pycodestyle
     pydocstyle
     pylint
@@ -257,8 +239,10 @@ deps =
     toml
     untokenize
 commands =
+    pip install -U pip
+    pip install .
     docformatter --recursive {toxinidir}/src/docformatter
-    pycodestyle {toxinidir}/src/docformatter
+    pycodestyle --ignore=E203,W503,W504 {toxinidir}/src/docformatter
     pydocstyle {toxinidir}/src/docformatter
     pylint --rcfile={toxinidir}/pyproject.toml {toxinidir}/src/docformatter
     rstcheck --report-level=1 {toxinidir}/README.rst
diff --git a/src/docformatter/__init__.py b/src/docformatter/__init__.py
index 09f3441..876b013 100644
--- a/src/docformatter/__init__.py
+++ b/src/docformatter/__init__.py
@@ -1,6 +1,6 @@
 #!/usr/bin/env python
 #
-# Copyright (C) 2012-2022 Steven Myint
+# Copyright (C) 2012-2023 Steven Myint
 #
 # Permission is hereby granted, free of charge, to any person obtaining
 # a copy of this software and associated documentation files (the
diff --git a/src/docformatter/__main__.py b/src/docformatter/__main__.py
index bf7a2e1..31d1ab6 100755
--- a/src/docformatter/__main__.py
+++ b/src/docformatter/__main__.py
@@ -1,6 +1,6 @@
 #!/usr/bin/env python
 #
-# Copyright (C) 2012-2022 Steven Myint
+# Copyright (C) 2012-2023 Steven Myint
 #
 # Permission is hereby granted, free of charge, to any person obtaining
 # a copy of this software and associated documentation files (the
diff --git a/src/docformatter/__pkginfo__.py b/src/docformatter/__pkginfo__.py
index a65e0f6..fdd5d06 100644
--- a/src/docformatter/__pkginfo__.py
+++ b/src/docformatter/__pkginfo__.py
@@ -1,6 +1,6 @@
 #!/usr/bin/env python
 #
-# Copyright (C) 2012-2022 Steven Myint
+# Copyright (C) 2012-2023 Steven Myint
 #
 # Permission is hereby granted, free of charge, to any person obtaining
 # a copy of this software and associated documentation files (the
@@ -23,4 +23,4 @@
 # SOFTWARE.
 """Package information for docformatter."""
 
-__version__ = "1.5.0"
+__version__ = "1.6.1"
diff --git a/src/docformatter/configuration.py b/src/docformatter/configuration.py
index 1eb7122..bd18432 100644
--- a/src/docformatter/configuration.py
+++ b/src/docformatter/configuration.py
@@ -1,6 +1,6 @@
 #!/usr/bin/env python
 #
-# Copyright (C) 2012-2022 Steven Myint
+# Copyright (C) 2012-2023 Steven Myint
 #
 # Permission is hereby granted, free of charge, to any person obtaining
 # a copy of this software and associated documentation files (the
diff --git a/src/docformatter/encode.py b/src/docformatter/encode.py
index 10f1f99..49b6a3b 100644
--- a/src/docformatter/encode.py
+++ b/src/docformatter/encode.py
@@ -1,6 +1,6 @@
 #!/usr/bin/env python
 #
-# Copyright (C) 2012-2022 Steven Myint
+# Copyright (C) 2012-2023 Steven Myint
 #
 # Permission is hereby granted, free of charge, to any person obtaining
 # a copy of this software and associated documentation files (the
@@ -28,7 +28,7 @@ import collections
 import io
 import locale
 import sys
-from typing import Dict
+from typing import Dict, List
 
 # Third Party Imports
 from charset_normalizer import from_path  # pylint: disable=import-error
@@ -67,10 +67,10 @@ class Encoder:
         except (SyntaxError, LookupError, UnicodeDecodeError):
             self.encoding = "latin-1"
 
-    def do_find_newline(self, source: str) -> Dict[int, int]:
+    def do_find_newline(self, source: List[str]) -> Dict[int, int]:
         """Return type of newline used in source.
 
-        Paramaters
+        Parameters
         ----------
         source : list
             A list of lines.
diff --git a/src/docformatter/format.py b/src/docformatter/format.py
index 5195b91..d8bbbb9 100644
--- a/src/docformatter/format.py
+++ b/src/docformatter/format.py
@@ -1,6 +1,6 @@
 #!/usr/bin/env python
 #
-# Copyright (C) 2012-2022 Steven Myint
+# Copyright (C) 2012-2023 Steven Myint
 #
 # Permission is hereby granted, free of charge, to any person obtaining
 # a copy of this software and associated documentation files (the
@@ -29,6 +29,7 @@ import argparse
 import collections
 import contextlib
 import io
+import re
 import tokenize
 from typing import TextIO, Tuple
 
@@ -229,13 +230,13 @@ class Formatter:
             The text from the source file.
         """
         try:
-            original_newline = self.encodor.do_find_newline(
+            _original_newline = self.encodor.do_find_newline(
                 source.splitlines(True)
             )
-            code = self._format_code(source)
+            _code = self._format_code(source)
 
             return _strings.normalize_line_endings(
-                code.splitlines(True), original_newline
+                _code.splitlines(True), _original_newline
             )
         except (tokenize.TokenError, IndentationError):
             return source
@@ -310,29 +311,16 @@ class Formatter:
                     only_comments_so_far = False
 
                 previous_token_type = token_type
+                modified_tokens.append(
+                    (token_type, token_string, start, end, line)
+                )
 
-                # If the current token is a newline, the previous token was a
-                # newline or a comment, and these two sequential newlines
-                # follow a function or method definition, ignore the blank
-                # line before the docstring.
-                if (
-                    len(modified_tokens) <= 2
-                    or token_type not in {tokenize.NL, tokenize.NEWLINE}
-                    or modified_tokens[-1][0]
-                    not in {tokenize.NL, tokenize.NEWLINE}
-                    or modified_tokens[-2][1] != ":"
-                    and modified_tokens[-2][0] != tokenize.COMMENT
-                    or not modified_tokens[-2][4].lstrip().startswith(("def"))
-                ):
-                    modified_tokens.append(
-                        (token_type, token_string, start, end, line)
-                    )
-                    modified_tokens = self._do_remove_blank_lines_after_method(
-                        modified_tokens
-                    )
-                    modified_tokens = self._do_remove_blank_lines_before_class(
-                        modified_tokens
-                    )
+            modified_tokens = self._do_remove_blank_lines_after_definitions(
+                modified_tokens
+            )
+            modified_tokens = self._do_remove_blank_lines_after_docstring(
+                modified_tokens
+            )
 
             return untokenize.untokenize(modified_tokens)
         except tokenize.TokenError:
@@ -370,6 +358,12 @@ class Formatter:
         if contents.lstrip().startswith(">>>"):
             return docstring
 
+        # Do not modify docstring if the only thing it contains is a link.
+        _links = _syntax.do_find_links(contents)
+        with contextlib.suppress(IndexError):
+            if _links[0][0] == 0 and _links[0][1] == len(contents):
+                return docstring
+
         summary, description = _strings.split_summary_and_description(contents)
 
         # Leave docstrings with underlined summaries alone.
@@ -379,9 +373,12 @@ class Formatter:
         ):
             return docstring
 
-        if not self.args.force_wrap and _syntax.is_some_sort_of_list(
-            summary,
-            self.args.non_strict,
+        if not self.args.force_wrap and (
+            _syntax.is_some_sort_of_list(
+                summary,
+                self.args.non_strict,
+            )
+            or _syntax.do_find_directives(summary)
         ):
             # Something is probably not right with the splitting.
             return docstring
@@ -393,68 +390,130 @@ class Formatter:
         self.args.wrap_descriptions -= tab_compensation
 
         if description:
-            # Compensate for triple quotes by temporarily prepending 3 spaces.
-            # This temporary prepending is undone below.
-            initial_indent = (
-                indentation
-                if self.args.pre_summary_newline
-                else 3 * " " + indentation
-            )
-            pre_summary = (
-                "\n" + indentation if self.args.pre_summary_newline else ""
+            return self._do_format_multiline_docstring(
+                indentation,
+                summary,
+                description,
+                open_quote,
             )
-            summary = _syntax.wrap_summary(
-                _strings.normalize_summary(summary),
+
+        return self._do_format_oneline_docstring(
+            indentation,
+            contents,
+            open_quote,
+        )
+
+    def _do_format_oneline_docstring(
+        self,
+        indentation: str,
+        contents: str,
+        open_quote: str,
+    ) -> str:
+        """Format one line docstrings.
+
+        Parameters
+        ----------
+        indentation : str
+            The indentation to use for each line.
+        contents : str
+            The contents of the original docstring.
+        open_quote : str
+            The type of quote used by the original docstring.  Selected from
+            QUOTE_TYPES.
+
+        Returns
+        -------
+        formatted_docstring : str
+            The formatted docstring.
+        """
+        if self.args.make_summary_multi_line:
+            beginning = f"{open_quote}\n{indentation}"
+            ending = f'\n{indentation}"""'
+            summary_wrapped = _syntax.wrap_summary(
+                _strings.normalize_summary(contents),
                 wrap_length=self.args.wrap_summaries,
-                initial_indent=initial_indent,
+                initial_indent=indentation,
                 subsequent_indent=indentation,
-            ).lstrip()
-            description = _syntax.wrap_description(
-                description,
-                indentation=indentation,
-                wrap_length=self.args.wrap_descriptions,
-                force_wrap=self.args.force_wrap,
-                strict=self.args.non_strict,
-            )
-            post_description = "\n" if self.args.post_description_blank else ""
-            return f'''\
+            ).strip()
+            return f"{beginning}{summary_wrapped}{ending}"
+        else:
+            summary_wrapped = _syntax.wrap_summary(
+                open_quote + _strings.normalize_summary(contents) + '"""',
+                wrap_length=self.args.wrap_summaries,
+                initial_indent=indentation,
+                subsequent_indent=indentation,
+            ).strip()
+            if self.args.close_quotes_on_newline and "\n" in summary_wrapped:
+                summary_wrapped = (
+                    f"{summary_wrapped[:-3]}"
+                    f"\n{indentation}"
+                    f"{summary_wrapped[-3:]}"
+                )
+            return summary_wrapped
+
+    def _do_format_multiline_docstring(
+        self,
+        indentation: str,
+        summary: str,
+        description: str,
+        open_quote: str,
+    ) -> str:
+        """Format multiline docstrings.
+
+        Parameters
+        ----------
+        indentation : str
+            The indentation to use for each line.
+        summary : str
+            The summary from the original docstring.
+        description : str
+            The long description from the original docstring.
+        open_quote : str
+            The type of quote used by the original docstring.  Selected from
+            QUOTE_TYPES.
+
+        Returns
+        -------
+        formatted_docstring : str
+            The formatted docstring.
+        """
+        # Compensate for triple quotes by temporarily prepending 3 spaces.
+        # This temporary prepending is undone below.
+        initial_indent = (
+            indentation
+            if self.args.pre_summary_newline
+            else 3 * " " + indentation
+        )
+        pre_summary = (
+            "\n" + indentation if self.args.pre_summary_newline else ""
+        )
+        summary = _syntax.wrap_summary(
+            _strings.normalize_summary(summary),
+            wrap_length=self.args.wrap_summaries,
+            initial_indent=initial_indent,
+            subsequent_indent=indentation,
+        ).lstrip()
+        description = _syntax.wrap_description(
+            description,
+            indentation=indentation,
+            wrap_length=self.args.wrap_descriptions,
+            force_wrap=self.args.force_wrap,
+            strict=self.args.non_strict,
+        )
+        post_description = "\n" if self.args.post_description_blank else ""
+        return f'''\
 {open_quote}{pre_summary}{summary}
 
 {description}{post_description}
 {indentation}"""\
 '''
-        else:
-            if not self.args.make_summary_multi_line:
-                summary_wrapped = _syntax.wrap_summary(
-                    open_quote + _strings.normalize_summary(contents) + '"""',
-                    wrap_length=self.args.wrap_summaries,
-                    initial_indent=indentation,
-                    subsequent_indent=indentation,
-                ).strip()
-                if (
-                    self.args.close_quotes_on_newline
-                    and "\n" in summary_wrapped
-                ):
-                    summary_wrapped = (
-                        f"{summary_wrapped[:-3]}"
-                        f"\n{indentation}"
-                        f"{summary_wrapped[-3:]}"
-                    )
-                return summary_wrapped
-            else:
-                beginning = f"{open_quote}\n{indentation}"
-                ending = f'\n{indentation}"""'
-                summary_wrapped = _syntax.wrap_summary(
-                    _strings.normalize_summary(contents),
-                    wrap_length=self.args.wrap_summaries,
-                    initial_indent=indentation,
-                    subsequent_indent=indentation,
-                ).strip()
-                return f"{beginning}{summary_wrapped}{ending}"
 
     @staticmethod
-    def _do_remove_blank_lines_after_method(modified_tokens):
-        """Remove blank lines after method docstring.
+    def _do_remove_blank_lines_after_definitions(modified_tokens):
+        """Remove blank lines between definitions and docstrings.
+
+        Blank lines between class, method, function, and variable
+        definitions and the docstring will be removed.
 
         Parameters
         ----------
@@ -464,23 +523,37 @@ class Formatter:
         Returns
         -------
         modified_tokens: list
-            The list of tokens with any blank lines following a method
-            docstring removed.
+            The list of tokens with any blank lines following a variable
+            definition removed.
         """
-        with contextlib.suppress(IndexError):
-            if (
-                modified_tokens[-1][4] == "\n"
-                and modified_tokens[-2][4].lstrip().startswith('"""')
-                and modified_tokens[-5][4].lstrip().startswith("def")
-            ):
-                modified_tokens.pop(-1)
+        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(
+                    '"""'
+                ):
+                    modified_tokens.pop(_idx - j)
+                    j += 1
+
+                # Remove newline between class, method, and function
+                # definitions and docstring.
+                j = 2
+                while modified_tokens[_idx - j][4] == "\n" and modified_tokens[
+                    _idx - j - 2
+                ][4].strip().startswith(("def", "class")):
+                    modified_tokens.pop(_idx - j)
+                    j += 1
+
         return modified_tokens
 
     @staticmethod
-    def _do_remove_blank_lines_before_class(modified_tokens):
-        """Remove blank lines before class docstring.
-
-        If there is no class docstring, leave any blank lines as is.
+    def _do_remove_blank_lines_after_docstring(modified_tokens):
+        """Remove blank lines between docstring and first Python statement.
 
         Parameters
         ----------
@@ -490,16 +563,48 @@ class Formatter:
         Returns
         -------
         modified_tokens: list
-            The list of tokens with any blank lines following a method
-            docstring removed.
+            The list of tokens with any blank lines following a docstring
+            removed.
         """
-        with contextlib.suppress(IndexError):
-            if (
-                modified_tokens[-3][4] == "\n"
-                and modified_tokens[-2][4].lstrip().startswith('"""')
-                and modified_tokens[-6][4].lstrip().startswith("class")
-            ):
-                modified_tokens.pop(-3)
+        # Remove all newlines between docstring and first Python
+        # statement as long as it's not a stub function.
+        for _idx, _token in enumerate(modified_tokens):
+            with contextlib.suppress(IndexError):
+                _is_definition = (
+                    _token[4].lstrip().startswith(("class ", "def ", "@"))
+                )
+                _is_docstring = (
+                    modified_tokens[_idx - 2][4].strip().endswith('"""')
+                )
+                _after_definition = (
+                    modified_tokens[_idx - 6][4]
+                    .lstrip()
+                    .startswith(("class", "def", "@"))
+                )
+                _after_docstring = modified_tokens[_idx - 5][
+                    4
+                ].strip().endswith('"""') or modified_tokens[_idx - 5][
+                    4
+                ].strip().startswith(
+                    '"""'
+                )
+                _comment_follows = re.search(
+                    r"\"\"\" *#", modified_tokens[_idx - 4][4]
+                )
+
+                if (
+                    _token[0] == 1
+                    and not _is_definition
+                    and not _is_docstring
+                    and not _comment_follows
+                    and _after_definition
+                    and _after_docstring
+                ):
+                    j = 1
+                    while modified_tokens[_idx - j][4] == "\n":
+                        modified_tokens.pop(_idx - j)
+                        j += 1
+
         return modified_tokens
 
     def _do_strip_docstring(self, docstring: str) -> Tuple[str, str]:
diff --git a/src/docformatter/strings.py b/src/docformatter/strings.py
index a5f9e09..f72777c 100644
--- a/src/docformatter/strings.py
+++ b/src/docformatter/strings.py
@@ -1,6 +1,6 @@
 #!/usr/bin/env python
 #
-# Copyright (C) 2012-2022 Steven Myint
+# Copyright (C) 2012-2023 Steven Myint
 #
 # Permission is hereby granted, free of charge, to any person obtaining
 # a copy of this software and associated documentation files (the
@@ -70,11 +70,11 @@ def is_probably_beginning_of_sentence(line):
     """
     # Check heuristically for a parameter list.
     for token in ["@", "-", r"\*"]:
-        if re.search(r"\s" + token + r"\s", line):
+        if re.search(rf"\s{token}\s", line):
             return True
 
     stripped_line = line.strip()
-    is_beginning_of_sentence = re.match(r'[^\w"\'`\(\)]', stripped_line)
+    is_beginning_of_sentence = re.match(r"^[-@\)]", stripped_line)
     is_pydoc_ref = re.match(r"^:\w+:", stripped_line)
 
     return is_beginning_of_sentence and not is_pydoc_ref
@@ -97,9 +97,9 @@ def normalize_line_endings(lines, newline):
     return "".join([normalize_line(line, newline) for line in lines])
 
 
-def normalize_summary(summary):
+def normalize_summary(summary: str) -> str:
     """Return normalized docstring summary."""
-    # remove trailing whitespace
+    # Remove trailing whitespace
     summary = summary.rstrip()
 
     # Add period at end of sentence and capitalize the first word of the
@@ -162,16 +162,11 @@ def split_summary_and_description(contents):
     split_lines = contents.rstrip().splitlines()
 
     for index in range(1, len(split_lines)):
-        found = False
-
-        # Empty line separation would indicate the rest is the description or,
+        # Empty line separation would indicate the rest is the description or
         # symbol on second line probably is a description with a list.
         if not split_lines[index].strip() or is_probably_beginning_of_sentence(
             split_lines[index]
         ):
-            found = True
-
-        if found:
             return (
                 "\n".join(split_lines[:index]).strip(),
                 "\n".join(split_lines[index:]).rstrip(),
diff --git a/src/docformatter/syntax.py b/src/docformatter/syntax.py
index 127ea2b..6c893be 100644
--- a/src/docformatter/syntax.py
+++ b/src/docformatter/syntax.py
@@ -1,6 +1,6 @@
 #!/usr/bin/env python
 #
-# Copyright (C) 2012-2022 Steven Myint
+# Copyright (C) 2012-2023 Steven Myint
 #
 # Permission is hereby granted, free of charge, to any person obtaining
 # a copy of this software and associated documentation files (the
@@ -23,21 +23,120 @@
 # SOFTWARE.
 """This module provides docformatter's Syntaxor class."""
 
+
 # Standard Library Imports
 import contextlib
 import re
 import textwrap
-from typing import List
+from typing import Iterable, List, Tuple, Union
+
+REST_REGEX = r"(\.{2}|``) ?[\w-]+(:{1,2}|``)?"
+"""The regular expression to use for finding reST directives."""
+
+URL_PATTERNS = (
+    "afp|"
+    "apt|"
+    "bitcoin|"
+    "chrome|"
+    "cvs|"
+    "dav|"
+    "dns|"
+    "file|"
+    "finger|"
+    "fish|"
+    "ftp|"
+    "ftps|"
+    "git|"
+    "http|"
+    "https|"
+    "imap|"
+    "ipp|"
+    "ipps|"
+    "irc|"
+    "irc6|"
+    "ircs|"
+    "jar|"
+    "ldap|"
+    "ldaps|"
+    "mailto|"
+    "news|"
+    "nfs|"
+    "nntp|"
+    "pop|"
+    "rsync|"
+    "s3|"
+    "sftp|"
+    "shttp|"
+    "sip|"
+    "sips|"
+    "smb|"
+    "sms|"
+    "snmp|"
+    "ssh|"
+    "svn|"
+    "telnet|"
+    "vnc|"
+    "xmpp|"
+    "xri"
+)
+"""The URL patterns to look for when finding links.
+
+Based on the table at
+<https://en.wikipedia.org/wiki/List_of_URI_schemes>
+"""
+
+# This is the regex used to find URL links:
+#
+# (`{{2}}|`\w[\w. :\n]*|\.\. _?[\w. :]+|')? is used to find in-line links that
+# should remain on a single line even if it exceeds the wrap length.
+#   `{{2}} is used to find two back-tick characters.
+#   This finds patterns like: ``http://www.example.com``
+#
+#   `\w[\w. :#\n]* matches the back-tick character immediately followed by one
+#   letter, then followed by any number of letters, periods, spaces, colons,
+#   hash marks or newlines.
+#   This finds patterns like: `Link text <https://domain.invalid/>`_
+#
+#   \.\. _?[\w. :]+ matches the pattern .. followed one space, then by zero or
+#   one underscore, then any number of letters, periods, spaces, or colons.
+#   This finds patterns like: .. _a link: https://domain.invalid/
+#
+#   ' matches a single quote.
+#   This finds patterns like: 'http://www.example.com'
+#
+#   ? matches the previous pattern between zero or one times.
+#
+# <?({URL_PATTERNS}):(//)?(\S*)>? is used to find the actual link.
+#   <? matches the character < between zero and one times.
+#   ({URL_PATTERNS}) matches one of the strings in the variable
+#   URL_PATTERNS
+#   : matches a colon.
+#   (//)? matches two forward slashes zero or one time.
+#   (\S*) matches any non-whitespace character between zero and infinity times.
+#   >? matches the character > between zero and one times.
+URL_REGEX = rf"(`{{2}}|`\w[\w. :#\n]*|\.\. _?[\w. :]+|')?<?({URL_PATTERNS}):(\
+//)?(\S*)>?"
+
+URL_SKIP_REGEX = rf"({URL_PATTERNS}):(/){{0,2}}(``|')"
+"""The regex used to ignore found hyperlinks.
+
+URLs that don't actually contain a domain, but only the URL pattern should
+be treated like simple text. This will ignore URLs like ``http://`` or 'ftp:`.
+
+({URL_PATTERNS}) matches one of the URL patterns.
+:(/){{0,2}} matches a colon followed by up to two forward slashes.
+(``|') matches a double back-tick or single quote.
+"""
 
 HEURISTIC_MIN_LIST_ASPECT_RATIO = 0.4
 
 
-def do_preserve_links(
+def description_to_list(
     text: str,
     indentation: str,
     wrap_length: int,
 ) -> List[str]:
-    """Rebuild links in docstring.
+    """Convert the description to a list of wrap length lines.
 
     Parameters
     ----------
@@ -51,81 +150,211 @@ def do_preserve_links(
 
     Returns
     -------
-    lines : list
+    _lines : list
         A list containing each line of the description with any links put
         back together.
     """
-    lines = textwrap.wrap(
-        textwrap.dedent(text),
-        width=wrap_length,
-        initial_indent=indentation,
-        subsequent_indent=indentation,
-    )
+    # This is a description containing only one paragraph.
+    if len(re.findall(r"\n\n", text)) <= 0:
+        return textwrap.wrap(
+            textwrap.dedent(text),
+            width=wrap_length,
+            initial_indent=indentation,
+            subsequent_indent=indentation,
+        )
 
-    # There is nothing to do if the input wasn't wrapped.
-    if len(lines) < 2:
-        return lines
+    # This is a description containing multiple paragraphs.
+    _lines = []
+    for _line in text.split("\n\n"):
+        _text = textwrap.wrap(
+            textwrap.dedent(_line),
+            width=wrap_length,
+            initial_indent=indentation,
+            subsequent_indent=indentation,
+        )
+        if _text:
+            _lines.extend(_text)
+        _lines.append("")
 
-    url = next(
-        (
-            line
-            for line in lines
-            if re.search(r"<?(http://|https://|ftp://|sftp://)", line)
-        ),
-        "",
-    )
+    return _lines
 
-    if url != "":
-        url_idx = lines.index(url)
 
-        # Is this an in-line link (i.e., enclosed in <>)?  We want to keep
-        # the '<' and '>' part of the link.
-        if re.search(r"<", url):
-            if len(url.split(sep="<")[0].strip()) > 0:
-                lines[url_idx] = (
-                    f"{indentation}" + url.split(sep="<")[0].strip()
-                )
+def do_clean_url(url: str, indentation: str) -> str:
+    r"""Strip newlines and multiple whitespace from URL string.
+
+    This function deals with situations such as:
+
+    `Get\n
+        Cookies.txt <https://chrome.google.com/webstore/detail/get-
+
+    by returning:
+
+    `Get Cookies.txt <https://chrome.google.com/webstore/detail/get-
+
+    Parameters
+    ----------
+    url : str
+        The URL that was found by the do_find_links() function and needs to be
+        processed.
+    indentation : str
+        The indentation pattern used.
+
+    Returns
+    -------
+    url : str
+        The URL with internal newlines removed and excess whitespace removed.
+    """
+    _lines = url.splitlines()
+    for _idx, _line in enumerate(_lines):
+        if indentation != "" and _line[: len(indentation)] == indentation:
+            _lines[_idx] = f" {_line.strip()}"
+
+    return f'{indentation}{"".join(list(_lines))}'
+
+
+def do_find_directives(text: str) -> bool:
+    """Determine if docstring contains any reST directives.
+
+    .. todo::
+
+        Currently this function only returns True/False to indicate whether a
+        reST directive was found.  Should return a list of tuples containing
+        the start and end position of each reST directive found similar to the
+        function do_find_links().
+
+    Parameters
+    ----------
+    text : str
+        The docstring text to test.
+
+    Returns
+    -------
+    is_directive : bool
+        Whether the docstring is a reST directive.
+    """
+    _rest_iter = re.finditer(REST_REGEX, text)
+    return bool([(rest.start(0), rest.end(0)) for rest in _rest_iter])
+
+
+def do_find_links(text: str) -> List[Tuple[int, int]]:
+    r"""Determine if docstring contains any links.
+
+    Parameters
+    ----------
+    text : str
+        the docstring description to check for a link patterns.
+
+    Returns
+    -------
+    url_index : list
+        a list of tuples with each tuple containing the starting and ending
+        position of each URL found in the passed description.
+    """
+    _url_iter = re.finditer(URL_REGEX, text)
+    return [(_url.start(0), _url.end(0)) for _url in _url_iter]
+
+
+def do_skip_link(text: str, index: Tuple[int, int]) -> bool:
+    """Check if the identified URL is something other than a complete link.
+
+    Is the identified link simply:
+        1. The URL scheme pattern such as 's3://' or 'file://' or 'dns:'.
+        2. The beginning of a URL link that has been wrapped by the user.
+
+    Arguments
+    ---------
+    text : str
+        The description text containing the link.
+    index : tuple
+        The index in the text of the starting and ending position of the
+        identified link.
+
+    Returns
+    -------
+    _do_skip : bool
+        Whether to skip this link and simply treat it as a standard text word.
+    """
+    _do_skip = re.search(URL_SKIP_REGEX, text[index[0] : index[1]]) is not None
+
+    with contextlib.suppress(IndexError):
+        _do_skip = _do_skip or (
+            text[index[0]] == "<" and text[index[1]] != ">"
+        )
+
+    return _do_skip
+
+
+def do_split_description(
+    text: str,
+    indentation: str,
+    wrap_length: int,
+) -> Union[List[str], Iterable]:
+    """Split the description into a list of lines.
 
-            url = f"{indentation}<" + url.split(sep="<")[1]
-            if len(url.split(sep=">")) < 2:
-                url = url + lines[url_idx + 1].strip()
-                lines[url_idx + 1] = url
-
-        # Is this a link target definition (i.e., .. a link: https://)?  We
-        # want to keep the .. a link: on the same line as the url.
-        elif re.search(r"(\.\. )", url):
-            url = url + lines[url_idx + 1].strip()
-            lines[url_idx] = url
-            lines.pop(url_idx + 1)
-
-        # Is this a simple link (i.e., just a link in the text) that should
-        # be unwrapped?  We want to break the url out from the rest of the
-        # text.
-        elif len(lines[url_idx]) >= wrap_length:
-            lines[url_idx] = (
-                f"{indentation}" + url.strip().split(sep=" ")[0].strip()
+    Parameters
+    ----------
+    text : str
+        The docstring description.
+    indentation : str
+        The indentation (number of spaces or tabs) to place in front of each
+        line.
+    wrap_length : int
+        The column to wrap each line at.
+
+    Returns
+    -------
+    lines : list
+        A list containing each line of the description with any links put
+        back together.
+    """
+    # Check if the description contains any URLs.
+    _url_idx = do_find_links(text)
+    if not _url_idx:
+        return description_to_list(
+            text,
+            indentation,
+            wrap_length,
+        )
+    _lines = []
+    _text_idx = 0
+    for _idx in _url_idx:
+        # Skip URL if it is simply a quoted pattern.
+        if do_skip_link(text, _idx):
+            continue
+
+        # If the text including the URL is longer than the wrap length,
+        # we need to split the description before the URL, wrap the pre-URL
+        # text, and add the URL as a separate line.
+        if len(text[_text_idx : _idx[1]]) > (wrap_length - len(indentation)):
+            # Wrap everything in the description before the first URL.
+            _lines.extend(
+                description_to_list(
+                    text[_text_idx : _idx[0]],
+                    indentation,
+                    wrap_length,
+                )
             )
-            url = f"{indentation}" + url.strip().split(sep=" ")[1].strip()
-            url = url + lines[url_idx + 1].strip().split(sep=" ")[0].strip()
-            lines.append(
-                indentation
-                + " ".join(lines[url_idx + 1].strip().split(sep=" ")[1:])
+
+            # Add the URL.
+            _lines.append(
+                f"{do_clean_url(text[_idx[0] : _idx[1]], indentation)}"
             )
-            lines[url_idx + 1] = url
 
-        with contextlib.suppress(IndexError):
-            if lines[url_idx + 2].strip() in [".", "?", "!", ";"] or re.search(
-                r">", lines[url_idx + 2]
-            ):
-                url = url + lines[url_idx + 2].strip()
-                lines[url_idx + 1] = url
-                lines.pop(url_idx + 2)
+            _text_idx = _idx[1]
 
-    return lines
+    # Finally, add everything after the last URL.
+    with contextlib.suppress(IndexError):
+        _stripped_text = (
+            text[_text_idx + 1 :].strip(indentation)
+            if text[_text_idx] == "\n"
+            else text[_text_idx:].strip()
+        )
+        _lines.append(f"{indentation}{_stripped_text}")
+    return _lines
 
 
 # pylint: disable=line-too-long
-def is_some_sort_of_list(text, strict) -> bool:
+def is_some_sort_of_list(text: str, strict: bool) -> bool:
     """Determine if docstring is a reST list.
 
     Notes
@@ -157,8 +386,6 @@ def is_some_sort_of_list(text, strict) -> bool:
 
     return any(
         (
-            re.match(r"\s*$", line)
-            or
             # "1. item"
             re.match(r"\s*\d\.", line)
             or
@@ -184,8 +411,7 @@ def is_some_sort_of_list(text, strict) -> bool:
 def is_some_sort_of_code(text: str) -> bool:
     """Return True if text looks like code."""
     return any(
-        len(word) > 50
-        and not re.match(r"<{0,1}(http:|https:|ftp:|sftp:)", word)
+        len(word) > 50 and not re.match(URL_REGEX, word)
         for word in text.split()
     )
 
@@ -276,10 +502,14 @@ def wrap_description(text, indentation, wrap_length, force_wrap, strict):
     # Ignore possibly complicated cases.
     if wrap_length <= 0 or (
         not force_wrap
-        and (is_some_sort_of_code(text) or is_some_sort_of_list(text, strict))
+        and (
+            is_some_sort_of_code(text)
+            or do_find_directives(text)
+            or is_some_sort_of_list(text, strict)
+        )
     ):
         return text
 
-    text = do_preserve_links(text, indentation, wrap_length)
+    lines = do_split_description(text, indentation, wrap_length)
 
-    return indentation + "\n".join(text).strip()
+    return indentation + "\n".join(lines).strip()
diff --git a/src/docformatter/util.py b/src/docformatter/util.py
index b78376d..a7c2709 100644
--- a/src/docformatter/util.py
+++ b/src/docformatter/util.py
@@ -1,6 +1,6 @@
 #!/usr/bin/env python
 #
-# Copyright (C) 2012-2022 Steven Myint
+# Copyright (C) 2012-2023 Steven Myint
 #
 # Permission is hereby granted, free of charge, to any person obtaining
 # a copy of this software and associated documentation files (the
@@ -43,7 +43,6 @@ def find_py_files(sources, recursive, exclude=None):
 
     Return: yields paths to found files.
     """
-
     def not_hidden(name):
         """Return True if file 'name' isn't .hidden."""
         return not name.startswith(".")

test_patch: diff --git a/tests/__init__.py b/tests/__init__.py
index 9f49e72..45a15a5 100644
--- a/tests/__init__.py
+++ b/tests/__init__.py
@@ -3,7 +3,7 @@
 #
 #       tests._init__.py is part of the docformatter project
 #
-# Copyright (C) 2012-2019 Steven Myint
+# Copyright (C) 2012-2023 Steven Myint
 #
 # Permission is hereby granted, free of charge, to any person obtaining
 # a copy of this software and associated documentation files (the
diff --git a/tests/conftest.py b/tests/conftest.py
index 5f5a9aa..4995dcb 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -3,7 +3,7 @@
 #
 #       tests.conftest.py is part of the docformatter project
 #
-# Copyright (C) 2012-2019 Steven Myint
+# Copyright (C) 2012-2023 Steven Myint
 #
 # Permission is hereby granted, free of charge, to any person obtaining
 # a copy of this software and associated documentation files (the
diff --git a/tests/test_configuration_functions.py b/tests/test_configuration_functions.py
index 6012b95..e402fbc 100644
--- a/tests/test_configuration_functions.py
+++ b/tests/test_configuration_functions.py
@@ -4,7 +4,7 @@
 #       tests.test_configuration_functions.py is part of the docformatter
 #       project
 #
-# Copyright (C) 2012-2022 Steven Myint
+# Copyright (C) 2012-2023 Steven Myint
 #
 # Permission is hereby granted, free of charge, to any person obtaining
 # a copy of this software and associated documentation files (the
diff --git a/tests/test_docformatter.py b/tests/test_docformatter.py
index 7a6fd96..2a19d29 100644
--- a/tests/test_docformatter.py
+++ b/tests/test_docformatter.py
@@ -3,7 +3,7 @@
 #
 #       tests.test_docformatter.py is part of the docformatter project
 #
-# Copyright (C) 2012-2019 Steven Myint
+# Copyright (C) 2012-2023 Steven Myint
 #
 # Permission is hereby granted, free of charge, to any person obtaining
 # a copy of this software and associated documentation files (the
@@ -299,7 +299,7 @@ def foo():
         contents,
         arguments,
     ):
-        """"""
+        """Wrap summary at --wrap-summaries number of columns."""
         assert '''\
 @@ -1,4 +1,3 @@
  def foo():
@@ -331,13 +331,14 @@ def foo():
     @pytest.mark.parametrize(
         "arguments", [["--wrap-summaries=0", "--wrap-description=0"]]
     )
-    def test_end_to_end_with_no_wrapping(
+    def test_end_to_end_with_no_wrapping_long_sentences(
         self,
         run_docformatter,
         temporary_file,
         contents,
         arguments,
     ):
+        """Long sentences remain long with wrapping turned off."""
         assert "" == "\n".join(
             run_docformatter.communicate()[0]
             .decode()
@@ -363,14 +364,14 @@ def foo():
     @pytest.mark.parametrize(
         "arguments", [["--wrap-summaries=0", "--wrap-description=0"]]
     )
-    def test_end_to_end_with_no_wrapping_2(
+    def test_end_to_end_with_no_wrapping_short_sentences(
         self,
         run_docformatter,
         temporary_file,
         arguments,
         contents,
     ):
-        """"""
+        """Short sentences remain short with wrapping turned off."""
         assert "" == "\n".join(
             run_docformatter.communicate()[0]
             .decode()
@@ -397,13 +398,108 @@ def foo():
         arguments,
         contents,
     ):
-        """"""
+        """Add period to end of summary even with wrapping off."""
         assert '''\
 @@ -1,3 +1,3 @@
  def foo():
      """Wrapping is off, but it will still add
 -    the trailing period  """
 +    the trailing period."""
+''' == "\n".join(
+            run_docformatter.communicate()[0]
+            .decode()
+            .replace("\r", "")
+            .split("\n")[2:]
+        )
+
+    @pytest.mark.system
+    @pytest.mark.parametrize(
+        "contents",
+        [
+            '''\
+def foo():
+    """Description from issue #145 that was being improperly wrapped.
+
+    .. _linspace API: https://numpy.org/doc/stable/reference/generated/numpy.linspace.html
+    .. _arange API: https://numpy.org/doc/stable/reference/generated/numpy.arange.html
+    .. _logspace API: https://numpy.org/doc/stable/reference/generated/numpy.logspace.html
+    """
+'''
+        ],
+    )
+    @pytest.mark.parametrize(
+        "arguments",
+        [
+            [
+                "--wrap-summaries=72",
+                "--wrap-descriptions=78",
+            ]
+        ],
+    )
+    def test_end_to_end_keep_rest_link_one_line(
+        self,
+        run_docformatter,
+        temporary_file,
+        arguments,
+        contents,
+    ):
+        """Keep reST in-line URL link on one line.
+
+        See issue #145. See requirement docformatter_10.1.3.1.
+        """
+        assert "" == "\n".join(
+            run_docformatter.communicate()[0]
+            .decode()
+            .replace("\r", "")
+            .split("\n")[2:]
+        )
+
+    @pytest.mark.system
+    @pytest.mark.parametrize(
+        "contents",
+        [
+            '''\
+def foo():
+    """Description from issue #150 that was being improperly wrapped.
+
+    The text file can be retrieved via the Chrome plugin `Get 
+    Cookies.txt <https://chrome.google.com/webstore/detail/get-
+    cookiestxt/bgaddhkoddajcdgocldbbfleckgcbcid>` while browsing."""
+'''
+        ],
+    )
+    @pytest.mark.parametrize(
+        "arguments",
+        [
+            [
+                "--wrap-summaries=72",
+                "--wrap-descriptions=78",
+            ]
+        ],
+    )
+    def test_ignore_already_wrapped_link(
+        self,
+        run_docformatter,
+        temporary_file,
+        arguments,
+        contents,
+    ):
+        """Ignore a URL link that was wrapped by the user.
+
+        See issue #150.
+        """
+        assert '''\
+@@ -1,6 +1,7 @@
+ def foo():
+     """Description from issue #150 that was being improperly wrapped.
+ 
+-    The text file can be retrieved via the Chrome plugin `Get 
+-    Cookies.txt <https://chrome.google.com/webstore/detail/get-
+-    cookiestxt/bgaddhkoddajcdgocldbbfleckgcbcid>` while browsing."""
++    The text file can be retrieved via the Chrome plugin
++    `Get Cookies.txt <https://chrome.google.com/webstore/detail/get-
++    cookiestxt/bgaddhkoddajcdgocldbbfleckgcbcid>` while browsing.
++    """
 ''' == "\n".join(
             run_docformatter.communicate()[0]
             .decode()
@@ -736,12 +832,12 @@ pre-summary-space = false
         "contents",
         [
             '''\
-            class TestFoo():
-                """Docstring that should have a pre-summary newline.
-                
-                This is a multi-line docstring that should have a newline 
-                placed before the summary."""
-            '''
+class TestFoo():
+    """Docstring that should have a pre-summary newline.
+
+    This is a multi-line docstring that should have a newline 
+    placed before the summary."""
+'''
         ],
     )
     @pytest.mark.parametrize(
@@ -776,19 +872,17 @@ pre-summary-space = false
         See issue #119.
         """
         assert '''\
-@@ -1,6 +1,7 @@
-             class TestFoo():
--                """Docstring that should have a pre-summary newline.
--                
--                This is a multi-line docstring that should have a newline 
--                placed before the summary."""
--            
-+                """
-+                Docstring that should have a pre-summary newline.
-+
-+                This is a multi-line docstring that should have a
-+                newline placed before the summary.
-+                """
+@@ -1,5 +1,7 @@
+ class TestFoo():
+-    """Docstring that should have a pre-summary newline.
++    """
++    Docstring that should have a pre-summary newline.
+ 
+-    This is a multi-line docstring that should have a newline 
+-    placed before the summary."""
++    This is a multi-line docstring that should have a newline placed
++    before the summary.
++    """
 ''' == "\n".join(
             run_docformatter.communicate()[0]
             .decode()
@@ -915,13 +1009,13 @@ pre-summary-space = false
         "contents",
         [
             '''\
-            class TestFoo():
-                """Summary docstring that is followed by a description.
-                
-                This is the description and it shouldn't have a blank line 
-                inserted after it.
-                """
-            '''
+class TestFoo():
+    """Summary docstring that is followed by a description.
+
+    This is the description and it shouldn't have a blank line 
+    inserted after it.
+    """
+'''
         ],
     )
     @pytest.mark.parametrize(
@@ -956,17 +1050,15 @@ pre-summary-space = false
         See issue #119.
         """
         assert '''\
-@@ -1,7 +1,6 @@
-             class TestFoo():
-                 """Summary docstring that is followed by a description.
--                
--                This is the description and it shouldn\'t have a blank line 
--                inserted after it.
-+
-+                This is the description and it shouldn\'t have a blank
-+                line inserted after it.
-                 """
--            
+@@ -1,6 +1,6 @@
+ class TestFoo():
+     """Summary docstring that is followed by a description.
+ 
+-    This is the description and it shouldn\'t have a blank line 
+-    inserted after it.
++    This is the description and it shouldn\'t have a blank line inserted
++    after it.
+     """
 ''' == "\n".join(
             run_docformatter.communicate()[0]
             .decode()
@@ -979,22 +1071,22 @@ pre-summary-space = false
         "contents",
         [
             '''\
-                class TestFoo():
-                    """Summary docstring that is followed by a description.
+class TestFoo():
+    """Summary docstring that is followed by a description.
 
-                    This is the description and it should have a blank line 
-                    inserted after it.
-                    """
-                '''
+    This is the description and it should have a blank line 
+    inserted after it.
+    """
+'''
         ],
     )
     @pytest.mark.parametrize(
         "config",
         [
             """\
-                            [tool.docformatter]
-                            blank = true
-                            """
+            [tool.docformatter]
+            blank = true
+            """
         ],
     )
     @pytest.mark.parametrize(
@@ -1020,17 +1112,16 @@ pre-summary-space = false
         See issue #119.
         """
         assert '''\
-@@ -1,7 +1,7 @@
-                 class TestFoo():
-                     """Summary docstring that is followed by a description.
+@@ -1,6 +1,7 @@
+ class TestFoo():
+     """Summary docstring that is followed by a description.
  
--                    This is the description and it should have a blank line 
--                    inserted after it.
-+                    This is the description and it should have a blank
-+                    line inserted after it.
+-    This is the description and it should have a blank line 
+-    inserted after it.
++    This is the description and it should have a blank line inserted
++    after it.
 +
-                     """
--                
+     """
 ''' == "\n".join(
             run_docformatter.communicate()[0]
             .decode()
diff --git a/tests/test_encoding_functions.py b/tests/test_encoding_functions.py
index 2058683..082da04 100644
--- a/tests/test_encoding_functions.py
+++ b/tests/test_encoding_functions.py
@@ -3,7 +3,7 @@
 #
 #       tests.test_encoding_functions.py is part of the docformatter project
 #
-# Copyright (C) 2012-2019 Steven Myint
+# Copyright (C) 2012-2023 Steven Myint
 #
 # Permission is hereby granted, free of charge, to any person obtaining
 # a copy of this software and associated documentation files (the
@@ -55,7 +55,7 @@ class TestDetectEncoding:
     def test_detect_encoding_with_explicit_utf_8(
         self, temporary_file, contents
     ):
-        """Return utf-8 when explicitely set in file."""
+        """Return utf-8 when explicitly set in file."""
         uut = Encoder()
         uut.do_detect_encoding(temporary_file)
 
diff --git a/tests/test_format_code.py b/tests/test_format_code.py
index f12931e..132c304 100644
--- a/tests/test_format_code.py
+++ b/tests/test_format_code.py
@@ -3,7 +3,7 @@
 #
 #       tests.test_format_code.py is part of the docformatter project
 #
-# Copyright (C) 2012-2019 Steven Myint
+# Copyright (C) 2012-2023 Steven Myint
 #
 # Permission is hereby granted, free of charge, to any person obtaining
 # a copy of this software and associated documentation files (the
@@ -1055,6 +1055,108 @@ class TestClass:
 '''
         )
 
+    @pytest.mark.unit
+    @pytest.mark.parametrize("args", [[""]])
+    def test_format_code_strip_blank_line_after_class_variable(
+        self,
+        test_args,
+        args,
+    ):
+        """Strip any newlines between a class variable defintion and docstring.
+
+        See requirement .
+        """
+        uut = Formatter(
+            test_args,
+            sys.stderr,
+            sys.stdin,
+            sys.stdout,
+        )
+
+        docstring = '''\
+class TestClass:
+    """This is a class docstring."""
+
+    test_var = 0
+    """This is a class variable docstring."""
+
+    test_var2 = 1
+    """This is a second class variable docstring."""
+'''
+        assert docstring == uut._do_format_code(
+            '''\
+class TestClass:
+
+    """This is a class docstring."""
+
+    test_var = 0
+
+    """This is a class variable docstring."""
+
+    test_var2 = 1
+
+
+    """This is a second class variable docstring."""
+'''
+        )
+
+    @pytest.mark.unit
+    @pytest.mark.parametrize("args", [[""]])
+    def test_format_code_strip_blank_line_after_module_variable(
+        self,
+        test_args,
+        args,
+    ):
+        """Strip newlines between module variable defintiion and docstring."""
+        uut = Formatter(
+            test_args,
+            sys.stderr,
+            sys.stdin,
+            sys.stdout,
+        )
+
+        docstring = '''\
+CONST = 123
+"""docstring for CONST."""
+'''
+        assert docstring == uut._do_format_code(
+            '''\
+CONST = 123
+
+"""docstring for CONST."""
+'''
+        )
+
+    @pytest.mark.unit
+    @pytest.mark.parametrize("args", [[""]])
+    def test_format_code_do_not_touch_function_no_docstring(
+        self,
+        test_args,
+        args,
+    ):
+        """Do not remove newlines in functions with no docstring.
+
+        See issue #156.
+        """
+        uut = Formatter(
+            test_args,
+            sys.stderr,
+            sys.stdin,
+            sys.stdout,
+        )
+
+        docstring = '''\
+def test_wps3_process_step_io_data_or_href():
+    """Validates that \'data\' literal values and \'href\' file references are both
+    handled as input for workflow steps corresponding to a WPS-3 process."""
+
+    def mock_wps_request(method, url, *_, **kwargs):
+        nonlocal test_reached_parse_inputs
+
+        method = method.upper()
+'''
+        assert docstring == uut._do_format_code(docstring)
+
 
 class TestFormatCodeRanges:
     """Class for testing _format_code() with the line_range or length_range
@@ -1190,7 +1292,7 @@ def foo():
             sys.stdout,
         )
 
-        assert '''\
+        docstring = '''\
 #!/usr/env/bin python
 """This is a module docstring.
 
@@ -1202,7 +1304,8 @@ def foo():
 this
 is
 not."""
-''' == uut._do_format_code(
+'''
+        assert docstring == uut._do_format_code(
             '''\
 #!/usr/env/bin python
 """This is
@@ -1219,3 +1322,72 @@ is
 not."""
 '''
         )
+
+    @pytest.mark.unit
+    @pytest.mark.parametrize("args", [[""]])
+    def test_do_format_code_keep_newline_for_stub_functions(
+        self, test_args, args
+    ):
+        """Should keep newline after docstring in stub functions.
+
+        See issue #156 and issue #173.
+        """
+        uut = Formatter(
+            test_args,
+            sys.stderr,
+            sys.stdin,
+            sys.stdout,
+        )
+
+        docstring = '''\
+class Foo:
+
+    @abstractmethod
+    def bar(self):
+        """This is a description."""
+
+    @abstractmethod
+    def baz(self):
+        """This is a second description."""
+
+'''
+        assert docstring == uut._do_format_code(
+            '''\
+class Foo:
+
+    @abstractmethod
+    def bar(self):
+
+        """This is a description."""
+
+    @abstractmethod
+    def baz(self):
+
+        """This is a second description."""
+
+'''
+        )
+
+    @pytest.mark.unit
+    @pytest.mark.parametrize("args", [[""]])
+    def test_do_format_code_keep_newlines_outside_docstring(
+        self, test_args, args
+    ):
+        """Should keep newlines in code following docstring."""
+        uut = Formatter(
+            test_args,
+            sys.stderr,
+            sys.stdin,
+            sys.stdout,
+        )
+
+        docstring = '''\
+def new_function():
+    """Description of function."""
+    found = next(
+        (index for index, line in enumerate(split) if line.strip()), 0
+    )
+
+    return "\n".join(split[found:])
+'''
+        assert docstring == uut._do_format_code(docstring)
\ No newline at end of file
diff --git a/tests/test_format_docstring.py b/tests/test_format_docstring.py
index 99c34e4..e4cdfe4 100644
--- a/tests/test_format_docstring.py
+++ b/tests/test_format_docstring.py
@@ -3,7 +3,7 @@
 #
 #       tests.test_format_docstring.py is part of the docformatter project
 #
-# Copyright (C) 2012-2019 Steven Myint
+# Copyright (C) 2012-2023 Steven Myint
 #
 # Permission is hereby granted, free of charge, to any person obtaining
 # a copy of this software and associated documentation files (the
@@ -24,7 +24,7 @@
 # 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."""
+"""Module for testing the Formatter class."""
 
 
 # Standard Library Imports
@@ -287,6 +287,127 @@ Hello.
     """'''
         assert docstring == uut._do_format_docstring(INDENTATION, docstring)
 
+    @pytest.mark.unit
+    @pytest.mark.parametrize("args", [[""]])
+    def test_format_docstring_leave_directive_alone(self, test_args, args):
+        """Leave docstrings that have a reST directive in the summary alone."""
+        uut = Formatter(
+            test_args,
+            sys.stderr,
+            sys.stdin,
+            sys.stdout,
+        )
+
+        docstring = '''
+    """.. code-block:: shell-session
+
+    ► apm --version
+    apm  2.6.2
+    npm  6.14.13
+    node 12.14.1 x64
+    atom 1.58.0
+    python 2.7.16
+    git 2.33.0
+    """'''
+        assert docstring == uut._do_format_docstring(INDENTATION, docstring)
+
+    @pytest.mark.unit
+    @pytest.mark.parametrize("args", [[""]])
+    def test_format_docstring_leave_link_only_docstring_alone(
+        self, test_args, args
+    ):
+        """Leave docstrings that consist of only a link alone."""
+        uut = Formatter(
+            test_args,
+            sys.stderr,
+            sys.stdin,
+            sys.stdout,
+        )
+
+        docstring = '''"""
+    `Source of this snippet
+    <https://www.freecodecamp.org/news/how-to-flatten-a-dictionary-in-python-in-4-different-ways/>`_.
+    """'''
+        assert docstring == uut._do_format_docstring(INDENTATION, docstring)
+
+    @pytest.mark.unit
+    @pytest.mark.parametrize("args", [[""]])
+    def test_format_docstring_leave_blank_line_after_variable_def(self,
+                                                                  test_args,
+                                                                  args,):
+        """Leave blank lines after any variable beginning with 'def'.
+
+        See issue #156.
+        """
+        uut = Formatter(
+            test_args,
+            sys.stderr,
+            sys.stdin,
+            sys.stdout,
+        )
+
+        docstring = '\
+class AcceptHeader(ExtendedSchemaNode): \
+    # ok to use name in this case because target key in the mapping must \
+    # be that specific value but cannot have a field named with this format \
+    name = "Accept" \
+    schema_type = String \
+    missing = drop \
+    default = ContentType.APP_JSON  # defaults to JSON for easy use within browsers \
+\
+\
+class AcceptLanguageHeader(ExtendedSchemaNode): \
+    # ok to use name in this case because target key in the mapping must \
+    # be that specific value but cannot have a field named with this format \
+    name = "Accept-Language" \
+    schema_type = String \
+    missing = drop \
+    default = AcceptLanguage.EN_CA \
+    # FIXME: oneOf validator for supported languages (?)'
+        assert docstring == uut._do_format_code(docstring)
+
+    @pytest.mark.unit
+    @pytest.mark.parametrize("args", [[""]])
+    def test_format_docstring_leave_blank_line_after_comment(self,
+                                                                  test_args,
+                                                                  args,):
+        """Leave blank lines after docstring followed by a comment.
+
+        See issue #176.
+        """
+        uut = Formatter(
+            test_args,
+            sys.stderr,
+            sys.stdin,
+            sys.stdout,
+        )
+
+        docstring = '''\
+def Class1:
+    """Class.""" #noqa
+
+    attribute
+    """Attr."""
+
+
+def Class2:
+    """Class."""
+
+    attribute
+    """Attr."""
+
+
+def Class3:
+    """Class docstring.
+
+    With long description.
+    """    #noqa
+
+    attribute
+    """Attr."""
+'''
+        assert docstring == uut._do_format_code(docstring)
+
 
 class TestFormatLists:
     """Class for testing format_docstring() with lists in the docstring."""
@@ -437,6 +558,43 @@ class TestFormatWrap:
                 if len(line.split()) > 1:
                     assert len(line) <= max_length
 
+    @pytest.mark.unit
+    @pytest.mark.parametrize(
+        "args", [["--wrap-descriptions", "120", "--wrap-summaries", "120", ""]]
+    )
+    def test_format_docstring_with_multi_paragraph_description(
+        self,
+        test_args,
+        args,
+    ):
+        """Wrap each paragraph in the long description separately.
+
+        See issue #127.
+        """
+        uut = Formatter(
+            test_args,
+            sys.stderr,
+            sys.stdin,
+            sys.stdout,
+        )
+
+        assert '''\
+"""My awesome function.
+
+    This line is quite long. In fact is it longer than one hundred and twenty characters so it should be wrapped but it
+    is not.
+
+    It doesn\'t wrap because of this line and the blank line in between! Delete them and it will wrap.
+    """''' == uut._do_format_docstring(
+            INDENTATION,
+            '''"""My awesome function.
+
+    This line is quite long. In fact is it longer than one hundred and twenty characters so it should be wrapped but it is not.
+
+    It doesn't wrap because of this line and the blank line in between! Delete them and it will wrap.
+    """'''.strip(),
+        )
+
     @pytest.mark.unit
     @pytest.mark.parametrize("args", [["--wrap-summaries", "79", ""]])
     def test_format_docstring_with_weird_indentation_and_punctuation(
@@ -503,14 +661,17 @@ class TestFormatWrap:
 '''.strip(),
         )
 
-    @pytest.mark.unit
+    @pytest.mark.xfail
     @pytest.mark.parametrize("args", [["--wrap-descriptions", "72", ""]])
     def test_format_docstring_should_ignore_multi_paragraph(
         self,
         test_args,
         args,
     ):
-        """Ignore multiple paragraphs in elaborate description."""
+        """Ignore multiple paragraphs in elaborate description.
+
+        Multiple description paragraphs is supported since v1.5.0.
+        """
         uut = Formatter(
             test_args,
             sys.stderr,
@@ -803,9 +964,9 @@ num_iterations is the number of updates - instead of a better definition of conv
         [["--wrap-descriptions", "72", ""]],
     )
     def test_format_docstring_with_short_inline_link(
-            self,
-            test_args,
-            args,
+        self,
+        test_args,
+        args,
     ):
         """Short in-line links will remain untouched.
 
@@ -821,14 +982,14 @@ num_iterations is the number of updates - instead of a better definition of conv
         docstring = '''\
 """This is yanf with a short link.
 
-    See `the link <https://www.link.com`_ for more details.
+    See `the link <https://www.link.com>`_ for more details.
     """\
 '''
 
         assert '''\
 """This is yanf with a short link.
 
-    See `the link <https://www.link.com`_ for more details.
+    See `the link <https://www.link.com>`_ for more details.
     """\
 ''' == uut._do_format_docstring(
             INDENTATION, docstring.strip()
@@ -836,23 +997,60 @@ num_iterations is the number of updates - instead of a better definition of conv
 
     @pytest.mark.unit
     @pytest.mark.parametrize(
-            "args",
-            [["--wrap-descriptions", "72", ""]],
+        "args",
+        [["--wrap-descriptions", "72", ""]],
+    )
+    def test_format_docstring_with_short_inline_link(
+        self,
+        test_args,
+        args,
+    ):
+        """Should move long in-line links to line by themselves."""
+        uut = Formatter(
+            test_args,
+            sys.stderr,
+            sys.stdin,
+            sys.stdout,
         )
+
+        docstring = '''\
+    """Helpful docstring.
+
+    A larger description that starts here.  https://github.com/apache/kafka/blob/2.5/clients/src/main/java/org/apache/kafka/common/requests/DescribeConfigsResponse.java
+    A larger description that ends here.
+    """\
+'''
+
+        assert '''\
+"""Helpful docstring.
+
+    A larger description that starts here.
+    https://github.com/apache/kafka/blob/2.5/clients/src/main/java/org/apache/kafka/common/requests/DescribeConfigsResponse.java
+    A larger description that ends here.
+    """\
+''' == uut._do_format_docstring(
+                INDENTATION, docstring.strip()
+            )
+
+    @pytest.mark.unit
+    @pytest.mark.parametrize(
+        "args",
+        [["--wrap-descriptions", "72", ""]],
+    )
     def test_format_docstring_with_inline_link_retain_spaces(
-                self,
-                test_args,
-                args,
+        self,
+        test_args,
+        args,
     ):
         """In-line links shouldn't remove blank spaces between words.
 
         See issue #140.
         """
         uut = Formatter(
-                test_args,
-                sys.stderr,
-                sys.stdin,
-                sys.stdout,
+            test_args,
+            sys.stderr,
+            sys.stdin,
+            sys.stdout,
         )
 
         docstring = '''\
@@ -865,13 +1063,13 @@ num_iterations is the number of updates - instead of a better definition of conv
         assert '''\
 """This is a docstring with a link that causes a wrap.
 
-    See `the link
-    <https://www.link.com/a/long/link/that/causes/line/break>`_ for more
-    details.
+    See
+    `the link <https://www.link.com/a/long/link/that/causes/line/break>`_
+    for more details.
     """\
 ''' == uut._do_format_docstring(
-                INDENTATION, docstring.strip()
-            )
+            INDENTATION, docstring.strip()
+        )
 
     @pytest.mark.unit
     @pytest.mark.parametrize(
@@ -910,6 +1108,24 @@ num_iterations is the number of updates - instead of a better definition of conv
             INDENTATION, docstring.strip()
         )
 
+        docstring = '''\
+"""<Short decription>
+
+    .. _linspace API: https://numpy.org/doc/stable/reference/generated/numpy.linspace.html
+    .. _arange API: https://numpy.org/doc/stable/reference/generated/numpy.arange.html
+    .. _logspace API: https://numpy.org/doc/stable/reference/generated/numpy.logspace.html
+    """\
+'''
+        assert '''\
+"""<Short decription>
+
+    .. _linspace API: https://numpy.org/doc/stable/reference/generated/numpy.linspace.html
+    .. _arange API: https://numpy.org/doc/stable/reference/generated/numpy.arange.html
+    .. _logspace API: https://numpy.org/doc/stable/reference/generated/numpy.logspace.html
+    """\
+''' == uut._do_format_docstring(
+                    INDENTATION, docstring.strip()
+                )
     @pytest.mark.unit
     @pytest.mark.parametrize(
         "args",
@@ -949,6 +1165,97 @@ 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", "88", ""]],
+    )
+    def test_format_docstring_keep_inline_link_together(
+        self,
+        test_args,
+        args,
+    ):
+        """Keep in-line links together with the display text.
+
+        See issue #157.
+        """
+        uut = Formatter(
+            test_args,
+            sys.stderr,
+            sys.stdin,
+            sys.stdout,
+        )
+
+        docstring = '''\
+    """Get the Python type of a Click parameter.
+
+    See the list of `custom types provided by Click
+    <https://click.palletsprojects.com/en/8.1.x/api/?highlight=intrange#types>`_.
+    """\
+    '''
+
+        assert '''\
+"""Get the Python type of a Click parameter.
+
+    See the list of
+    `custom types provided by Click <https://click.palletsprojects.com/en/8.1.x/api/?highlight=intrange#types>`_.
+    """\
+''' == uut._do_format_docstring(
+                INDENTATION, docstring.strip()
+            )
+
+    @pytest.mark.unit
+    @pytest.mark.parametrize(
+        "args",
+        [["--wrap-descriptions", "88", ""]],
+    )
+    def test_format_docstring_keep_inline_link_together_two_paragraphs(
+            self,
+            test_args,
+            args,
+    ):
+        """Keep in-line links together with the display text.
+
+        If there is another paragraph following the in-line link, don't strip
+        the newline in between.
+
+        See issue #157.
+        """
+        uut = Formatter(
+            test_args,
+            sys.stderr,
+            sys.stdin,
+            sys.stdout,
+        )
+
+        docstring = '''\
+"""Fetch parameters values from configuration file and merge them with the
+    defaults.
+
+    User configuration is `merged to the context default_map as Click does
+    <https://click.palletsprojects.com/en/8.1.x/commands/#context-defaults>`_.
+
+    This allow user's config to only overrides defaults. Values sets from direct
+    command line parameters, environment variables or interactive prompts, takes
+    precedence over any values from the config file.
+"""\
+'''
+
+        assert '''\
+"""Fetch parameters values from configuration file and merge them with the
+    defaults.
+
+    User configuration is
+    `merged to the context default_map as Click does <https://click.palletsprojects.com/en/8.1.x/commands/#context-defaults>`_.
+    
+    This allow user\'s config to only overrides defaults. Values sets from direct
+    command line parameters, environment variables or interactive prompts, takes
+    precedence over any values from the config file.
+    """\
+''' == uut._do_format_docstring(
+                INDENTATION, docstring.strip()
+            )
+
     @pytest.mark.unit
     @pytest.mark.parametrize(
         "args",
diff --git a/tests/test_string_functions.py b/tests/test_string_functions.py
index 84f4f9f..0a155b2 100644
--- a/tests/test_string_functions.py
+++ b/tests/test_string_functions.py
@@ -3,7 +3,7 @@
 #
 #       tests.test_string_functions.py is part of the docformatter project
 #
-# Copyright (C) 2012-2019 Steven Myint
+# Copyright (C) 2012-2023 Steven Myint
 #
 # Permission is hereby granted, free of charge, to any person obtaining
 # a copy of this software and associated documentation files (the
@@ -377,7 +377,7 @@ class TestSplitters:
         )
 
     @pytest.mark.unit
-    def test_split_summary_and_description_with_late__punctuation(self):
+    def test_split_summary_and_description_with_punctuation(self):
         """"""
         assert (
             (
@@ -402,7 +402,7 @@ Try this and this and this and this and this and this and this at
         )
 
     @pytest.mark.unit
-    def test_split_summary_and_description_without__punctuation(self):
+    def test_split_summary_and_description_without_punctuation(self):
         """"""
         assert (
             (
@@ -440,6 +440,23 @@ Try this and this and this and this and this and this and this at
                 text
             )
 
+    @pytest.mark.unit
+    def test_split_summary_and_description_with_url(self):
+        """Retain URL on second line with summary."""
+        text = '''\
+"""Sequence of package managers as defined by `XKCD #1654: Universal Install Script
+<https://xkcd.com/1654/>`_.
+
+See the corresponding :issue:`implementation rationale in issue #10 <10>`.
+"""\
+'''
+        assert (
+            '"""Sequence of package managers as defined by `XKCD #1654: Universal Install Script\n'
+            "<https://xkcd.com/1654/>`_.",
+            "\nSee the corresponding :issue:`implementation rationale in issue #10 <10>`."
+            '\n"""',
+        ) == docformatter.split_summary_and_description(text)
+
 
 class TestStrippers:
     """Class for testing the string stripping functions.
diff --git a/tests/test_syntax_functions.py b/tests/test_syntax_functions.py
new file mode 100644
index 0000000..235bb45
--- /dev/null
+++ b/tests/test_syntax_functions.py
@@ -0,0 +1,161 @@
+# pylint: skip-file
+# type: ignore
+#
+#       tests.test_syntax_functions.py is part of the docformatter project
+#
+# Copyright (C) 2012-2023 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 functions that deal with syntax.
+
+This module contains tests for syntax functions.  Syntax functions are
+those:
+
+    do_clean_link()
+    do_find_directives()
+    do_find_links()
+    do_skip_link()
+"""
+
+# Third Party Imports
+import pytest
+
+# docformatter Package Imports
+import docformatter
+
+
+class TestURLHandlers:
+    """Class for testing the URL handling functions.
+
+    Includes tests for:
+
+        - do_clean_link()
+        - do_find_links()
+        - do_skip_link()
+    """
+
+    @pytest.mark.unit
+    def test_find_in_line_link(self):
+        """Should find link pattern in a text block."""
+        assert [(53, 162)] == docformatter.do_find_links(
+            "The text file can be retrieved via the Chrome plugin `Get \
+Cookies.txt <https://chrome.google.com/webstore/detail/get-\
+cookiestxt/bgaddhkoddajcdgocldbbfleckgcbcid>` while browsing."
+        )
+        assert [(95, 106), (110, 123)] == docformatter.do_find_links(
+            "``pattern`` is considered as an URL only if it is parseable as such\
+            and starts with ``http://`` or ``https://``."
+        )
+
+    @pytest.mark.unit
+    def test_skip_link_with_manual_wrap(self):
+        """Should skip a link that has been manually wrapped by the user."""
+        assert docformatter.do_skip_link(
+            "``pattern`` is considered as an URL only if it is parseable as such\
+            and starts with ``http://`` or ``https://``.",
+            (95, 106),
+        )
+        assert docformatter.do_skip_link(
+            "``pattern`` is considered as an URL only if it is parseable as such\
+            and starts with ``http://`` or ``https://``.",
+            (110, 123),
+        )
+
+    @pytest.mark.unit
+    def test_do_clean_link(self):
+        """Should remove line breaks from links."""
+        assert (
+            "    `Get Cookies.txt <https://chrome.google.com/webstore/detail/get-cookiestxt/bgaddhkoddajcdgocldbbfleckgcbcid>`"
+        ) == docformatter.do_clean_url(
+            "`Get \
+Cookies.txt <https://chrome.google.com/webstore/detail/get-\
+cookiestxt/bgaddhkoddajcdgocldbbfleckgcbcid>`",
+            "    ",
+        )
+
+        assert (
+            "    `custom types provided by Click        <https://click.palletsprojects.com/en/8.1.x/api/?highlight=intrange#types>`_."
+        ) == docformatter.do_clean_url(
+            "`custom types provided by Click\
+        <https://click.palletsprojects.com/en/8.1.x/api/?highlight=intrange#types>`_.",
+            "    ",
+        )
+
+
+class TestreSTHandlers:
+    """Class for testing the reST directive handling functions.
+
+    Includes tests for:
+
+        - do_find_directives()
+    """
+
+    @pytest.mark.unit
+    def test_find_in_line_directives(self):
+        """Should find reST directieves in a text block."""
+        assert docformatter.do_find_directives(
+            "These are some reST directives that need  to be retained even if it means not wrapping the line they are found on.\
+         Constructs and returns a :class:`QuadraticCurveTo <QuadraticCurveTo>`.\
+         Register ``..click:example::`` and ``.. click:run::`` directives, augmented with ANSI coloring."
+        )
+
+    @pytest.mark.unit
+    def test_find_double_dot_directives(self):
+        """Should find reST directives preceeded by .."""
+        assert docformatter.do_find_directives(
+            ".. _linspace API: https://numpy.org/doc/stable/reference/generated/numpy.linspace.html\
+             .. _arange API: https://numpy.org/doc/stable/reference/generated/numpy.arange.html\
+             .. _logspace API: https://numpy.org/doc/stable/reference/generated/numpy.logspace.html"
+        )
+
+        assert docformatter.do_find_directives(
+            "``pattern`` is considered as an URL only if it is parseable as such"
+            "and starts with ``http://`` or ``https://``."
+            ""
+            ".. important::"
+            ""
+            "This is a straight `copy of the functools.cache implementation"
+            "<https://github.com/python/cpython/blob/55a26de6ba938962dc23f2495723cf0f4f3ab7c6/Lib/functools.py#L647-L653>`_,"
+            "hich is only `available in the standard library starting with Python v3.9"
+            "<https://docs.python.org/3/library/functools.html?highlight=caching#functools.cache>`."
+        )
+
+    @pytest.mark.unit
+    def test_find_double_backtick_directives(self):
+        """Should find reST directives preceeded by ``."""
+        assert docformatter.do_find_directives(
+            "By default we choose to exclude:"
+            ""
+            "``Cc``"
+            "   Since ``mailman`` apparently `sometimes trims list members"
+            "   <https://mail.python.org/pipermail/mailman-developers/2002-September/013233.html>`_"
+            "   from the ``Cc`` header to avoid sending duplicates. Which means that copies of mail"
+            "   reflected back from the list server will have a different ``Cc`` to the copy saved by"
+            "   the MUA at send-time."
+            ""
+            "``Bcc``"
+            "    Because copies of the mail saved by the MUA at send-time will have ``Bcc``, but copies"
+            "    reflected back from the list server won't."
+            ""
+            "``Reply-To``"
+            "    Since a mail could be ``Cc``'d to two lists with different ``Reply-To`` munging"
+            "options set."
+        )
diff --git a/tests/test_utility_functions.py b/tests/test_utility_functions.py
index 270a064..52a3873 100644
--- a/tests/test_utility_functions.py
+++ b/tests/test_utility_functions.py
@@ -3,7 +3,7 @@
 #
 #       tests.test_utility_functions.py is part of the docformatter project
 #
-# Copyright (C) 2012-2019 Steven Myint
+# Copyright (C) 2012-2023 Steven Myint
 #
 # Permission is hereby granted, free of charge, to any person obtaining
 # a copy of this software and associated documentation files (the
@@ -45,12 +45,11 @@ import docformatter
 
 
 class TestFindPyFiles:
-    """Class for testing the find_py_files() Function."""
+    """Class for testing the find_py_files() function."""
 
     @pytest.mark.unit
     def test_is_hidden(self):
         """Skip files that are .hidden."""
-
         assert docformatter.find_py_files("not_hidden", ".hidden_file.py")
 
     @pytest.mark.xfail(
@@ -58,7 +57,6 @@ class TestFindPyFiles:
     )
     def test_non_recursive_ignore_non_py_files(self):
         """Only process python (*.py) files."""
-
         sources = ["one.py", "two.py", "three.toml"]
 
         test_only_py = list(docformatter.find_py_files(sources, False))
@@ -67,7 +65,6 @@ class TestFindPyFiles:
     @pytest.mark.unit
     def test_recursive_ignore_non_py_files(self):
         """Only process python (*.py) files when recursing directories."""
-
         sources = {"/root"}
         patch_data = [
             ("/root", [], ["one.py", "two.py", "three.toml"]),
@@ -226,7 +223,192 @@ class TestIsProbablySentence:
         )
 
 
-class TestIsSomeSortdOfList:
+class TestDoFindLinks:
+    """Class for testing the do_find_links() function."""
+
+    @pytest.mark.unit
+    def test_do_find_file_system_link(self):
+        """Identify afp://, nfs://, smb:// as a link."""
+        text = "This is an Apple Filing Protocol URL pattern: afp://[<user@]<host>[:<port>][/[<path>]]"
+        assert docformatter.do_find_links(text) == [(46, 86)]
+        text = "This is an Network File System URL pattern: nfs://server<:port>/<path>"
+        assert docformatter.do_find_links(text) == [(44, 70)]
+        text = "This is an Samba URL pattern: smb://[<user>@]<host>[:<port>][/[<path>]][?<param1>=<value1>[;<param2>=<value2>]]"
+        assert docformatter.do_find_links(text) == [(30, 111)]
+
+    @pytest.mark.unit
+    def test_do_find_miscellaneous_link(self):
+        """Identify apt:, bitcoin:, chrome://, and jar: as a link."""
+        text = "This is an apt URL pattern: apt:docformatter"
+        assert docformatter.do_find_links(text) == [(28, 44)]
+        text = "This is a bitcoin URL pattern: bitcoin:<address>[?[amount=<size>][&][label=<label>][&][message=<message>]]"
+        assert docformatter.do_find_links(text) == [(31, 106)]
+        text = (
+            "This is a chrome URL pattern: chrome://<package>/<section>/<path>"
+        )
+        assert docformatter.do_find_links(text) == [(30, 65)]
+        text = "This is a Java compressed archive URL pattern: jar:<url>!/[<entry>]"
+        assert docformatter.do_find_links(text) == [(47, 67)]
+
+    @pytest.mark.unit
+    def test_do_find_version_control_link(self):
+        """Identify cvs://, git://, or svn:// as a link."""
+        text = "This is a Concurrent Versions System URL pattern: cvs://<method:logindetails>@<repository>/<modulepath>;[date=date to retrieve | tag=tag to retrieve]"
+        assert docformatter.do_find_links(text) == [(50, 114)]
+        text = "This is a Git URL pattern: git://github.com/PyCQA/docformatter.git"
+        assert docformatter.do_find_links(text) == [(27, 66)]
+        text = "This is a Subversion URL pattern: svn://<logindetails>@<repository><:port>/<modulepath>"
+        assert docformatter.do_find_links(text) == [(34, 87)]
+
+    @pytest.mark.unit
+    def test_do_find_domain_name_system_link(self):
+        """Identify dns: as a link."""
+        text = "This is a Domain Name System URL pattern: dns:example?TYPE=A;CLASS=IN"
+        assert docformatter.do_find_links(text) == [(42, 69)]
+
+    @pytest.mark.unit
+    def test_do_find_file_transfer_protocol_link(self):
+        """Identify file://, ftp://, ftps://, and sftp:// as a link."""
+        text = (
+            "This is a URL pattern for addressing a file: file://[host]/path"
+        )
+        assert docformatter.do_find_links(text) == [(39, 44), (45, 63)]
+        text = "This is a File Transfer Protocol URL pattern: ftp://[user[:password]@]host[:port]/url-path"
+        assert docformatter.do_find_links(text) == [(46, 90)]
+        text = "This is a Secure File Transfer Protocol URL pattern: ftps://[user[:password]@]host[:port]/url-path"
+        assert docformatter.do_find_links(text) == [(53, 98)]
+        text = "This is a SSH File Transfer Protocol URL pattern: sftp://[<user>[;fingerprint=<host-key fingerprint>]@]<host>[:<port>]/<path>/<file>"
+        assert docformatter.do_find_links(text) == [(50, 87)]
+
+    @pytest.mark.unit
+    def test_do_find_network_command_link(self):
+        """Identify finger://, rsync://, telnet://, and vnc:// as a link."""
+        text = "This is a finger protocol URL pattern: finger://host[:port][/<request>]"
+        assert docformatter.do_find_links(text) == [(39, 71)]
+        text = "This is a remote synchronization URL pattern: rsync://<host>[:<port>]/<path>"
+        assert docformatter.do_find_links(text) == [(46, 76)]
+        text = "This is a telnet URL pattern: telnet://<user>:<password>@<host>[:<port>/]"
+        assert docformatter.do_find_links(text) == [(30, 73)]
+        text = "This is a Virtual Network Computing URL pattern: vnc://[<host>[:<port>]][?<params>]"
+        assert docformatter.do_find_links(text) == [(49, 83)]
+        text = "This is an eXtensible Resource Identifier URL pattern: xri://<authority>[/[<path>]][?<query>][#fragment]"
+        assert docformatter.do_find_links(text) == [(55, 104)]
+
+    @pytest.mark.unit
+    def test_do_find_remote_shell_link(self):
+        """Identify fish:// and ssh:// as a link."""
+        text = "This is a fish URL pattern: fish://[<username>[:<password>]@]<hostname>[:<port>]"
+        assert docformatter.do_find_links(text) == [(28, 80)]
+        text = "This is a Secure Shell URL pattern: ssh://[<user>[;fingerprint=<host-key fingerprint>]@]<host>[:<port>]"
+        assert docformatter.do_find_links(text) == [(36, 72)]
+
+    @pytest.mark.unit
+    def test_do_find_internet_transfer_protocol_link(self):
+        """Identify dav:, http://, https://, and shttp:// as a link."""
+        text = "This is a WebDAV Transfer Protocol URL pattern: dav://example.com/directory/subdirectory"
+        assert docformatter.do_find_links(text) == [(48, 88)]
+        text = "This is a Hypertext Transfer Protocol URL pattern: http://github.com/PyCQA/docformatter"
+        assert docformatter.do_find_links(text) == [(51, 87)]
+        text = "This is a Secure Hypertext Transfer Protocol URL pattern: https://github.com/PyCQA/docformatter"
+        assert docformatter.do_find_links(text) == [(58, 95)]
+        text = "This is an obsolete Secure Hypertext Transfer Protocol URL pattern: shttp://github.com/PyCQA/docformatter"
+        assert docformatter.do_find_links(text) == [(68, 105)]
+
+    @pytest.mark.unit
+    def test_do_find_mail_link(self):
+        """Identify imap://, mailto:, and pop:// as a link."""
+        text = "This is a Internet Message Access Protocol URL pattern: imap://[<user>[;AUTH=<type>]@]<host>[:<port>]/<command>"
+        assert docformatter.do_find_links(text) == [(56, 111)]
+        text = "This is a Simple Mail Transfer Protocol URL pattern: mailto:<address>[?<header1>=<value1>[&<header2>=<value2>]]"
+        assert docformatter.do_find_links(text) == [(53, 111)]
+        text = "This is a Post Office Protocol URL pattern: pop://[<user>[;AUTH=<auth>]@]<host>[:<port>]"
+        assert docformatter.do_find_links(text) == [(44, 88)]
+
+    @pytest.mark.unit
+    def test_do_find_printer_link(self):
+        """Identify ipp:// and ipps:// as a link."""
+        text = "This is a Internet Printing Protocol URL pattern: ipp://printer.example.com/ipp/print"
+        assert docformatter.do_find_links(text) == [(50, 85)]
+        text = "This is a Secure Internet Printing Protocol URL pattern: ipps://printer2.example.com:443/ipp/print"
+        assert docformatter.do_find_links(text) == [(57, 98)]
+
+    @pytest.mark.unit
+    def test_do_find_messaging_link(self):
+        """Identify irc://, irc6://, ircs://, sms://, and xmpp:// as a link."""
+        text = "This is a Internet Relay Chat URL pattern: irc://<host>[:<port>]/[<channel>[?<password>]]"
+        assert docformatter.do_find_links(text) == [(43, 89)]
+        text = "This is a Internet Relay Chat v6 URL pattern: irc6://<host>[:<port>]/[<channel>[?<password>]]"
+        assert docformatter.do_find_links(text) == [(46, 93)]
+        text = "This is a Secure Internet Relay Chat URL pattern: ircs://<host>[:<port>]/[<channel>[?<password>]]"
+        assert docformatter.do_find_links(text) == [(50, 97)]
+        text = "This is a Short Message Service URL pattern: sms:+15558675309?body=hello%20there"
+        assert docformatter.do_find_links(text) == [(45, 80)]
+        text = "This is a Extensible Messaging and Presence Protocol URL pattern: xmpp:[<user>]@<host>[:<port>]/[<resource>][?<query>]"
+        assert docformatter.do_find_links(text) == [(66, 118)]
+
+    @pytest.mark.unit
+    def test_do_find_directory_access_link(self):
+        """Identify ldap://, ldaps://, and s3:// as a link."""
+        text = "This is a Lightweight Directory Access Protocol URL pattern: ldap://[<host>[:<port>]][/<dn> [?[<attributes>][?[<scope>][?[<filter>][?<extensions>]]]]]"
+        assert docformatter.do_find_links(text) == [(61, 91)]
+        text = "This is a Secure Lightweight Directory Access Protocol URL pattern: ldaps://[<host>[:<port>]][/<dn> [?[<attributes>][?[<scope>][?[<filter>][?<extensions>]]]]]"
+        assert docformatter.do_find_links(text) == [(68, 99)]
+        text = (
+            "This is an Amazon S3 bucket URL pattern: s3://mybucket/puppy.jpg"
+        )
+        assert docformatter.do_find_links(text) == [(41, 64)]
+
+    @pytest.mark.unit
+    def test_do_find_news_link(self):
+        """Identify news: and nntp:// as a link."""
+        text = "This is a Usenet newsgroup URL pattern: news:<newsgroupname>"
+        assert docformatter.do_find_links(text) == [(40, 60)]
+        text = "This is a Network News Transfer Protocol URL pattern: nntp://<host>:<port>/<newsgroup-name>/<article-number>"
+        assert docformatter.do_find_links(text) == [(54, 108)]
+
+    @pytest.mark.unit
+    def test_do_find_initiation_link(self):
+        """Identify sip:, sips:, and snmp:// as a link."""
+        text = "This is a Session Initiation Protocol URL pattern: sip:<user>[:<password>]@<host>[:<port>][;<uri-parameters>][?<headers>]"
+        assert docformatter.do_find_links(text) == [(51, 121)]
+        text = "This is a Secure Session Initiation Protocol URL pattern: sips:<user>[:<password>]@<host>[:<port>][;<uri-parameters>][?<headers>]"
+        assert docformatter.do_find_links(text) == [(58, 129)]
+        text = "This is a Simple Network Management Protocol URL pattern: snmp://[user@]host[:port][/[<context>[;<contextEngineID>]][/<oid>]]"
+        assert docformatter.do_find_links(text) == [(58, 125)]
+
+
+class TestDoSkipLink:
+    """Class for testing the do_skip_links() function."""
+
+    @pytest.mark.unit
+    def test_do_skip_only_link_pattern(self):
+        """Don't treat things like 's3://' or 'file://' as links.
+
+        See issue #150.
+        """
+        text = (
+            "Directories are implicitly created.  The accepted URL can start "
+            "with 's3://' to refer to files on s3.  Local files can be "
+            "prefixed with 'file://' (but it is not needed!)"
+        )
+        assert docformatter.do_skip_link(text, (70, 76))
+        assert docformatter.do_skip_link(text, (137, 145))
+
+    @pytest.mark.unit
+    def test_do_skip_already_wrapped_link(self):
+        """Skip links that were already wrapped by the user.
+
+        See issue #150.
+        """
+        text = (
+            "The text file can be retrieved via the Chrome plugin `Get "
+            "Cookies.txt <https://chrome.google.com/webstore/detail/get-"
+            "cookiestxt/bgaddhkoddajcdgocldbbfleckgcbcid>` while browsing."
+        )
+        assert docformatter.do_skip_link(text, (70, 117))
+
+
+class TestIsSomeSortOfList:
     """Class for testing the is_some_sort_of_list() function."""
 
     @pytest.mark.unit


----

problem_statement: Doc formatter reformats correct docstring
with `docformatter 1.4`, command `docformatter --wrap-summaries 100 --wrap-descriptions 100`
the following docstring
```
"""hello yeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeet -v
"""
```
gets transformed to
```
"""hello yeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeet
-v."""
```
which is fine (correct), but then if I run docformatter again, the correct string incorrectly becomes
```
"""hello yeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeet.

-v.
"""
```
date: 2023-04-22
patch: diff --git a/pyproject.toml b/pyproject.toml
index 165f478..da52ac2 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
 [tool.poetry]
 name = "docformatter"
-version = "1.6.1"
+version = "1.6.2"
 description = "Formats docstrings to follow PEP 257"
 authors = ["Steven Myint"]
 maintainers = [
diff --git a/src/docformatter/__pkginfo__.py b/src/docformatter/__pkginfo__.py
index fdd5d06..615cc72 100644
--- a/src/docformatter/__pkginfo__.py
+++ b/src/docformatter/__pkginfo__.py
@@ -23,4 +23,4 @@
 # SOFTWARE.
 """Package information for docformatter."""
 
-__version__ = "1.6.1"
+__version__ = "1.6.2"