Repository: breathe-doc/breathe (language: python)

Issue:
Introduce formatter for the code base
To my understanding we don't use a formatter for the python code in this project? I'm become quite used to autoformatted code through elm-format and prettier. 

Are we happy to introduce one? Is there a strong preference? I've used [Black](https://github.com/psf/black) on some other python projects and am happy enough with that. It seems to be under the python software foundation github organisation but I don't know if it is in some way official. Would that suit?


Discussion / hints:
I'm not too familiar with formatters for Python, but sometimes there are code fragments where the automatic formatting doesn't fit. Though I don't know if there some of these in Breathe, I usually just make ``flake8`` happy manually :-).
I can't see it in the documentation but `black` does seem to support disabling formatting for certain sections using comments before and after: https://github.com/psf/black/issues/451#issuecomment-412977023

I realise that can be useful in certain situations though I like leaning into the autoformatted style as much as possible generally.
I'm using black in some of my projecs and it works nicely.

Fix patch:
diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
index e5c3d0ada..6b8962e6a 100644
--- a/.github/workflows/lint.yml
+++ b/.github/workflows/lint.yml
@@ -19,10 +19,12 @@ jobs:
       - name: install dependencies
         run: |
           pip install -r requirements/development.txt
-          pip install flake8
 
       - name: lint the source code
         run: make flake8
 
+      - name: check source code formatting
+        run: make black
+
       - name: type check the source code
         run: make type-check
diff --git a/Makefile b/Makefile
index 129a441e6..c0b9706b4 100644
--- a/Makefile
+++ b/Makefile
@@ -43,6 +43,10 @@ flake8:
 		breathe/renderer/filter.py \
 		breathe/parser/compound.py
 
+.PHONY: black
+black:
+	black --check .
+
 .PHONY: type-check
 type-check:
 	mypy breathe tests
diff --git a/breathe-apidoc.py b/breathe-apidoc.py
index 79b659a33..a34378120 100755
--- a/breathe-apidoc.py
+++ b/breathe-apidoc.py
@@ -3,6 +3,7 @@
 
 import sys
 
-if __name__ == '__main__':
+if __name__ == "__main__":
     from breathe.apidoc import main
+
     sys.exit(main())
diff --git a/breathe/__init__.py b/breathe/__init__.py
index 41c569e2e..060f3e5da 100644
--- a/breathe/__init__.py
+++ b/breathe/__init__.py
@@ -4,7 +4,7 @@
 
 from sphinx.application import Sphinx
 
-__version__ = '4.31.0'
+__version__ = "4.31.0"
 
 
 def setup(app: Sphinx):
@@ -12,8 +12,4 @@ def setup(app: Sphinx):
     file_state_cache_setup(app)
     renderer_setup(app)
 
-    return {
-        'version': __version__,
-        'parallel_read_safe': True,
-        'parallel_write_safe': True
-    }
+    return {"version": __version__, "parallel_read_safe": True, "parallel_write_safe": True}
diff --git a/breathe/apidoc.py b/breathe/apidoc.py
index ac73ecc5e..e7ff33eb9 100644
--- a/breathe/apidoc.py
+++ b/breathe/apidoc.py
@@ -35,22 +35,18 @@
 # Reference: Doxygen XSD schema file, CompoundKind only
 # Only what breathe supports are included
 # Translates identifier to English
-TYPEDICT = {'class': 'Class',
-            'interface': 'Interface',
-            'struct': 'Struct',
-            'union': 'Union',
-            'file': 'File',
-            'namespace': 'Namespace',
-            'group': 'Group'}
+TYPEDICT = {
+    "class": "Class",
+    "interface": "Interface",
+    "struct": "Struct",
+    "union": "Union",
+    "file": "File",
+    "namespace": "Namespace",
+    "group": "Group",
+}
 
 # Types that accept the :members: option.
-MEMBERS_TYPES = [
-        'class',
-        'group',
-        'interface',
-        'namespace',
-        'struct'
-]
+MEMBERS_TYPES = ["class", "group", "interface", "namespace", "struct"]
 
 
 def print_info(msg, args):
@@ -60,14 +56,14 @@ def print_info(msg, args):
 
 def write_file(name, text, args):
     """Write the output file for module/package <name>."""
-    fname = os.path.join(args.destdir, '%s.%s' % (name, args.suffix))
+    fname = os.path.join(args.destdir, "%s.%s" % (name, args.suffix))
     if args.dryrun:
-        print_info('Would create file %s.' % fname, args)
+        print_info("Would create file %s." % fname, args)
         return
     if not args.force and os.path.isfile(fname):
-        print_info('File %s already exists, skipping.' % fname, args)
+        print_info("File %s already exists, skipping." % fname, args)
     else:
-        print_info('Creating file %s.' % fname, args)
+        print_info("Creating file %s." % fname, args)
         if not os.path.exists(os.path.dirname(fname)):
             try:
                 os.makedirs(os.path.dirname(fname))
@@ -75,32 +71,34 @@ def write_file(name, text, args):
                 if exc.errno != errno.EEXIST:
                     raise
         try:
-            with open(fname, 'r') as target:
+            with open(fname, "r") as target:
                 orig = target.read()
                 if orig == text:
-                    print_info('File %s up to date, skipping.' % fname, args)
+                    print_info("File %s up to date, skipping." % fname, args)
                     return
         except FileNotFoundError:
             # Don't mind if it isn't there
             pass
 
-        with open(fname, 'w') as target:
+        with open(fname, "w") as target:
             target.write(text)
 
 
 def format_heading(level, text):
     """Create a heading of <level> [1, 2 or 3 supported]."""
-    underlining = ['=', '-', '~', ][level - 1] * len(text)
-    return '%s\n%s\n\n' % (text, underlining)
+    underlining = ["=", "-", "~",][
+        level - 1
+    ] * len(text)
+    return "%s\n%s\n\n" % (text, underlining)
 
 
 def format_directive(package_type, package, args):
     """Create the breathe directive and add the options."""
-    directive = '.. doxygen%s:: %s\n' % (package_type, package)
+    directive = ".. doxygen%s:: %s\n" % (package_type, package)
     if args.project:
-        directive += '   :project: %s\n' % args.project
+        directive += "   :project: %s\n" % args.project
     if args.members and package_type in MEMBERS_TYPES:
-        directive += '   :members:\n'
+        directive += "   :members:\n"
     return directive
 
 
@@ -109,7 +107,7 @@ def create_package_file(package, package_type, package_id, args):
     # Skip over types that weren't requested
     if package_type not in args.outtypes:
         return
-    text = format_heading(1, '%s %s' % (TYPEDICT[package_type], package))
+    text = format_heading(1, "%s %s" % (TYPEDICT[package_type], package))
     text += format_directive(package_type, package, args)
 
     write_file(os.path.join(package_type, package_id), text, args)
@@ -119,12 +117,12 @@ def create_modules_toc_file(key, value, args):
     """Create the module's index."""
     if not os.path.isdir(os.path.join(args.destdir, key)):
         return
-    text = format_heading(1, '%s list' % value)
-    text += '.. toctree::\n'
-    text += '   :glob:\n\n'
-    text += '   %s/*\n' % key
+    text = format_heading(1, "%s list" % value)
+    text += ".. toctree::\n"
+    text += "   :glob:\n\n"
+    text += "   %s/*\n" % key
 
-    write_file('%slist' % key, text, args)
+    write_file("%slist" % key, text, args)
 
 
 def recurse_tree(args):
@@ -132,22 +130,23 @@ def recurse_tree(args):
     Look for every file in the directory tree and create the corresponding
     ReST files.
     """
-    index = xml.etree.ElementTree.parse(os.path.join(args.rootpath, 'index.xml'))
+    index = xml.etree.ElementTree.parse(os.path.join(args.rootpath, "index.xml"))
 
     # Assuming this is a valid Doxygen XML
     for compound in index.getroot():
-        create_package_file(compound.findtext('name'), compound.get('kind'),
-                            compound.get('refid'), args)
+        create_package_file(
+            compound.findtext("name"), compound.get("kind"), compound.get("refid"), args
+        )
 
 
 class TypeAction(argparse.Action):
     def __init__(self, option_strings, dest, **kwargs):
         super(TypeAction, self).__init__(option_strings, dest, **kwargs)
         self.default = TYPEDICT.keys()
-        self.metavar = ','.join(TYPEDICT.keys())
+        self.metavar = ",".join(TYPEDICT.keys())
 
     def __call__(self, parser, namespace, values, option_string=None):
-        value_list = values.split(',')
+        value_list = values.split(",")
         for value in value_list:
             if value not in TYPEDICT:
                 raise ValueError("%s not a valid option" % value)
@@ -162,39 +161,79 @@ def main():
 breathe generation directives per definition in the <DESTDIR>.
 
 Note: By default this script will not overwrite already created files.""",
-        formatter_class=argparse.RawDescriptionHelpFormatter)
-
-    parser.add_argument('-o', '--output-dir', action='store', dest='destdir',
-                        help='Directory to place all output', required=True)
-    parser.add_argument('-f', '--force', action='store_true', dest='force',
-                        help='Overwrite existing files')
-    parser.add_argument('-m', '--members', action='store_true', dest='members',
-                        help='Include members for types: %s' % MEMBERS_TYPES)
-    parser.add_argument('-n', '--dry-run', action='store_true', dest='dryrun',
-                        help='Run the script without creating files')
-    parser.add_argument('-T', '--no-toc', action='store_true', dest='notoc',
-                        help='Don\'t create a table of contents file')
-    parser.add_argument('-s', '--suffix', action='store', dest='suffix',
-                        help='file suffix (default: rst)', default='rst')
-    parser.add_argument('-p', '--project', action='store', dest='project',
-                        help='project to add to generated directives')
-    parser.add_argument('-g', '--generate', action=TypeAction, dest='outtypes',
-                        help='types of output to generate, comma-separated list')
-    parser.add_argument('-q', '--quiet', action='store_true', dest='quiet',
-                        help='suppress informational messages')
-    parser.add_argument('--version', action='version',
-                        version='Breathe (breathe-apidoc) %s' % __version__)
-    parser.add_argument('rootpath', type=str,
-                        help='The directory contains index.xml')
+        formatter_class=argparse.RawDescriptionHelpFormatter,
+    )
+
+    parser.add_argument(
+        "-o",
+        "--output-dir",
+        action="store",
+        dest="destdir",
+        help="Directory to place all output",
+        required=True,
+    )
+    parser.add_argument(
+        "-f", "--force", action="store_true", dest="force", help="Overwrite existing files"
+    )
+    parser.add_argument(
+        "-m",
+        "--members",
+        action="store_true",
+        dest="members",
+        help="Include members for types: %s" % MEMBERS_TYPES,
+    )
+    parser.add_argument(
+        "-n",
+        "--dry-run",
+        action="store_true",
+        dest="dryrun",
+        help="Run the script without creating files",
+    )
+    parser.add_argument(
+        "-T",
+        "--no-toc",
+        action="store_true",
+        dest="notoc",
+        help="Don't create a table of contents file",
+    )
+    parser.add_argument(
+        "-s",
+        "--suffix",
+        action="store",
+        dest="suffix",
+        help="file suffix (default: rst)",
+        default="rst",
+    )
+    parser.add_argument(
+        "-p",
+        "--project",
+        action="store",
+        dest="project",
+        help="project to add to generated directives",
+    )
+    parser.add_argument(
+        "-g",
+        "--generate",
+        action=TypeAction,
+        dest="outtypes",
+        help="types of output to generate, comma-separated list",
+    )
+    parser.add_argument(
+        "-q", "--quiet", action="store_true", dest="quiet", help="suppress informational messages"
+    )
+    parser.add_argument(
+        "--version", action="version", version="Breathe (breathe-apidoc) %s" % __version__
+    )
+    parser.add_argument("rootpath", type=str, help="The directory contains index.xml")
     args = parser.parse_args()
 
-    if args.suffix.startswith('.'):
+    if args.suffix.startswith("."):
         args.suffix = args.suffix[1:]
     if not os.path.isdir(args.rootpath):
-        print('%s is not a directory.' % args.rootpath, file=sys.stderr)
+        print("%s is not a directory." % args.rootpath, file=sys.stderr)
         sys.exit(1)
-    if 'index.xml' not in os.listdir(args.rootpath):
-        print('%s does not contain a index.xml' % args.rootpath, file=sys.stderr)
+    if "index.xml" not in os.listdir(args.rootpath):
+        print("%s does not contain a index.xml" % args.rootpath, file=sys.stderr)
         sys.exit(1)
     if not os.path.isdir(args.destdir):
         if not args.dryrun:
diff --git a/breathe/directives/__init__.py b/breathe/directives/__init__.py
index b95fd4834..70d19157a 100644
--- a/breathe/directives/__init__.py
+++ b/breathe/directives/__init__.py
@@ -21,14 +21,19 @@ def __init__(self, state, context: Dict[str, Any]) -> None:
         self.state = state
         self.context = context
 
-    def warn(self, raw_text: str, *, rendered_nodes: Sequence[nodes.Node] = None,
-             unformatted_suffix: str = '') -> List[nodes.Node]:
+    def warn(
+        self,
+        raw_text: str,
+        *,
+        rendered_nodes: Sequence[nodes.Node] = None,
+        unformatted_suffix: str = ""
+    ) -> List[nodes.Node]:
         raw_text = self.format(raw_text) + unformatted_suffix
         if rendered_nodes is None:
             rendered_nodes = [nodes.paragraph("", "", nodes.Text(raw_text))]
         return [
             nodes.warning("", *rendered_nodes),
-            self.state.document.reporter.warning(raw_text, line=self.context['lineno'])
+            self.state.document.reporter.warning(raw_text, line=self.context["lineno"]),
         ]
 
     def format(self, text: str) -> str:
@@ -42,10 +47,13 @@ class BaseDirective(SphinxDirective):
     has_content: bool
     final_argument_whitespace: bool
 
-    def __init__(self, finder_factory: FinderFactory,
-                 project_info_factory: ProjectInfoFactory,
-                 parser_factory: DoxygenParserFactory,
-                 *args) -> None:
+    def __init__(
+        self,
+        finder_factory: FinderFactory,
+        project_info_factory: ProjectInfoFactory,
+        parser_factory: DoxygenParserFactory,
+        *args
+    ) -> None:
         super().__init__(*args)
         self.directive_args = list(args)  # Convert tuple to list to allow modification.
 
@@ -61,22 +69,23 @@ def kind(self) -> str:
     def create_warning(self, project_info: Optional[ProjectInfo], **kwargs) -> _WarningHandler:
         if project_info:
             tail = 'in doxygen xml output for project "{project}" from directory: {path}'.format(
-                project=project_info.name(),
-                path=project_info.project_path()
+                project=project_info.name(), path=project_info.project_path()
             )
         else:
-            tail = ''
+            tail = ""
 
-        context = dict(
-            lineno=self.lineno,
-            tail=tail,
-            **kwargs
-        )
+        context = dict(lineno=self.lineno, tail=tail, **kwargs)
         return _WarningHandler(self.state, context)
 
-    def render(self, node_stack, project_info: ProjectInfo, filter_: Filter,
-               target_handler: TargetHandler, mask_factory: MaskFactoryBase,
-               directive_args) -> List[Node]:
+    def render(
+        self,
+        node_stack,
+        project_info: ProjectInfo,
+        filter_: Filter,
+        target_handler: TargetHandler,
+        mask_factory: MaskFactoryBase,
+        directive_args,
+    ) -> List[Node]:
         "Standard render process used by subclasses"
 
         try:
@@ -88,14 +97,16 @@ def render(self, node_stack, project_info: ProjectInfo, filter_: Filter,
                 self.state.document,
                 target_handler,
                 self.parser_factory.create_compound_parser(project_info),
-                filter_
+                filter_,
             )
         except ParserError as e:
-            return format_parser_error("doxygenclass", e.error, e.filename, self.state,
-                                       self.lineno, True)
+            return format_parser_error(
+                "doxygenclass", e.error, e.filename, self.state, self.lineno, True
+            )
         except FileIOError as e:
-            return format_parser_error("doxygenclass", e.error, e.filename, self.state,
-                                       self.lineno, True)
+            return format_parser_error(
+                "doxygenclass", e.error, e.filename, self.state, self.lineno, True
+            )
 
         context = RenderContext(node_stack, mask_factory, directive_args)
         return object_renderer.render(node_stack[0], context)
diff --git a/breathe/directives/class_like.py b/breathe/directives/class_like.py
index b94f83af2..ca452b2de 100644
--- a/breathe/directives/class_like.py
+++ b/breathe/directives/class_like.py
@@ -36,13 +36,13 @@ def run(self) -> List[Node]:
             project_info = self.project_info_factory.create_project_info(self.options)
         except ProjectError as e:
             warning = self.create_warning(None, kind=self.kind)
-            return warning.warn('doxygen{kind}: %s' % e)
+            return warning.warn("doxygen{kind}: %s" % e)
 
         try:
             finder = self.finder_factory.create_finder(project_info)
         except MTimeError as e:
             warning = self.create_warning(None, kind=self.kind)
-            return warning.warn('doxygen{kind}: %s' % e)
+            return warning.warn("doxygen{kind}: %s" % e)
 
         finder_filter = self.filter_factory.create_compound_finder_filter(name, self.kind)
 
@@ -58,8 +58,9 @@ def run(self) -> List[Node]:
         filter_ = self.filter_factory.create_class_filter(name, self.options)
 
         mask_factory = NullMaskFactory()
-        return self.render(matches[0], project_info, filter_, target_handler, mask_factory,
-                           self.directive_args)
+        return self.render(
+            matches[0], project_info, filter_, target_handler, mask_factory, self.directive_args
+        )
 
 
 class DoxygenClassDirective(_DoxygenClassLikeDirective):
diff --git a/breathe/directives/content_block.py b/breathe/directives/content_block.py
index f0fd20abf..c40a4359d 100644
--- a/breathe/directives/content_block.py
+++ b/breathe/directives/content_block.py
@@ -26,7 +26,7 @@ class _DoxygenContentBlockDirective(BaseDirective):
         "protected-members": flag,
         "private-members": flag,
         "undoc-members": flag,
-        "no-link": flag
+        "no-link": flag,
     }
     has_content = False
 
@@ -37,13 +37,13 @@ def run(self) -> List[Node]:
             project_info = self.project_info_factory.create_project_info(self.options)
         except ProjectError as e:
             warning = self.create_warning(None, kind=self.kind)
-            return warning.warn('doxygen{kind}: %s' % e)
+            return warning.warn("doxygen{kind}: %s" % e)
 
         try:
             finder = self.finder_factory.create_finder(project_info)
         except MTimeError as e:
             warning = self.create_warning(None, kind=self.kind)
-            return warning.warn('doxygen{kind}: %s' % e)
+            return warning.warn("doxygen{kind}: %s" % e)
 
         finder_filter = self.filter_factory.create_finder_filter(self.kind, name)
 
@@ -57,15 +57,16 @@ def run(self) -> List[Node]:
             warning = self.create_warning(project_info, name=name, kind=self.kind)
             return warning.warn('doxygen{kind}: Cannot find {kind} "{name}" {tail}')
 
-        if 'content-only' in self.options and self.kind != "page":
+        if "content-only" in self.options and self.kind != "page":
             # Unpack the single entry in the matches list
             (node_stack,) = matches
 
             filter_ = self.filter_factory.create_content_filter(self.kind, self.options)
             # Having found the compound node for the namespace or group in the index we want to grab
             # the contents of it which match the filter
-            contents_finder = self.finder_factory.create_finder_from_root(node_stack[0],
-                                                                          project_info)
+            contents_finder = self.finder_factory.create_finder_from_root(
+                node_stack[0], project_info
+            )
             # TODO: find a more specific type for the Doxygen nodes
             contents = []  # type: List[Any]
             contents_finder.filter_(filter_, contents)
@@ -86,7 +87,7 @@ def run(self) -> List[Node]:
                 self.state.document,
                 target_handler,
                 self.parser_factory.create_compound_parser(project_info),
-                filter_
+                filter_,
             )
 
             mask_factory = NullMaskFactory()
diff --git a/breathe/directives/file.py b/breathe/directives/file.py
index 8342993c4..4d77d54b6 100644
--- a/breathe/directives/file.py
+++ b/breathe/directives/file.py
@@ -45,7 +45,7 @@ def handle_contents(self, file_, project_info):
                 self.state.document,
                 target_handler,
                 self.parser_factory.create_compound_parser(project_info),
-                filter_
+                filter_,
             )
 
             mask_factory = NullMaskFactory()
@@ -56,7 +56,7 @@ def handle_contents(self, file_, project_info):
 
 
 class DoxygenFileDirective(_BaseFileDirective):
-    directive_name = 'doxygenfile'
+    directive_name = "doxygenfile"
 
     required_arguments = 0
     optional_arguments = 3
@@ -77,13 +77,13 @@ def run(self):
             project_info = self.project_info_factory.create_project_info(self.options)
         except ProjectError as e:
             warning = self.create_warning(None)
-            return warning.warn('doxygenfile: %s' % e)
+            return warning.warn("doxygenfile: %s" % e)
 
         return self.handle_contents(file_, project_info)
 
 
 class AutoDoxygenFileDirective(_BaseFileDirective):
-    directive_name = 'autodoxygenfile'
+    directive_name = "autodoxygenfile"
 
     required_arguments = 1
     option_spec = {
@@ -104,6 +104,6 @@ def run(self):
             project_info = self.project_info_factory.retrieve_project_info_for_auto(self.options)
         except ProjectError as e:
             warning = self.create_warning(None)
-            return warning.warn('autodoxygenfile: %s' % e)
+            return warning.warn("autodoxygenfile: %s" % e)
 
         return self.handle_contents(file_, project_info)
diff --git a/breathe/directives/function.py b/breathe/directives/function.py
index 2634e0477..54fc56093 100644
--- a/breathe/directives/function.py
+++ b/breathe/directives/function.py
@@ -5,9 +5,7 @@
 from breathe.project import ProjectError
 from breathe.renderer import format_parser_error, RenderContext
 from breathe.renderer.sphinxrenderer import WithContext
-from breathe.renderer.mask import (
-    MaskFactory, NullMaskFactory, NoParameterNamesMask
-)
+from breathe.renderer.mask import MaskFactory, NullMaskFactory, NoParameterNamesMask
 from breathe.renderer.sphinxrenderer import SphinxRenderer
 from breathe.renderer.target import create_target_handler
 
@@ -61,13 +59,13 @@ def run(self) -> List[Node]:
             project_info = self.project_info_factory.create_project_info(self.options)
         except ProjectError as e:
             warning = self.create_warning(None)
-            return warning.warn('doxygenfunction: %s' % e)
+            return warning.warn("doxygenfunction: %s" % e)
 
         try:
             finder = self.finder_factory.create_finder(project_info)
         except MTimeError as e:
             warning = self.create_warning(None)
-            return warning.warn('doxygenfunction: %s' % e)
+            return warning.warn("doxygenfunction: %s" % e)
 
         # Extract arguments from the function name.
         try:
@@ -75,16 +73,19 @@ def run(self) -> List[Node]:
         except cpp.DefinitionError as e:
             return self.create_warning(
                 project_info,
-                namespace='%s::' % namespace if namespace else '',
+                namespace="%s::" % namespace if namespace else "",
                 function=function_name,
                 args=str(args),
-                cpperror=str(e)
-            ).warn('doxygenfunction: Unable to resolve function '
-                   '"{namespace}{function}" with arguments "{args}".\n'
-                   'Could not parse arguments. Parsing eror is\n{cpperror}')
+                cpperror=str(e),
+            ).warn(
+                "doxygenfunction: Unable to resolve function "
+                '"{namespace}{function}" with arguments "{args}".\n'
+                "Could not parse arguments. Parsing eror is\n{cpperror}"
+            )
 
         finder_filter = self.filter_factory.create_function_and_all_friend_finder_filter(
-            namespace, function_name)
+            namespace, function_name
+        )
 
         # TODO: find a more specific type for the Doxygen nodes
         matchesAll = []  # type: List[Any]
@@ -94,7 +95,7 @@ def run(self) -> List[Node]:
             # only take functions and friend functions
             # ignore friend classes
             node = m[0]
-            if node.kind == 'friend' and not node.argsstring:
+            if node.kind == "friend" and not node.argsstring:
                 continue
             matches.append(m)
 
@@ -102,55 +103,61 @@ def run(self) -> List[Node]:
         # clauses below
         warning = self.create_warning(
             project_info,
-            namespace='%s::' % namespace if namespace else '',
+            namespace="%s::" % namespace if namespace else "",
             function=function_name,
-            args=str(args)
+            args=str(args),
         )
 
         try:
             node_stack = self._resolve_function(matches, args, project_info)
         except _NoMatchingFunctionError:
-            return warning.warn('doxygenfunction: Cannot find function "{namespace}{function}" '
-                                '{tail}')
+            return warning.warn(
+                'doxygenfunction: Cannot find function "{namespace}{function}" ' "{tail}"
+            )
         except _UnableToResolveFunctionError as error:
-            message = 'doxygenfunction: Unable to resolve function ' \
-                '"{namespace}{function}" with arguments {args} {tail}.\n' \
-                'Potential matches:\n'
+            message = (
+                "doxygenfunction: Unable to resolve function "
+                '"{namespace}{function}" with arguments {args} {tail}.\n'
+                "Potential matches:\n"
+            )
 
-            text = ''
+            text = ""
             for i, entry in enumerate(sorted(error.signatures)):
-                text += '- %s\n' % entry
-            block = nodes.literal_block('', '', nodes.Text(text))
+                text += "- %s\n" % entry
+            block = nodes.literal_block("", "", nodes.Text(text))
             formatted_message = warning.format(message)
-            warning_nodes = [
-                nodes.paragraph("", "", nodes.Text(formatted_message)),
-                block
-            ]
-            result = warning.warn(message, rendered_nodes=warning_nodes,
-                                  unformatted_suffix=text)
+            warning_nodes = [nodes.paragraph("", "", nodes.Text(formatted_message)), block]
+            result = warning.warn(message, rendered_nodes=warning_nodes, unformatted_suffix=text)
             return result
         except cpp.DefinitionError as error:
-            warning.context['cpperror'] = str(error)
+            warning.context["cpperror"] = str(error)
             return warning.warn(
-                'doxygenfunction: Unable to resolve function '
+                "doxygenfunction: Unable to resolve function "
                 '"{namespace}{function}" with arguments "{args}".\n'
-                'Candidate function could not be parsed. Parsing error is\n{cpperror}')
+                "Candidate function could not be parsed. Parsing error is\n{cpperror}"
+            )
 
         target_handler = create_target_handler(self.options, project_info, self.state.document)
         filter_ = self.filter_factory.create_outline_filter(self.options)
 
-        return self.render(node_stack, project_info, filter_, target_handler, NullMaskFactory(),
-                           self.directive_args)
+        return self.render(
+            node_stack,
+            project_info,
+            filter_,
+            target_handler,
+            NullMaskFactory(),
+            self.directive_args,
+        )
 
     def _parse_args(self, function_description: str) -> Optional[cpp.ASTParametersQualifiers]:
         # Note: the caller must catch cpp.DefinitionError
-        if function_description == '':
+        if function_description == "":
             return None
 
-        parser = cpp.DefinitionParser(function_description,
-                                      location=self.get_source_info(),
-                                      config=self.config)
-        paramQual = parser._parse_parameters_and_qualifiers(paramMode='function')
+        parser = cpp.DefinitionParser(
+            function_description, location=self.get_source_info(), config=self.config
+        )
+        paramQual = parser._parse_parameters_and_qualifiers(paramMode="function")
         # strip everything that doesn't contribute to overloading
 
         def stripParamQual(paramQual):
@@ -168,23 +175,26 @@ def stripParamQual(paramQual):
                 declarator = p.arg.type.decl
 
                 def stripDeclarator(declarator):
-                    if hasattr(declarator, 'next'):
+                    if hasattr(declarator, "next"):
                         stripDeclarator(declarator.next)
                         if isinstance(declarator, cpp.ASTDeclaratorParen):
-                            assert hasattr(declarator, 'inner')
+                            assert hasattr(declarator, "inner")
                             stripDeclarator(declarator.inner)
                     else:
                         assert isinstance(declarator, cpp.ASTDeclaratorNameParamQual)
-                        assert hasattr(declarator, 'declId')
+                        assert hasattr(declarator, "declId")
                         declarator.declId = None  # type: ignore
                         if declarator.paramQual is not None:
                             stripParamQual(declarator.paramQual)
+
                 stripDeclarator(declarator)
+
         stripParamQual(paramQual)
         return paramQual
 
-    def _create_function_signature(self, node_stack, project_info, filter_, target_handler,
-                                   mask_factory, directive_args) -> str:
+    def _create_function_signature(
+        self, node_stack, project_info, filter_, target_handler, mask_factory, directive_args
+    ) -> str:
         "Standard render process used by subclasses"
 
         try:
@@ -199,11 +209,13 @@ def _create_function_signature(self, node_stack, project_info, filter_, target_h
                 filter_,
             )
         except ParserError as e:
-            return format_parser_error("doxygenclass", e.error, e.filename, self.state,
-                                       self.lineno, True)
+            return format_parser_error(
+                "doxygenclass", e.error, e.filename, self.state, self.lineno, True
+            )
         except FileIOError as e:
-            return format_parser_error("doxygenclass", e.error, e.filename, self.state,
-                                       self.lineno, False)
+            return format_parser_error(
+                "doxygenclass", e.error, e.filename, self.state, self.lineno, False
+            )
 
         context = RenderContext(node_stack, mask_factory, directive_args)
         node = node_stack[0]
@@ -211,16 +223,18 @@ def _create_function_signature(self, node_stack, project_info, filter_, target_h
             # this part should be kept in sync with visit_function in sphinxrenderer
             name = node.get_name()
             # assume we are only doing this for C++ declarations
-            declaration = ' '.join([
-                object_renderer.create_template_prefix(node),
-                ''.join(n.astext() for n in object_renderer.render(node.get_type())),
-                name,
-                node.get_argsstring()
-            ])
-        parser = cpp.DefinitionParser(declaration,
-                                      location=self.get_source_info(),
-                                      config=self.config)
-        ast = parser.parse_declaration('function', 'function')
+            declaration = " ".join(
+                [
+                    object_renderer.create_template_prefix(node),
+                    "".join(n.astext() for n in object_renderer.render(node.get_type())),
+                    name,
+                    node.get_argsstring(),
+                ]
+            )
+        parser = cpp.DefinitionParser(
+            declaration, location=self.get_source_info(), config=self.config
+        )
+        ast = parser.parse_declaration("function", "function")
         return str(ast)
 
     def _resolve_function(self, matches, args: Optional[cpp.ASTParametersQualifiers], project_info):
@@ -230,21 +244,22 @@ def _resolve_function(self, matches, args: Optional[cpp.ASTParametersQualifiers]
         res = []
         candSignatures = []
         for entry in matches:
-            text_options = {'no-link': u'', 'outline': u''}
+            text_options = {"no-link": u"", "outline": u""}
 
             # Render the matches to docutils nodes
-            target_handler = create_target_handler({'no-link': u''},
-                                                   project_info, self.state.document)
+            target_handler = create_target_handler(
+                {"no-link": u""}, project_info, self.state.document
+            )
             filter_ = self.filter_factory.create_outline_filter(text_options)
-            mask_factory = MaskFactory({'param': NoParameterNamesMask})
+            mask_factory = MaskFactory({"param": NoParameterNamesMask})
 
             # Override the directive args for this render
             directive_args = self.directive_args[:]
             directive_args[2] = text_options
 
-            signature = self._create_function_signature(entry, project_info, filter_,
-                                                        target_handler,
-                                                        mask_factory, directive_args)
+            signature = self._create_function_signature(
+                entry, project_info, filter_, target_handler, mask_factory, directive_args
+            )
             candSignatures.append(signature)
 
             if args is not None:
diff --git a/breathe/directives/index.py b/breathe/directives/index.py
index 517c0f013..9b1dda112 100644
--- a/breathe/directives/index.py
+++ b/breathe/directives/index.py
@@ -15,8 +15,7 @@ class RootDataObject:
 
 
 class _BaseIndexDirective(BaseDirective):
-    """Base class handle the main work when given the appropriate project info to work from.
-    """
+    """Base class handle the main work when given the appropriate project info to work from."""
 
     # We use inheritance here rather than a separate object and composition, because so much
     # information is present in the Directive class from the docutils framework that we'd have to
@@ -26,8 +25,9 @@ def handle_contents(self, project_info):
         try:
             finder = self.finder_factory.create_finder(project_info)
         except ParserError as e:
-            return format_parser_error(self.name, e.error, e.filename, self.state,
-                                       self.lineno, True)
+            return format_parser_error(
+                self.name, e.error, e.filename, self.state, self.lineno, True
+            )
         except FileIOError as e:
             return format_parser_error(self.name, e.error, e.filename, self.state, self.lineno)
 
@@ -44,18 +44,18 @@ def handle_contents(self, project_info):
             self.state.document,
             target_handler,
             self.parser_factory.create_compound_parser(project_info),
-            filter_
+            filter_,
         )
 
         mask_factory = NullMaskFactory()
-        context = RenderContext([data_object, RootDataObject()], mask_factory,
-                                self.directive_args)
+        context = RenderContext([data_object, RootDataObject()], mask_factory, self.directive_args)
 
         try:
             node_list = object_renderer.render(context.node_stack[0], context)
         except ParserError as e:
-            return format_parser_error(self.name, e.error, e.filename, self.state,
-                                       self.lineno, True)
+            return format_parser_error(
+                self.name, e.error, e.filename, self.state, self.lineno, True
+            )
         except FileIOError as e:
             return format_parser_error(self.name, e.error, e.filename, self.state, self.lineno)
 
@@ -80,7 +80,7 @@ def run(self):
             project_info = self.project_info_factory.create_project_info(self.options)
         except ProjectError as e:
             warning = self.create_warning(None)
-            return warning.warn('doxygenindex: %s' % e)
+            return warning.warn("doxygenindex: %s" % e)
 
         return self.handle_contents(project_info)
 
@@ -104,6 +104,6 @@ def run(self):
             project_info = self.project_info_factory.retrieve_project_info_for_auto(self.options)
         except ProjectError as e:
             warning = self.create_warning(None)
-            return warning.warn('autodoxygenindex: %s' % e)
+            return warning.warn("autodoxygenindex: %s" % e)
 
         return self.handle_contents(project_info)
diff --git a/breathe/directives/item.py b/breathe/directives/item.py
index 786da8bdc..234f16851 100644
--- a/breathe/directives/item.py
+++ b/breathe/directives/item.py
@@ -20,14 +20,13 @@ class _DoxygenBaseItemDirective(BaseDirective):
         "project": unchanged_required,
         "outline": flag,
         "no-link": flag,
-        }
+    }
     has_content = False
 
     def create_finder_filter(self, namespace: str, name: str) -> Filter:
         """Creates a filter to find the node corresponding to this item."""
 
-        return self.filter_factory.create_member_finder_filter(
-            namespace, name, self.kind)
+        return self.filter_factory.create_member_finder_filter(namespace, name, self.kind)
 
     def run(self) -> List[Node]:
         try:
@@ -39,13 +38,13 @@ def run(self) -> List[Node]:
             project_info = self.project_info_factory.create_project_info(self.options)
         except ProjectError as e:
             warning = self.create_warning(None, kind=self.kind)
-            return warning.warn('doxygen{kind}: %s' % e)
+            return warning.warn("doxygen{kind}: %s" % e)
 
         try:
             finder = self.finder_factory.create_finder(project_info)
         except MTimeError as e:
             warning = self.create_warning(None, kind=self.kind)
-            return warning.warn('doxygen{kind}: %s' % e)
+            return warning.warn("doxygen{kind}: %s" % e)
 
         finder_filter = self.create_finder_filter(namespace, name)
 
@@ -55,8 +54,7 @@ def run(self) -> List[Node]:
 
         if len(matches) == 0:
             display_name = "%s::%s" % (namespace, name) if namespace else name
-            warning = self.create_warning(project_info, kind=self.kind,
-                                          display_name=display_name)
+            warning = self.create_warning(project_info, kind=self.kind, display_name=display_name)
             return warning.warn('doxygen{kind}: Cannot find {kind} "{display_name}" {tail}')
 
         target_handler = create_target_handler(self.options, project_info, self.state.document)
@@ -64,8 +62,9 @@ def run(self) -> List[Node]:
 
         node_stack = matches[0]
         mask_factory = NullMaskFactory()
-        return self.render(node_stack, project_info, filter_, target_handler, mask_factory,
-                           self.directive_args)
+        return self.render(
+            node_stack, project_info, filter_, target_handler, mask_factory, self.directive_args
+        )
 
 
 class DoxygenVariableDirective(_DoxygenBaseItemDirective):
@@ -100,4 +99,4 @@ def create_finder_filter(self, namespace: str, name: str) -> Filter:
         # type dependent
         #
         xml_name = "%s::%s" % (namespace, name) if namespace else name
-        return self.filter_factory.create_compound_finder_filter(xml_name, 'union')
+        return self.filter_factory.create_compound_finder_filter(xml_name, "union")
diff --git a/breathe/directives/setup.py b/breathe/directives/setup.py
index 480cf6b23..fc860a26b 100644
--- a/breathe/directives/setup.py
+++ b/breathe/directives/setup.py
@@ -1,16 +1,24 @@
 from breathe.directives import BaseDirective
 from breathe.directives.class_like import (
-    DoxygenStructDirective, DoxygenClassDirective, DoxygenInterfaceDirective,
+    DoxygenStructDirective,
+    DoxygenClassDirective,
+    DoxygenInterfaceDirective,
 )
 from breathe.directives.content_block import (
-    DoxygenNamespaceDirective, DoxygenGroupDirective, DoxygenPageDirective,
+    DoxygenNamespaceDirective,
+    DoxygenGroupDirective,
+    DoxygenPageDirective,
 )
 from breathe.directives.file import DoxygenFileDirective, AutoDoxygenFileDirective
 from breathe.directives.function import DoxygenFunctionDirective
 from breathe.directives.index import DoxygenIndexDirective, AutoDoxygenIndexDirective
 from breathe.directives.item import (
-    DoxygenVariableDirective, DoxygenDefineDirective, DoxygenUnionDirective,
-    DoxygenEnumDirective, DoxygenEnumValueDirective, DoxygenTypedefDirective,
+    DoxygenVariableDirective,
+    DoxygenDefineDirective,
+    DoxygenUnionDirective,
+    DoxygenEnumDirective,
+    DoxygenEnumValueDirective,
+    DoxygenTypedefDirective,
 )
 from breathe.finder.factory import FinderFactory
 from breathe.parser import DoxygenParserFactory
@@ -26,9 +34,14 @@
 
 
 class DirectiveContainer:
-    def __init__(self, app: Sphinx, directive: Type[BaseDirective],
-                 finder_factory: FinderFactory, project_info_factory: ProjectInfoFactory,
-                 parser_factory: DoxygenParserFactory):
+    def __init__(
+        self,
+        app: Sphinx,
+        directive: Type[BaseDirective],
+        finder_factory: FinderFactory,
+        project_info_factory: ProjectInfoFactory,
+        parser_factory: DoxygenParserFactory,
+    ):
         self.app = app
         self.directive = directive
         self.finder_factory = finder_factory
@@ -43,11 +56,7 @@ def __init__(self, app: Sphinx, directive: Type[BaseDirective],
         self.final_argument_whitespace = directive.final_argument_whitespace
 
     def __call__(self, *args):
-        call_args = [
-            self.finder_factory,
-            self.project_info_factory,
-            self.parser_factory
-        ]
+        call_args = [self.finder_factory, self.project_info_factory, self.parser_factory]
         call_args.extend(args)
         return self.directive(*call_args)
 
@@ -82,34 +91,34 @@ def setup(app: Sphinx) -> None:
     for name, directive in directives.items():
         # ordinarily app.add_directive takes a class it self, but we need to inject extra arguments
         # so we give a DirectiveContainer object which has an overloaded __call__ operator.
-        app.add_directive(name, DirectiveContainer(  # type: ignore
-            app,
-            directive,
-            finder_factory,
-            project_info_factory,
-            parser_factory
-            ))
+        app.add_directive(
+            name,
+            DirectiveContainer(  # type: ignore
+                app, directive, finder_factory, project_info_factory, parser_factory
+            ),
+        )
 
     app.add_config_value("breathe_projects", {}, True)  # Dict[str, str]
     app.add_config_value("breathe_default_project", "", True)  # str
     # Provide reasonable defaults for domain_by_extension mapping. Can be overridden by users.
-    app.add_config_value("breathe_domain_by_extension",
-                         {'py': 'py', 'cs': 'cs'}, True)  # Dict[str, str]
+    app.add_config_value(
+        "breathe_domain_by_extension", {"py": "py", "cs": "cs"}, True
+    )  # Dict[str, str]
     app.add_config_value("breathe_domain_by_file_pattern", {}, True)  # Dict[str, str]
     app.add_config_value("breathe_projects_source", {}, True)
-    app.add_config_value("breathe_build_directory", '', True)
+    app.add_config_value("breathe_build_directory", "", True)
     app.add_config_value("breathe_default_members", (), True)
-    app.add_config_value("breathe_show_define_initializer", False, 'env')
-    app.add_config_value("breathe_show_enumvalue_initializer", False, 'env')
-    app.add_config_value("breathe_implementation_filename_extensions", ['.c', '.cc', '.cpp'], True)
+    app.add_config_value("breathe_show_define_initializer", False, "env")
+    app.add_config_value("breathe_show_enumvalue_initializer", False, "env")
+    app.add_config_value("breathe_implementation_filename_extensions", [".c", ".cc", ".cpp"], True)
     app.add_config_value("breathe_doxygen_config_options", {}, True)
     app.add_config_value("breathe_doxygen_aliases", {}, True)
     app.add_config_value("breathe_use_project_refids", False, "env")
-    app.add_config_value("breathe_order_parameters_first", False, 'env')
-    app.add_config_value("breathe_separate_member_pages", False, 'env')
+    app.add_config_value("breathe_order_parameters_first", False, "env")
+    app.add_config_value("breathe_separate_member_pages", False, "env")
 
     breathe_css = "breathe.css"
-    if (os.path.exists(os.path.join(app.confdir, "_static", breathe_css))):  # type: ignore
+    if os.path.exists(os.path.join(app.confdir, "_static", breathe_css)):  # type: ignore
         app.add_css_file(breathe_css)
 
     def write_file(directory, filename, content):
@@ -122,14 +131,14 @@ def write_file(directory, filename, content):
             f.write(content)
 
     doxygen_handle = AutoDoxygenProcessHandle(
-        subprocess.check_call,
-        write_file,
-        project_info_factory)
+        subprocess.check_call, write_file, project_info_factory
+    )
 
     def doxygen_hook(app):
         doxygen_handle.generate_xml(
             app.config.breathe_projects_source,
             app.config.breathe_doxygen_config_options,
-            app.config.breathe_doxygen_aliases
+            app.config.breathe_doxygen_aliases,
         )
+
     app.connect("builder-inited", doxygen_hook)
diff --git a/breathe/exception.py b/breathe/exception.py
index ebb7b0d10..44c5929d8 100644
--- a/breathe/exception.py
+++ b/breathe/exception.py
@@ -1,3 +1,2 @@
-
 class BreatheError(Exception):
     pass
diff --git a/breathe/file_state_cache.py b/breathe/file_state_cache.py
index e80cbdde2..6d2d71939 100644
--- a/breathe/file_state_cache.py
+++ b/breathe/file_state_cache.py
@@ -24,7 +24,7 @@ def _getmtime(filename: str):
     try:
         return os.path.getmtime(filename)
     except OSError:
-        raise MTimeError('Cannot find file: %s' % os.path.realpath(filename))
+        raise MTimeError("Cannot find file: %s" % os.path.realpath(filename))
 
 
 def update(app: Sphinx, source_file: str) -> None:
@@ -33,7 +33,8 @@ def update(app: Sphinx, source_file: str) -> None:
 
     new_mtime = _getmtime(source_file)
     mtime, docnames = app.env.breathe_file_state.setdefault(  # type: ignore
-        source_file, (new_mtime, set()))
+        source_file, (new_mtime, set())
+    )
 
     assert app.env is not None
     docnames.add(app.env.docname)
@@ -41,8 +42,9 @@ def update(app: Sphinx, source_file: str) -> None:
     app.env.breathe_file_state[source_file] = (new_mtime, docnames)  # type: ignore
 
 
-def _get_outdated(app: Sphinx, env: BuildEnvironment,
-                  added: Set[str], changed: Set[str], removed: Set[str]) -> List[str]:
+def _get_outdated(
+    app: Sphinx, env: BuildEnvironment, added: Set[str], changed: Set[str], removed: Set[str]
+) -> List[str]:
     if not hasattr(app.env, "breathe_file_state"):
         return []
 
diff --git a/breathe/finder/__init__.py b/breathe/finder/__init__.py
index 3a973168e..4217ce5f0 100644
--- a/breathe/finder/__init__.py
+++ b/breathe/finder/__init__.py
@@ -12,8 +12,7 @@ def stack(element, list_):
 
 
 class ItemFinder:
-    def __init__(self, project_info: ProjectInfo, data_object,
-                 item_finder_factory):
+    def __init__(self, project_info: ProjectInfo, data_object, item_finder_factory):
         self.data_object = data_object
         # DoxygenItemFinderFactory, but actually typing it would introduce an import cycle
         self.item_finder_factory = item_finder_factory
diff --git a/breathe/finder/compound.py b/breathe/finder/compound.py
index 022effe23..e38d2c6b7 100644
--- a/breathe/finder/compound.py
+++ b/breathe/finder/compound.py
@@ -49,7 +49,7 @@ def filter_(self, ancestors, filter_: Filter, matches) -> None:
         if filter_.allow(node_stack):
             matches.append(node_stack)
 
-        if data_object.kind == 'enum':
+        if data_object.kind == "enum":
             for value in data_object.enumvalue:
                 value_stack = stack(value, node_stack)
                 if filter_.allow(value_stack):
diff --git a/breathe/finder/factory.py b/breathe/finder/factory.py
index 5f4fc8fba..ab8918edc 100644
--- a/breathe/finder/factory.py
+++ b/breathe/finder/factory.py
@@ -17,8 +17,7 @@ def __init__(self, app: Sphinx, parser_factory: DoxygenParserFactory):
 
     def __call__(self, project_info: ProjectInfo, *args):
         compound_parser = self.parser_factory.create_compound_parser(project_info)
-        return indexfinder.CompoundTypeSubItemFinder(self.app, compound_parser,
-                                                     project_info, *args)
+        return indexfinder.CompoundTypeSubItemFinder(self.app, compound_parser, project_info, *args)
 
 
 class DoxygenItemFinderFactory:
diff --git a/breathe/finder/index.py b/breathe/finder/index.py
index 0893f8077..32e357dd4 100644
--- a/breathe/finder/index.py
+++ b/breathe/finder/index.py
@@ -55,7 +55,8 @@ def filter_(self, ancestors, filter_: Filter, matches) -> None:
 
             for member_stack in member_matches:
                 ref_filter = self.filter_factory.create_id_filter(
-                    'memberdef', member_stack[0].refid)
+                    "memberdef", member_stack[0].refid
+                )
                 finder.filter_(node_stack, ref_filter, matches)
         else:
             # Read in the xml file referenced by the compound and descend into that as well
diff --git a/breathe/path_handler.py b/breathe/path_handler.py
index 6f95ac4d0..88bf46876 100644
--- a/breathe/path_handler.py
+++ b/breathe/path_handler.py
@@ -6,7 +6,7 @@
 def includes_directory(file_path: str):
     # Check for backslash or forward slash as we don't know what platform we're on and sometimes
     # the doxygen paths will have forward slash even on Windows.
-    return bool(file_path.count('\\')) or bool(file_path.count('/'))
+    return bool(file_path.count("\\")) or bool(file_path.count("/"))
 
 
 def resolve_path(app: Sphinx, directory: str, filename: str):
diff --git a/breathe/process.py b/breathe/process.py
index 30bf65c85..2387cf3df 100644
--- a/breathe/process.py
+++ b/breathe/process.py
@@ -57,7 +57,8 @@ def generate_xml(self, projects_source, doxygen_options, doxygen_aliases):
             contents = file_structure[1]
 
             auto_project_info = self.project_info_factory.create_auto_project_info(
-                project_name, folder)
+                project_name, folder
+            )
 
             project_files[project_name] = ProjectData(auto_project_info, contents)
 
@@ -66,10 +67,8 @@ def generate_xml(self, projects_source, doxygen_options, doxygen_aliases):
         for project_name, data in project_files.items():
 
             project_path = self.process(
-                data.auto_project_info,
-                data.files,
-                doxygen_options,
-                doxygen_aliases)
+                data.auto_project_info, data.files, doxygen_options, doxygen_aliases
+            )
 
             project_info = data.auto_project_info.create_project_info(project_path)
 
@@ -83,27 +82,24 @@ def process(self, auto_project_info, files, doxygen_options, doxygen_aliases):
         full_paths = map(lambda x: auto_project_info.abs_path_to_source_file(x), files)
 
         options = "\n".join("%s=%s" % pair for pair in doxygen_options.items())
-        aliases = '\n'.join(
-            f'ALIASES += {name}="{value}"' for name, value in doxygen_aliases.items())
+        aliases = "\n".join(
+            f'ALIASES += {name}="{value}"' for name, value in doxygen_aliases.items()
+        )
 
         cfg = AUTOCFG_TEMPLATE.format(
             project_name=name,
             output_dir=name,
             input=" ".join(full_paths),
-            extra=f"{options}\n{aliases}"
-            )
+            extra=f"{options}\n{aliases}",
+        )
 
-        build_dir = os.path.join(
-            auto_project_info.build_dir(),
-            "breathe",
-            "doxygen"
-            )
+        build_dir = os.path.join(auto_project_info.build_dir(), "breathe", "doxygen")
 
         self.write_file(build_dir, cfgfile, cfg)
 
         # Shell-escape the cfg file name to try to avoid any issue where the name might include
         # malicious shell character - We have to use the shell=True option to make it work on
         # Windows. See issue #271
-        self.run_process('doxygen %s' % quote(cfgfile), cwd=build_dir, shell=True)
+        self.run_process("doxygen %s" % quote(cfgfile), cwd=build_dir, shell=True)
 
         return os.path.join(build_dir, name, "xml")
diff --git a/breathe/project.py b/breathe/project.py
index f0cbce605..778aab0d1 100644
--- a/breathe/project.py
+++ b/breathe/project.py
@@ -1,4 +1,3 @@
-
 from .exception import BreatheError
 
 from sphinx.application import Sphinx
@@ -48,11 +47,7 @@ def abs_path_to_source_file(self, file_):
     def create_project_info(self, project_path):
         """Creates a proper ProjectInfo object based on the information in this AutoProjectInfo"""
 
-        return ProjectInfo(self.app,
-                           self._name,
-                           project_path,
-                           self._source_path,
-                           self._reference)
+        return ProjectInfo(self.app, self._name, project_path, self._source_path, self._reference)
 
 
 class ProjectInfo:
@@ -145,8 +140,11 @@ def default_path(self) -> str:
             return config.breathe_projects[config.breathe_default_project]
         except KeyError:
             raise ProjectError(
-                ("breathe_default_project value '%s' does not seem to be a valid key for the "
-                 "breathe_projects dictionary") % config.breathe_default_project
+                (
+                    "breathe_default_project value '%s' does not seem to be a valid key for the "
+                    "breathe_projects dictionary"
+                )
+                % config.breathe_default_project
             )
 
     def create_project_info(self, options) -> ProjectInfo:
@@ -158,8 +156,10 @@ def create_project_info(self, options) -> ProjectInfo:
                 path = config.breathe_projects[options["project"]]
                 name = options["project"]
             except KeyError:
-                raise ProjectError("Unable to find project '%s' in breathe_projects dictionary"
-                                   % options["project"])
+                raise ProjectError(
+                    "Unable to find project '%s' in breathe_projects dictionary"
+                    % options["project"]
+                )
         elif "path" in options:
             path = options["path"]
         else:
@@ -174,11 +174,7 @@ def create_project_info(self, options) -> ProjectInfo:
                 reference = path
                 self.project_count += 1
 
-            project_info = ProjectInfo(self.app,
-                                       name,
-                                       path,
-                                       "NoSourcePath",
-                                       reference)
+            project_info = ProjectInfo(self.app, name, path, "NoSourcePath", reference)
             self.project_info_store[path] = project_info
             return project_info
 
@@ -198,7 +194,7 @@ def retrieve_project_info_for_auto(self, options) -> AutoProjectInfo:
         sense.
         """
 
-        name = options.get('project', self.app.config.breathe_default_project)  # type: ignore
+        name = options.get("project", self.app.config.breathe_default_project)  # type: ignore
         if name is None:
             raise NoDefaultProjectError(
                 "No breathe_default_project config setting to fall back on "
@@ -217,10 +213,8 @@ def create_auto_project_info(self, name: str, source_path) -> AutoProjectInfo:
                 reference = source_path
                 self.project_count += 1
 
-            auto_project_info = AutoProjectInfo(self.app,
-                                                name,
-                                                source_path,
-                                                self.build_dir,
-                                                reference)
+            auto_project_info = AutoProjectInfo(
+                self.app, name, source_path, self.build_dir, reference
+            )
             self.auto_project_info_store[key] = auto_project_info
             return auto_project_info
diff --git a/breathe/renderer/__init__.py b/breathe/renderer/__init__.py
index 20282a2f7..5dbdde1de 100644
--- a/breathe/renderer/__init__.py
+++ b/breathe/renderer/__init__.py
@@ -7,15 +7,21 @@
 
 def format_parser_error(name, error, filename, state, lineno, do_unicode_warning):
     warning = '%s: Unable to parse xml file "%s". ' % (name, filename)
-    explanation = 'Reported error: %s. ' % error
+    explanation = "Reported error: %s. " % error
 
     unicode_explanation_text = ""
     unicode_explanation = []
     if do_unicode_warning:
-        unicode_explanation_text = textwrap.dedent("""
+        unicode_explanation_text = (
+            textwrap.dedent(
+                """
         Parsing errors are often due to unicode errors associated with the encoding of the original
         source files. Doxygen propagates invalid characters from the input source files to the
-        output xml.""").strip().replace("\n", " ")
+        output xml."""
+            )
+            .strip()
+            .replace("\n", " ")
+        )
         unicode_explanation = [nodes.paragraph("", "", nodes.Text(unicode_explanation_text))]
 
     return [
@@ -26,13 +32,15 @@ def format_parser_error(name, error, filename, state, lineno, do_unicode_warning
             *unicode_explanation
         ),
         state.document.reporter.warning(
-            warning + explanation + unicode_explanation_text, line=lineno)
+            warning + explanation + unicode_explanation_text, line=lineno
+        ),
     ]
 
 
 class RenderContext:
-    def __init__(self, node_stack, mask_factory, directive_args,
-                 domain: str='', child: bool=False) -> None:
+    def __init__(
+        self, node_stack, mask_factory, directive_args, domain: str = "", child: bool = False
+    ) -> None:
         self.node_stack = node_stack
         self.mask_factory = mask_factory
         self.directive_args = directive_args
@@ -42,4 +50,4 @@ def __init__(self, node_stack, mask_factory, directive_args,
     def create_child_context(self, data_object) -> "RenderContext":
         node_stack = self.node_stack[:]
         node_stack.insert(0, self.mask_factory.mask(data_object))
-        return RenderContext(node_stack, self.mask_factory, self.directive_args, self.domain, True)
\ No newline at end of file
+        return RenderContext(node_stack, self.mask_factory, self.directive_args, self.domain, True)
diff --git a/breathe/renderer/filter.py b/breathe/renderer/filter.py
index 836e94acb..b8004c7e3 100644
--- a/breathe/renderer/filter.py
+++ b/breathe/renderer/filter.py
@@ -215,6 +215,7 @@ class UnrecognisedKindError(Exception):
 # Selectors
 ###############################################################################
 
+
 class Selector:
     def __call__(self, node_stack):
         raise NotImplementedError
@@ -225,35 +226,35 @@ def node_type(self):
 
     @property
     def kind(self):
-        return AttributeAccessor(self, 'kind')
+        return AttributeAccessor(self, "kind")
 
     @property
     def node_name(self):
-        return AttributeAccessor(self, 'node_name')
+        return AttributeAccessor(self, "node_name")
 
     @property
     def name(self):
-        return AttributeAccessor(self, 'name')
+        return AttributeAccessor(self, "name")
 
     @property
     def briefdescription(self):
-        return AttributeAccessor(self, 'briefdescription')
+        return AttributeAccessor(self, "briefdescription")
 
     @property
     def detaileddescription(self):
-        return AttributeAccessor(self, 'detaileddescription')
+        return AttributeAccessor(self, "detaileddescription")
 
     @property
     def prot(self):
-        return AttributeAccessor(self, 'prot')
+        return AttributeAccessor(self, "prot")
 
     @property
     def valueOf(self):
-        return AttributeAccessor(self, 'valueOf_')
+        return AttributeAccessor(self, "valueOf_")
 
     @property
     def id(self):
-        return AttributeAccessor(self, 'id')
+        return AttributeAccessor(self, "id")
 
 
 class Ancestor(Selector):
@@ -278,6 +279,7 @@ def __call__(self, node_stack):
 # Accessors
 ###############################################################################
 
+
 class Accessor:
     def __init__(self, selector: Selector) -> None:
         self.selector = selector
@@ -367,6 +369,7 @@ def __call__(self, node_stack):
 # Filters
 ###############################################################################
 
+
 class Filter:
     def allow(self, node_stack) -> bool:
         raise NotImplementedError
@@ -450,7 +453,7 @@ def allow(self, node_stack) -> bool:
             # If the target_file contains directory separators then
             # match against the same length at the end of the location
             #
-            location_match = location[-len(self.target_file):]
+            location_match = location[-len(self.target_file) :]
             return location_match == self.target_file
         else:
             # If there are no separators, match against the whole filename
@@ -550,6 +553,7 @@ def allow(self, node_stack) -> bool:
 # Other stuff
 ###############################################################################
 
+
 class Glob:
     def __init__(self, method, pattern):
         self.method = method
@@ -561,14 +565,16 @@ def match(self, name):
 
 class FilterFactory:
     # C++ style public entries
-    public_kinds = set([
-        "public-type",
-        "public-func",
-        "public-attrib",
-        "public-slot",
-        "public-static-func",
-        "public-static-attrib",
-    ])
+    public_kinds = set(
+        [
+            "public-type",
+            "public-func",
+            "public-attrib",
+            "public-slot",
+            "public-static-func",
+            "public-static-attrib",
+        ]
+    )
 
     def __init__(self, app: Sphinx) -> None:
         self.app = app
@@ -576,43 +582,48 @@ def __init__(self, app: Sphinx) -> None:
     def create_render_filter(self, kind: str, options: Dict[str, Any]) -> Filter:
         """Render filter for group & namespace blocks"""
 
-        if kind not in ['group', 'page', 'namespace']:
+        if kind not in ["group", "page", "namespace"]:
             raise UnrecognisedKindError(kind)
 
         # Generate new dictionary from defaults
-        filter_options = dict((entry, '')
-                              for entry in self.app.config.breathe_default_members)  # type: ignore
+        filter_options = dict(
+            (entry, "") for entry in self.app.config.breathe_default_members
+        )  # type: ignore
 
         # Update from the actual options
         filter_options.update(options)
 
         # Convert the doxygengroup members flag (which just stores None as the value) to an empty
         # string to allow the create_class_member_filter to process it properly
-        if 'members' in filter_options:
-            filter_options['members'] = ''
+        if "members" in filter_options:
+            filter_options["members"] = ""
 
         node = Node()
         grandparent = Ancestor(2)
         has_grandparent = HasAncestorFilter(2)
 
-        non_class_memberdef = \
-            has_grandparent \
-            & (grandparent.node_type == 'compounddef') \
-            & (grandparent.kind != 'class') \
-            & (grandparent.kind != 'struct') \
-            & (grandparent.kind != 'interface') \
-            & (node.node_type == 'memberdef')
+        non_class_memberdef = (
+            has_grandparent
+            & (grandparent.node_type == "compounddef")
+            & (grandparent.kind != "class")
+            & (grandparent.kind != "struct")
+            & (grandparent.kind != "interface")
+            & (node.node_type == "memberdef")
+        )
 
-        return (self.create_class_member_filter(filter_options) | non_class_memberdef) \
-            & self.create_innerclass_filter(filter_options) \
+        return (
+            (self.create_class_member_filter(filter_options) | non_class_memberdef)
+            & self.create_innerclass_filter(filter_options)
             & self.create_outline_filter(filter_options)
+        )
 
     def create_class_filter(self, target: str, options: Dict[str, Any]) -> Filter:
         """Content filter for classes based on various directive options"""
 
         # Generate new dictionary from defaults
-        filter_options = dict((entry, '')
-                              for entry in self.app.config.breathe_default_members)  # type: ignore
+        filter_options = dict(
+            (entry, "") for entry in self.app.config.breathe_default_members
+        )  # type: ignore
 
         # Update from the actual options
         filter_options.update(options)
@@ -622,9 +633,9 @@ def create_class_filter(self, target: str, options: Dict[str, Any]) -> Filter:
             self.create_innerclass_filter(filter_options, outerclass=target),
             self.create_outline_filter(filter_options),
             self.create_show_filter(filter_options),
-            )
+        )
 
-    def create_innerclass_filter(self, options: Dict[str, Any], outerclass: str = '') -> Filter:
+    def create_innerclass_filter(self, options: Dict[str, Any], outerclass: str = "") -> Filter:
         """
         :param outerclass: Should be the class/struct being target by the directive calling this
                            code. If it is a group or namespace directive then it should be left
@@ -638,14 +649,14 @@ def create_innerclass_filter(self, options: Dict[str, Any], outerclass: str = ''
         node_is_innerclass = (node.node_type == "ref") & (node.node_name == "innerclass")
 
         parent = Parent()
-        parent_is_compounddef = parent.node_type == 'compounddef'
-        parent_is_class = parent.kind.is_one_of(['class', 'struct', 'interface'])
+        parent_is_compounddef = parent.node_type == "compounddef"
+        parent_is_class = parent.kind.is_one_of(["class", "struct", "interface"])
 
         allowed = set()
         all_options = {
-            'protected-members': 'protected',
-            'private-members': 'private',
-            }
+            "protected-members": "protected",
+            "private-members": "private",
+        }
 
         for option, scope in all_options.items():
             if option in options:
@@ -655,13 +666,13 @@ def create_innerclass_filter(self, options: Dict[str, Any], outerclass: str = ''
 
         public_innerclass_filter = ClosedFilter()
 
-        if 'members' in options:
-            if options['members'].strip():
+        if "members" in options:
+            if options["members"].strip():
                 text = options["members"]
-                prefix = ('%s::' % outerclass) if outerclass else ''
+                prefix = ("%s::" % outerclass) if outerclass else ""
 
                 # Matches sphinx-autodoc behaviour of comma separated values
-                members = set(['%s%s' % (prefix, x.strip()) for x in text.split(",")])
+                members = set(["%s%s" % (prefix, x.strip()) for x in text.split(",")])
                 node_valueOf_is_in_members = node.valueOf.is_one_of(members)
 
                 # Accept any nodes which don't have a "sectiondef" as a parent or, if they do, only
@@ -669,12 +680,12 @@ def create_innerclass_filter(self, options: Dict[str, Any], outerclass: str = ''
                 public_innerclass_filter = ~node_is_innerclass_in_class | node_valueOf_is_in_members
 
             else:
-                allowed.add('public')
+                allowed.add("public")
 
         node_is_in_allowed_scope = node.prot.is_one_of(allowed)
 
-        innerclass = ~ node_is_innerclass_in_class | node_is_in_allowed_scope
-        description = self._create_description_filter(True, 'compounddef', options)
+        innerclass = ~node_is_innerclass_in_class | node_is_in_allowed_scope
+        description = self._create_description_filter(True, "compounddef", options)
 
         # Put parent check last as we only want to check parents of innerclass's otherwise we have
         # to check the parent's type as well
@@ -689,8 +700,8 @@ def create_show_filter(self, options: Dict[str, Any]) -> Filter:
             # Allow through everything except the header-file includes nodes
             return OrFilter(
                 NotFilter(InFilter(NodeTypeAccessor(Parent()), ["compounddef"])),
-                NotFilter(InFilter(NodeTypeAccessor(Node()), ["inc"]))
-                )
+                NotFilter(InFilter(NodeTypeAccessor(Node()), ["inc"])),
+            )
 
         if text == "header-file":
             # Allow through everything, including header-file includes
@@ -699,27 +710,27 @@ def create_show_filter(self, options: Dict[str, Any]) -> Filter:
         # Allow through everything except the header-file includes nodes
         return OrFilter(
             NotFilter(InFilter(NodeTypeAccessor(Parent()), ["compounddef"])),
-            NotFilter(InFilter(NodeTypeAccessor(Node()), ["inc"]))
-            )
+            NotFilter(InFilter(NodeTypeAccessor(Node()), ["inc"])),
+        )
 
-    def _create_description_filter(self, allow: bool, level: str,
-                                   options: Dict[str, Any]) -> Filter:
+    def _create_description_filter(
+        self, allow: bool, level: str, options: Dict[str, Any]
+    ) -> Filter:
         """Whether or not we allow descriptions is determined by the calling function and we just do
         whatever the 'allow' function parameter tells us.
         """
 
         node = Node()
-        node_is_description = node.node_type == 'description'
+        node_is_description = node.node_type == "description"
         parent = Parent()
         parent_is_level = parent.node_type == level
 
         # Nothing with a parent that's a sectiondef
-        description_filter = ~ parent_is_level
+        description_filter = ~parent_is_level
 
         # Let through any description children of sectiondefs if we output any kind members
         if allow:
-            description_filter = \
-                (parent_is_level & node_is_description) | ~ parent_is_level
+            description_filter = (parent_is_level & node_is_description) | ~parent_is_level
 
         return description_filter
 
@@ -733,12 +744,12 @@ def _create_public_members_filter(self, options: Dict[str, Any]) -> Filter:
 
         # Nothing with a parent that's a sectiondef
         is_memberdef = parent_is_sectiondef & node_is_memberdef
-        public_members_filter = ~ is_memberdef
+        public_members_filter = ~is_memberdef
 
         # If the user has specified the 'members' option with arguments then we only pay attention
         # to that and not to any other member settings
         if "members" in options:
-            if options['members'].strip():
+            if options["members"].strip():
                 text = options["members"]
 
                 # Matches sphinx-autodoc behaviour of comma separated values
@@ -748,17 +759,18 @@ def _create_public_members_filter(self, options: Dict[str, Any]) -> Filter:
 
                 # Accept any nodes which don't have a "sectiondef" as a parent or, if they do, only
                 # accept them if their names are in the members list
-                public_members_filter = \
-                    (parent_is_sectiondef & node_name_is_in_members) | ~ parent_is_sectiondef
+                public_members_filter = (
+                    parent_is_sectiondef & node_name_is_in_members
+                ) | ~parent_is_sectiondef
             else:
                 # Select anything that doesn't have a parent which is a sectiondef, or, if it does,
                 # only select the public ones
-                public_members_filter = \
-                    (is_memberdef & node_is_public) | ~ is_memberdef
+                public_members_filter = (is_memberdef & node_is_public) | ~is_memberdef
         return public_members_filter
 
-    def _create_non_public_members_filter(self, prot: str, option_name: str,
-                                          options: Dict[str, Any]) -> Filter:
+    def _create_non_public_members_filter(
+        self, prot: str, option_name: str, options: Dict[str, Any]
+    ) -> Filter:
         """'prot' is the doxygen xml term for 'public', 'protected' and 'private' categories."""
 
         node = Node()
@@ -770,24 +782,25 @@ def _create_non_public_members_filter(self, prot: str, option_name: str,
 
         # Nothing with a parent that's a sectiondef
         is_memberdef = parent_is_sectiondef & node_is_memberdef
-        filter_ = ~ is_memberdef
+        filter_ = ~is_memberdef
 
         if option_name in options:
             # Allow anything that isn't a memberdef, or if it is only allow the public ones
-            filter_ = ~ is_memberdef | node_is_public
+            filter_ = ~is_memberdef | node_is_public
         return filter_
 
     def _create_undoc_members_filter(self, options: Dict[str, Any]) -> Filter:
         node = Node()
-        node_is_memberdef = node.node_type == 'memberdef'
+        node_is_memberdef = node.node_type == "memberdef"
 
-        node_has_description = node.briefdescription.has_content() \
-            | node.detaileddescription.has_content()
+        node_has_description = (
+            node.briefdescription.has_content() | node.detaileddescription.has_content()
+        )
 
         # Allow anything that isn't a memberdef, or if it is only allow the ones with a description
-        undoc_members_filter = ~ node_is_memberdef | node_has_description
+        undoc_members_filter = ~node_is_memberdef | node_has_description
 
-        if 'undoc-members' in options:
+        if "undoc-members" in options:
             undoc_members_filter = OpenFilter()
         return undoc_members_filter
 
@@ -798,26 +811,22 @@ def create_class_member_filter(self, options: Dict[str, Any]) -> Filter:
         # out when it is needed. This approach reflects the old code that was here but it wasn't
         # commented (my fault.) I wonder if maybe the public and private declarations themselves can
         # be documented and we need to let them through. Not sure.
-        allow = 'members' in options \
-            or 'protected-members' in options \
-            or 'private-members' in options
+        allow = (
+            "members" in options or "protected-members" in options or "private-members" in options
+        )
 
-        description = self._create_description_filter(allow, 'sectiondef', options)
+        description = self._create_description_filter(allow, "sectiondef", options)
 
         # Create all necessary filters and combine them
         public_members = self._create_public_members_filter(options)
 
         protected_members = self._create_non_public_members_filter(
-            'protected',
-            'protected-members',
-            options
-            )
+            "protected", "protected-members", options
+        )
 
         private_members = self._create_non_public_members_filter(
-            'private',
-            'private-members',
-            options
-            )
+            "private", "private-members", options
+        )
 
         undoc_members = self._create_undoc_members_filter(options)
 
@@ -826,9 +835,9 @@ def create_class_member_filter(self, options: Dict[str, Any]) -> Filter:
         return allowed_members | description
 
     def create_outline_filter(self, options: Dict[str, Any]) -> Filter:
-        if 'outline' in options:
+        if "outline" in options:
             node = Node()
-            return ~ node.node_type.is_one_of(["description", "inc"])
+            return ~node.node_type.is_one_of(["description", "inc"])
         else:
             return OpenFilter()
 
@@ -847,13 +856,10 @@ def create_file_filter(self, filename: str, options: Dict[str, Any]) -> Filter:
                 AndFilter(
                     InFilter(NodeTypeAccessor(Node()), ["compounddef"]),
                     InFilter(KindAccessor(Node()), ["file"]),
-                    FilePathFilter(
-                        LambdaAccessor(Node(), lambda x: x.location),
-                        filename
-                        ),
-                    Gather(LambdaAccessor(Node(), lambda x: x.namespaces), valid_names)
-                    )
-                ),
+                    FilePathFilter(LambdaAccessor(Node(), lambda x: x.location), filename),
+                    Gather(LambdaAccessor(Node(), lambda x: x.namespaces), valid_names),
+                )
+            ),
             NotFilter(
                 # Take the valid_names and everytime we handle an
                 # innerclass or innernamespace, check that its name
@@ -868,12 +874,11 @@ def create_file_filter(self, filename: str, options: Dict[str, Any]) -> Filter:
                     InFilter(NodeNameAccessor(Node()), ["innerclass", "innernamespace"]),
                     NotFilter(
                         InFilter(
-                            LambdaAccessor(Node(), lambda x: x.content_[0].getValue()),
-                            valid_names
-                            )
+                            LambdaAccessor(Node(), lambda x: x.content_[0].getValue()), valid_names
                         )
-                    )
-                ),
+                    ),
+                )
+            ),
             NotFilter(
                 # Ignore innerclasses and innernamespaces that are inside a
                 # namespace that is going to be rendered as they will be
@@ -884,10 +889,10 @@ def create_file_filter(self, filename: str, options: Dict[str, Any]) -> Filter:
                     InFilter(NodeNameAccessor(Node()), ["innerclass", "innernamespace"]),
                     NamespaceFilter(
                         NamespaceAccessor(Parent()),
-                        LambdaAccessor(Node(), lambda x: x.content_[0].getValue())
-                        )
-                    )
-                ),
+                        LambdaAccessor(Node(), lambda x: x.content_[0].getValue()),
+                    ),
+                )
+            ),
             NotFilter(
                 # Ignore memberdefs from files which are different to
                 # the one we're rendering. This happens when we have to
@@ -896,11 +901,10 @@ def create_file_filter(self, filename: str, options: Dict[str, Any]) -> Filter:
                 AndFilter(
                     InFilter(NodeTypeAccessor(Node()), ["memberdef"]),
                     NotFilter(
-                        FilePathFilter(LambdaAccessor(Node(), lambda x: x.location),
-                                       filename)
-                        )
-                    )
-                ),
+                        FilePathFilter(LambdaAccessor(Node(), lambda x: x.location), filename)
+                    ),
+                )
+            ),
             NotFilter(
                 # Ignore compounddefs which are from another file
                 # (normally means classes and structs which are in a
@@ -914,12 +918,11 @@ def create_file_filter(self, filename: str, options: Dict[str, Any]) -> Filter:
                     InFilter(NodeTypeAccessor(Node()), ["compounddef"]),
                     NotFilter(InFilter(KindAccessor(Node()), ["namespace"])),
                     NotFilter(
-                        FilePathFilter(LambdaAccessor(Node(), lambda x: x.location),
-                                       filename)
-                        )
-                    )
+                        FilePathFilter(LambdaAccessor(Node(), lambda x: x.location), filename)
+                    ),
                 )
-            )
+            ),
+        )
         return AndFilter(self.create_outline_filter(options), filter_)
 
     def create_content_filter(self, kind: str, options: Dict[str, Any]) -> Filter:
@@ -932,27 +935,28 @@ def create_content_filter(self, kind: str, options: Dict[str, Any]) -> Filter:
         As a finder/content filter we only need to match exactly what we're interested in.
         """
 
-        if kind not in ['group', 'page', 'namespace']:
+        if kind not in ["group", "page", "namespace"]:
             raise UnrecognisedKindError(kind)
 
         node = Node()
 
         # Filter for public memberdefs
-        node_is_memberdef = node.node_type == 'memberdef'
-        node_is_public = node.prot == 'public'
+        node_is_memberdef = node.node_type == "memberdef"
+        node_is_public = node.prot == "public"
 
         public_members = node_is_memberdef & node_is_public
 
         # Filter for public innerclasses
         parent = Parent()
-        parent_is_compounddef = parent.node_type == 'compounddef'
+        parent_is_compounddef = parent.node_type == "compounddef"
         parent_is_class = parent.kind == kind
 
         node_is_innerclass = (node.node_type == "ref") & (node.node_name == "innerclass")
-        node_is_public = node.prot == 'public'
+        node_is_public = node.prot == "public"
 
-        public_innerclass = parent_is_compounddef & parent_is_class \
-            & node_is_innerclass & node_is_public
+        public_innerclass = (
+            parent_is_compounddef & parent_is_class & node_is_innerclass & node_is_public
+        )
 
         return public_members | public_innerclass
 
@@ -962,23 +966,20 @@ def create_index_filter(self, options: Dict[str, Any]) -> Filter:
                 AndFilter(
                     InFilter(NodeTypeAccessor(Parent()), ["compounddef"]),
                     InFilter(NodeTypeAccessor(Node()), ["ref"]),
-                    InFilter(NodeNameAccessor(Node()), ["innerclass", "innernamespace"])
-                    )
-                ),
+                    InFilter(NodeNameAccessor(Node()), ["innerclass", "innernamespace"]),
+                )
+            ),
             NotFilter(
                 AndFilter(
                     InFilter(NodeTypeAccessor(Parent()), ["compounddef"]),
                     InFilter(KindAccessor(Parent()), ["group"]),
                     InFilter(NodeTypeAccessor(Node()), ["sectiondef"]),
-                    InFilter(KindAccessor(Node()), ["func"])
-                    )
+                    InFilter(KindAccessor(Node()), ["func"]),
                 )
-            )
+            ),
+        )
 
-        return AndFilter(
-            self.create_outline_filter(options),
-            filter_
-            )
+        return AndFilter(self.create_outline_filter(options), filter_)
 
     def create_open_filter(self) -> Filter:
         """Returns a completely open filter which matches everything"""
@@ -993,7 +994,7 @@ def create_file_finder_filter(self, filename: str) -> Filter:
         filter_ = AndFilter(
             InFilter(NodeTypeAccessor(Node()), ["compounddef"]),
             InFilter(KindAccessor(Node()), ["file"]),
-            FilePathFilter(LambdaAccessor(Node(), lambda x: x.location), filename)
+            FilePathFilter(LambdaAccessor(Node(), lambda x: x.location), filename),
         )
         return filter_
 
@@ -1003,35 +1004,39 @@ def create_member_finder_filter(self, namespace: str, name: str, kind: str) -> F
         node = Node()
         parent = Parent()
 
-        node_matches = (node.node_type == 'member') \
-            & (node.kind == kind) \
-            & (node.name == name)
+        node_matches = (node.node_type == "member") & (node.kind == kind) & (node.name == name)
 
         if namespace:
-            parent_matches = (parent.node_type == 'compound') \
-                & ((parent.kind == 'namespace') |
-                   (parent.kind == 'class') |
-                   (parent.kind == 'struct') |
-                   (parent.kind == 'interface')) \
+            parent_matches = (
+                (parent.node_type == "compound")
+                & (
+                    (parent.kind == "namespace")
+                    | (parent.kind == "class")
+                    | (parent.kind == "struct")
+                    | (parent.kind == "interface")
+                )
                 & (parent.name == namespace)
+            )
             return parent_matches & node_matches
         else:
             is_implementation_file = parent.name.endswith(
-                self.app.config.breathe_implementation_filename_extensions)  # type: ignore
-            parent_is_compound = parent.node_type == 'compound'
-            parent_is_file = (parent.kind == 'file') & (~ is_implementation_file)
-            parent_is_not_file = parent.kind != 'file'
-
-            return (parent_is_compound & parent_is_file & node_matches) \
-                | (parent_is_compound & parent_is_not_file & node_matches)
+                self.app.config.breathe_implementation_filename_extensions
+            )  # type: ignore
+            parent_is_compound = parent.node_type == "compound"
+            parent_is_file = (parent.kind == "file") & (~is_implementation_file)
+            parent_is_not_file = parent.kind != "file"
+
+            return (parent_is_compound & parent_is_file & node_matches) | (
+                parent_is_compound & parent_is_not_file & node_matches
+            )
 
     def create_function_and_all_friend_finder_filter(self, namespace: str, name: str) -> Filter:
         parent = Parent()
-        parent_is_compound = parent.node_type == 'compound'
-        parent_is_group = parent.kind == 'group'
+        parent_is_compound = parent.node_type == "compound"
+        parent_is_group = parent.kind == "group"
 
-        function_filter = self.create_member_finder_filter(namespace, name, 'function')
-        friend_filter = self.create_member_finder_filter(namespace, name, 'friend')
+        function_filter = self.create_member_finder_filter(namespace, name, "function")
+        friend_filter = self.create_member_finder_filter(namespace, name, "friend")
         # Get matching functions but only ones where the parent is not a group. We want to skip
         # function entries in groups as we'll find the same functions in a file's xml output
         # elsewhere and having more than one match is confusing for our logic later on.
@@ -1041,13 +1046,13 @@ def create_enumvalue_finder_filter(self, name: str) -> Filter:
         """Returns a filter which looks for an enumvalue with the specified name."""
 
         node = Node()
-        return (node.node_type == 'enumvalue') & (node.name == name)
+        return (node.node_type == "enumvalue") & (node.name == name)
 
     def create_compound_finder_filter(self, name: str, kind: str) -> Filter:
         """Returns a filter which looks for a compound with the specified name and kind."""
 
         node = Node()
-        return (node.node_type == 'compound') & (node.kind == kind) & (node.name == name)
+        return (node.node_type == "compound") & (node.kind == kind) & (node.name == name)
 
     def create_finder_filter(self, kind: str, name: str) -> Filter:
         """Returns a filter which looks for the compound node from the index which is a group node
@@ -1057,23 +1062,23 @@ def create_finder_filter(self, kind: str, name: str) -> Filter:
         contents.
         """
 
-        if kind == 'group':
+        if kind == "group":
             filter_ = AndFilter(
                 InFilter(NodeTypeAccessor(Node()), ["compound"]),
                 InFilter(KindAccessor(Node()), ["group"]),
-                InFilter(NameAccessor(Node()), [name])
-                )
-        elif kind == 'page':
+                InFilter(NameAccessor(Node()), [name]),
+            )
+        elif kind == "page":
             filter_ = AndFilter(
                 InFilter(NodeTypeAccessor(Node()), ["compound"]),
                 InFilter(KindAccessor(Node()), ["page"]),
-                InFilter(NameAccessor(Node()), [name])
-                )
+                InFilter(NameAccessor(Node()), [name]),
+            )
         else:
             # Assume kind == 'namespace'
             filter_ = AndFilter(
                 InFilter(NodeTypeAccessor(Node()), ["compound"]),
                 InFilter(KindAccessor(Node()), ["namespace"]),
-                InFilter(NameAccessor(Node()), [name])
-                )
+                InFilter(NameAccessor(Node()), [name]),
+            )
         return filter_
diff --git a/breathe/renderer/mask.py b/breathe/renderer/mask.py
index 076a192a3..3bb158f06 100644
--- a/breathe/renderer/mask.py
+++ b/breathe/renderer/mask.py
@@ -18,12 +18,13 @@
 
 """
 
+
 class NoParameterNamesMask:
     def __init__(self, data_object) -> None:
         self.data_object = data_object
 
     def __getattr__(self, attr):
-        if attr in ['declname', 'defname', 'defval']:
+        if attr in ["declname", "defname", "defval"]:
             return None
         return getattr(self.data_object, attr)
 
diff --git a/breathe/renderer/sphinxrenderer.py b/breathe/renderer/sphinxrenderer.py
index 136aaf430..a750f4cdb 100644
--- a/breathe/renderer/sphinxrenderer.py
+++ b/breathe/renderer/sphinxrenderer.py
@@ -69,6 +69,7 @@ def transform_content(self, contentnode: addnodes.desc_content) -> None:
 
 # ----------------------------------------------------------------------------
 
+
 class CPPClassObject(BaseObject, cpp.CPPClassObject):
     pass
 
@@ -99,6 +100,7 @@ class CPPEnumeratorObject(BaseObject, cpp.CPPEnumeratorObject):
 
 # ----------------------------------------------------------------------------
 
+
 class CStructObject(BaseObject, c.CStructObject):
     pass
 
@@ -133,6 +135,7 @@ class CMacroObject(BaseObject, c.CMacroObject):
 
 # ----------------------------------------------------------------------------
 
+
 class PyFunction(BaseObject, python.PyFunction):
     pass
 
@@ -152,8 +155,10 @@ class PyClasslike(BaseObject, python.PyClasslike):
 # We use capitalization (and the namespace) to differentiate between the two
 
 if php is not None:
+
     class PHPNamespaceLevel(BaseObject, php.PhpNamespacelevel):
         """Description of a PHP item *in* a namespace (not the space itself)."""
+
         pass
 
     class PHPClassLike(BaseObject, php.PhpClasslike):
@@ -165,9 +170,11 @@ class PHPClassMember(BaseObject, php.PhpClassmember):
     class PHPGlobalLevel(BaseObject, php.PhpGloballevel):
         pass
 
+
 # ----------------------------------------------------------------------------
 
 if cs is not None:
+
     class CSharpCurrentNamespace(BaseObject, cs.CSharpCurrentNamespace):
         pass
 
@@ -216,78 +223,74 @@ class CSharpXRefRole(BaseObject, cs.CSharpXRefRole):
 
 # ----------------------------------------------------------------------------
 
+
 class DomainDirectiveFactory:
     # A mapping from node kinds to domain directives and their names.
     cpp_classes = {
-        'variable': (CPPMemberObject, 'var'),
-        'class': (CPPClassObject, 'class'),
-        'struct': (CPPClassObject, 'struct'),
-        'interface': (CPPClassObject, 'class'),
-        'function': (CPPFunctionObject, 'function'),
-        'friend': (CPPFunctionObject, 'function'),
-        'signal': (CPPFunctionObject, 'function'),
-        'slot': (CPPFunctionObject, 'function'),
-        'enum': (CPPEnumObject, 'enum'),
-        'typedef': (CPPTypeObject, 'type'),
-        'using': (CPPTypeObject, 'type'),
-        'union': (CPPUnionObject, 'union'),
-        'namespace': (CPPTypeObject, 'type'),
-        'enumvalue': (CPPEnumeratorObject, 'enumerator'),
-        'define': (CMacroObject, 'macro'),
+        "variable": (CPPMemberObject, "var"),
+        "class": (CPPClassObject, "class"),
+        "struct": (CPPClassObject, "struct"),
+        "interface": (CPPClassObject, "class"),
+        "function": (CPPFunctionObject, "function"),
+        "friend": (CPPFunctionObject, "function"),
+        "signal": (CPPFunctionObject, "function"),
+        "slot": (CPPFunctionObject, "function"),
+        "enum": (CPPEnumObject, "enum"),
+        "typedef": (CPPTypeObject, "type"),
+        "using": (CPPTypeObject, "type"),
+        "union": (CPPUnionObject, "union"),
+        "namespace": (CPPTypeObject, "type"),
+        "enumvalue": (CPPEnumeratorObject, "enumerator"),
+        "define": (CMacroObject, "macro"),
     }
     c_classes = {
-        'variable': (CMemberObject, 'var'),
-        'function': (CFunctionObject, 'function'),
-        'define': (CMacroObject, 'macro'),
-        'struct': (CStructObject, 'struct'),
-        'union': (CUnionObject, 'union'),
-        'enum': (CEnumObject, 'enum'),
-        'enumvalue': (CEnumeratorObject, 'enumerator'),
-        'typedef': (CTypeObject, 'type'),
+        "variable": (CMemberObject, "var"),
+        "function": (CFunctionObject, "function"),
+        "define": (CMacroObject, "macro"),
+        "struct": (CStructObject, "struct"),
+        "union": (CUnionObject, "union"),
+        "enum": (CEnumObject, "enum"),
+        "enumvalue": (CEnumeratorObject, "enumerator"),
+        "typedef": (CTypeObject, "type"),
     }
     python_classes = {
         # TODO: PyFunction is meant for module-level functions
         #       and PyAttribute is meant for class attributes, not module-level variables.
         #       Somehow there should be made a distinction at some point to get the correct
         #       index-text and whatever other things are different.
-        'function': (PyFunction, 'function'),
-        'variable': (PyAttribute, 'attribute'),
-        'class': (PyClasslike, 'class'),
-        'namespace': (PyClasslike, 'class'),
+        "function": (PyFunction, "function"),
+        "variable": (PyAttribute, "attribute"),
+        "class": (PyClasslike, "class"),
+        "namespace": (PyClasslike, "class"),
     }
 
     if php is not None:
         php_classes = {
-            'function': (PHPNamespaceLevel, 'function'),
-            'class': (PHPClassLike, 'class'),
-            'attr': (PHPClassMember, 'attr'),
-            'method': (PHPClassMember, 'method'),
-            'global': (PHPGlobalLevel, 'global'),
+            "function": (PHPNamespaceLevel, "function"),
+            "class": (PHPClassLike, "class"),
+            "attr": (PHPClassMember, "attr"),
+            "method": (PHPClassMember, "method"),
+            "global": (PHPGlobalLevel, "global"),
         }
-        php_classes_default = php_classes['class']  # Directive when no matching ones were found
+        php_classes_default = php_classes["class"]  # Directive when no matching ones were found
 
     if cs is not None:
         cs_classes = {
             # 'doxygen-name': (CSharp class, key in CSharpDomain.object_types)
-            'namespace': (CSharpNamespacePlain, 'namespace'),
-
-            'class': (CSharpClass, 'class'),
-            'struct': (CSharpStruct, 'struct'),
-            'interface': (CSharpInterface, 'interface'),
-
-            'function': (CSharpMethod, 'function'),
-            'method': (CSharpMethod, 'method'),
-
-            'variable': (CSharpVariable, 'var'),
-            'property': (CSharpProperty, 'property'),
-            'event': (CSharpEvent, 'event'),
-
-            'enum': (CSharpEnum, 'enum'),
-            'enumvalue': (CSharpEnumValue, 'enumerator'),
-            'attribute': (CSharpAttribute, 'attr'),
-
+            "namespace": (CSharpNamespacePlain, "namespace"),
+            "class": (CSharpClass, "class"),
+            "struct": (CSharpStruct, "struct"),
+            "interface": (CSharpInterface, "interface"),
+            "function": (CSharpMethod, "function"),
+            "method": (CSharpMethod, "method"),
+            "variable": (CSharpVariable, "var"),
+            "property": (CSharpProperty, "property"),
+            "event": (CSharpEvent, "event"),
+            "enum": (CSharpEnum, "enum"),
+            "enumvalue": (CSharpEnumValue, "enumerator"),
+            "attribute": (CSharpAttribute, "attr"),
             # Fallback to cpp domain
-            'typedef': (CPPTypeObject, 'type'),
+            "typedef": (CPPTypeObject, "type"),
         }
 
     @staticmethod
@@ -296,36 +299,36 @@ def create(domain: str, args) -> ObjectDescription:
         name = cast(str, None)
         # TODO: remove the 'type: ignore's below at some point
         #       perhaps something to do with the mypy version
-        if domain == 'c':
+        if domain == "c":
             cls, name = DomainDirectiveFactory.c_classes[args[0]]  # type: ignore
-        elif domain == 'py':
+        elif domain == "py":
             cls, name = DomainDirectiveFactory.python_classes[args[0]]  # type: ignore
-        elif php is not None and domain == 'php':
+        elif php is not None and domain == "php":
             separators = php.separators
             arg_0 = args[0]
-            if any([separators['method'] in n for n in args[1]]):
-                if any([separators['attr'] in n for n in args[1]]):
-                    arg_0 = 'attr'
+            if any([separators["method"] in n for n in args[1]]):
+                if any([separators["attr"] in n for n in args[1]]):
+                    arg_0 = "attr"
                 else:
-                    arg_0 = 'method'
+                    arg_0 = "method"
             else:
-                if arg_0 in ['variable']:
-                    arg_0 = 'global'
+                if arg_0 in ["variable"]:
+                    arg_0 = "global"
 
             if arg_0 in DomainDirectiveFactory.php_classes:
                 cls, name = DomainDirectiveFactory.php_classes[arg_0]  # type: ignore
             else:
                 cls, name = DomainDirectiveFactory.php_classes_default  # type: ignore
 
-        elif cs is not None and domain == 'cs':
+        elif cs is not None and domain == "cs":
             cls, name = DomainDirectiveFactory.cs_classes[args[0]]  # type: ignore
         else:
-            domain = 'cpp'
+            domain = "cpp"
             cls, name = DomainDirectiveFactory.cpp_classes[args[0]]  # type: ignore
         # Replace the directive name because domain directives don't know how to handle
         # Breathe's "doxygen" directives.
-        assert ':' not in name
-        args = [domain + ':' + name] + args[1:]
+        assert ":" not in name
+        args = [domain + ":" + name] + args[1:]
         return cls(*args)
 
 
@@ -370,22 +373,22 @@ def to_string(node):
                 if not isinstance(value, str):
                     value = value.valueOf_
                 result.append(value)
-        return ' '.join(result)
+        return " ".join(result)
 
     param_type = to_string(param.type_)
     param_name = param.declname if param.declname else param.defname
     if not param_name:
         param_decl = param_type
     else:
-        param_decl, number_of_subs = re.subn(r'(\((?:\w+::)*[*&]+)(\))',
-                                             r'\g<1>' + param_name + r'\g<2>',
-                                             param_type)
+        param_decl, number_of_subs = re.subn(
+            r"(\((?:\w+::)*[*&]+)(\))", r"\g<1>" + param_name + r"\g<2>", param_type
+        )
         if number_of_subs == 0:
-            param_decl = param_type + ' ' + param_name
+            param_decl = param_type + " " + param_name
     if param.array:
         param_decl += param.array
     if param.defval:
-        param_decl += ' = ' + to_string(param.defval)
+        param_decl += " = " + to_string(param.defval)
 
     return param_decl
 
@@ -398,20 +401,20 @@ def get_definition_without_template_args(data_object):
     For example in 'Result<T> A< B<C> >::f' we want to remove the '< B<C> >' part.
     """
     definition = data_object.definition
-    if (len(data_object.bitfield) > 0):
+    if len(data_object.bitfield) > 0:
         definition += " : " + data_object.bitfield
-    qual_name = '::' + data_object.name
+    qual_name = "::" + data_object.name
     if definition.endswith(qual_name):
         qual_name_start = len(definition) - len(qual_name)
         pos = qual_name_start - 1
-        if definition[pos] == '>':
+        if definition[pos] == ">":
             bracket_count = 0
             # Iterate back through the characters of the definition counting matching braces and
             # then remove all braces and everything between
             while pos > 0:
-                if definition[pos] == '>':
+                if definition[pos] == ">":
                     bracket_count += 1
-                elif definition[pos] == '<':
+                elif definition[pos] == "<":
                     bracket_count -= 1
                     if bracket_count == 0:
                         definition = definition[:pos] + definition[qual_name_start:]
@@ -427,8 +430,9 @@ class InlineText(Text):
     consumed and a inline element is generated as the parent, instead of the
     paragraph used by Text.
     """
-    patterns = {'inlinetext': r''}
-    initial_transitions = [('inlinetext',)]
+
+    patterns = {"inlinetext": r""}
+    initial_transitions = [("inlinetext",)]
 
     def indent(self, match, context, next_state):
         """
@@ -454,8 +458,7 @@ def inlinetext(self, match, context, next_state):
             block = self.state_machine.get_text_block()
         except UnexpectedIndentationError as err:
             block, src, srcline = err.args
-            msg = self.reporter.error('Unexpected indentation.',
-                                      source=src, line=srcline)
+            msg = self.reporter.error("Unexpected indentation.", source=src, line=srcline)
         lines = context + list(block)
         text, _ = self.inline_text(lines[0], startline)
         self.parent += text
@@ -469,14 +472,17 @@ class SphinxRenderer:
     Each visit method takes a Doxygen node as an argument and returns a list of RST nodes.
     """
 
-    def __init__(self, app: Sphinx,
-                 project_info: ProjectInfo,
-                 node_stack,
-                 state,
-                 document: nodes.document,
-                 target_handler: TargetHandler,
-                 compound_parser: DoxygenCompoundParser,
-                 filter_: Filter):
+    def __init__(
+        self,
+        app: Sphinx,
+        project_info: ProjectInfo,
+        node_stack,
+        state,
+        document: nodes.document,
+        target_handler: TargetHandler,
+        compound_parser: DoxygenCompoundParser,
+        filter_: Filter,
+    ):
         self.app = app
 
         self.project_info = project_info
@@ -495,7 +501,7 @@ def __init__(self, app: Sphinx,
 
     def set_context(self, context: RenderContext) -> None:
         self.context = context
-        if self.context.domain == '':
+        if self.context.domain == "":
             self.context.domain = self.get_domain()
 
     # XXX: fix broken links in XML generated by Doxygen when Doxygen's
@@ -518,18 +524,18 @@ def _fixup_separate_member_pages(self, refid: str) -> str:
             if len(parts) == 2 and parts[1].startswith("1"):
                 anchorid = parts[1][1:]
                 if len(anchorid) in set([33, 34]) and parts[0].endswith(anchorid):
-                    return parts[0][:-len(anchorid)] + parts[1]
+                    return parts[0][: -len(anchorid)] + parts[1]
                 elif len(anchorid) > 34:
                     index = 0
-                    if anchorid.startswith('gg'):
+                    if anchorid.startswith("gg"):
                         index = 1
                         _len = 35
-                    elif anchorid.startswith('g'):
+                    elif anchorid.startswith("g"):
                         _len = 34
                     else:
                         _len = 33
                     if parts[0].endswith(anchorid[index:_len]):
-                        return parts[0][:-(_len - index)] + parts[1]
+                        return parts[0][: -(_len - index)] + parts[1]
 
         return refid
 
@@ -562,15 +568,16 @@ def get_filename(node) -> Optional[str]:
         if not filename and node.node_type == "compound":
             file_data = self.compound_parser.parse(node.refid)
             filename = get_filename(file_data.compounddef)
-        return self.project_info.domain_for_file(filename) if filename else ''
+        return self.project_info.domain_for_file(filename) if filename else ""
 
     def join_nested_name(self, names: List[str]) -> str:
         dom = self.get_domain()
-        sep = '::' if not dom or dom == 'cpp' else '.'
+        sep = "::" if not dom or dom == "cpp" else "."
         return sep.join(names)
 
-    def run_directive(self, obj_type: str, declaration: str, contentCallback: ContentCallback,
-                      options={}) -> List[Node]:
+    def run_directive(
+        self, obj_type: str, declaration: str, contentCallback: ContentCallback, options={}
+    ) -> List[Node]:
         self.context = cast(RenderContext, self.context)
         args = [obj_type, [declaration]] + self.context.directive_args[2:]
         directive = DomainDirectiveFactory.create(self.context.domain, args)
@@ -578,8 +585,8 @@ def run_directive(self, obj_type: str, declaration: str, contentCallback: Conten
         directive.breathe_content_callback = contentCallback  # type: ignore
 
         # Translate Breathe's no-link option into the standard noindex option.
-        if 'no-link' in self.context.directive_args[2]:
-            directive.options['noindex'] = True
+        if "no-link" in self.context.directive_args[2]:
+            directive.options["noindex"] = True
         for k, v in options.items():
             directive.options[k] = v
 
@@ -588,9 +595,11 @@ def run_directive(self, obj_type: str, declaration: str, contentCallback: Conten
 
         if config.breathe_debug_trace_directives:
             global _debug_indent
-            print("{}Running directive: .. {}:: {}".format(
-                '  ' * _debug_indent,
-                directive.name, declaration))
+            print(
+                "{}Running directive: .. {}:: {}".format(
+                    "  " * _debug_indent, directive.name, declaration
+                )
+            )
             _debug_indent += 1
 
         self.nesting_level += 1
@@ -619,32 +628,38 @@ def run_directive(self, obj_type: str, declaration: str, contentCallback: Conten
             signode = finder.declarator
 
             if self.context.child:
-                signode.children = [n for n in signode.children if not n.tagname == 'desc_addname']
+                signode.children = [n for n in signode.children if not n.tagname == "desc_addname"]
         return nodes
 
-    def handle_declaration(self, node, declaration: str, *, obj_type: str = None,
-                           content_callback: ContentCallback = None,
-                           display_obj_type: str = None,
-                           declarator_callback: DeclaratorCallback = None,
-                           options={}) -> List[Node]:
+    def handle_declaration(
+        self,
+        node,
+        declaration: str,
+        *,
+        obj_type: str = None,
+        content_callback: ContentCallback = None,
+        display_obj_type: str = None,
+        declarator_callback: DeclaratorCallback = None,
+        options={}
+    ) -> List[Node]:
         if obj_type is None:
             obj_type = node.kind
         if content_callback is None:
+
             def content(contentnode):
                 contentnode.extend(self.description(node))
 
             content_callback = content
-        declaration = declaration.replace('\n', ' ')
+        declaration = declaration.replace("\n", " ")
         nodes_ = self.run_directive(obj_type, declaration, content_callback, options)
 
         assert self.app.env is not None
         if self.app.env.config.breathe_debug_trace_doxygen_ids:
             target = self.create_doxygen_target(node)
             if len(target) == 0:
-                print("{}Doxygen target: (none)".format('  ' * _debug_indent))
+                print("{}Doxygen target: (none)".format("  " * _debug_indent))
             else:
-                print("{}Doxygen target: {}".format('  ' * _debug_indent,
-                                                    target[0]['ids']))
+                print("{}Doxygen target: {}".format("  " * _debug_indent, target[0]["ids"]))
 
         # <desc><desc_signature> and then one or more <desc_signature_line>
         # each <desc_signature_line> has a sphinx_line_type which hints what is present in that line
@@ -659,12 +674,12 @@ def content(contentnode):
         sig = desc[0]
         assert isinstance(sig, addnodes.desc_signature)
         # if may or may not be a multiline signature
-        isMultiline = sig.get('is_multiline', False)
+        isMultiline = sig.get("is_multiline", False)
         declarator = None  # type: Optional[Declarator]
         if isMultiline:
             for line in sig:
                 assert isinstance(line, addnodes.desc_signature_line)
-                if line.sphinx_line_type == 'declarator':
+                if line.sphinx_line_type == "declarator":
                     declarator = line
         else:
             declarator = sig
@@ -676,17 +691,18 @@ def content(contentnode):
             if sphinx.version_info[0] < 4:
                 newStyle = False
             # but only for the C and C++ domains
-            if self.get_domain() and self.get_domain() not in ('c', 'cpp'):
+            if self.get_domain() and self.get_domain() not in ("c", "cpp"):
                 newStyle = False
             if newStyle:
                 # TODO: remove the "type: ignore" when Sphinx >= 4 is required
                 assert isinstance(n, addnodes.desc_sig_keyword)  # type: ignore
                 declarator[0] = addnodes.desc_sig_keyword(  # type: ignore
-                    display_obj_type, display_obj_type)
+                    display_obj_type, display_obj_type
+                )
             else:
                 assert isinstance(n, addnodes.desc_annotation)
                 assert n.astext()[-1] == " "
-                txt = display_obj_type + ' '
+                txt = display_obj_type + " "
                 declarator[0] = addnodes.desc_annotation(txt, txt)
         if not self.app.env.config.breathe_debug_trace_doxygen_ids:
             target = self.create_doxygen_target(node)
@@ -702,25 +718,27 @@ def get_qualification(self) -> List[str]:
         assert self.app.env is not None
         config = self.app.env.config
         if config.breathe_debug_trace_qualification:
+
             def debug_print_node(n):
                 return "node_type={}".format(n.node_type)
 
             global _debug_indent
-            print("{}{}".format(_debug_indent * '  ',
-                                debug_print_node(self.qualification_stack[0])))
+            print(
+                "{}{}".format(_debug_indent * "  ", debug_print_node(self.qualification_stack[0]))
+            )
             _debug_indent += 1
 
         names = []  # type: List[str]
         for node in self.qualification_stack[1:]:
             if config.breathe_debug_trace_qualification:
-                print("{}{}".format(_debug_indent * '  ', debug_print_node(node)))
-            if node.node_type == 'ref' and len(names) == 0:
+                print("{}{}".format(_debug_indent * "  ", debug_print_node(node)))
+            if node.node_type == "ref" and len(names) == 0:
                 if config.breathe_debug_trace_qualification:
-                    print("{}{}".format(_debug_indent * '  ', 'res='))
+                    print("{}{}".format(_debug_indent * "  ", "res="))
                 return []
-            if (node.node_type == 'compound' and
-                node.kind not in ['file', 'namespace', 'group']) or \
-                    node.node_type == 'memberdef':
+            if (
+                node.node_type == "compound" and node.kind not in ["file", "namespace", "group"]
+            ) or node.node_type == "memberdef":
                 # We skip the 'file' entries because the file name doesn't form part of the
                 # qualified name for the identifier. We skip the 'namespace' entries because if we
                 # find an object through the namespace 'compound' entry in the index.xml then we'll
@@ -728,18 +746,18 @@ def debug_print_node(n):
                 # need the 'compounddef' entry because if we find the object through the 'file'
                 # entry in the index.xml file then we need to get the namespace name from somewhere
                 names.append(node.name)
-            if (node.node_type == 'compounddef' and node.kind == 'namespace'):
+            if node.node_type == "compounddef" and node.kind == "namespace":
                 # Nested namespaces include their parent namespace(s) in compoundname. ie,
                 # compoundname is 'foo::bar' instead of just 'bar' for namespace 'bar' nested in
                 # namespace 'foo'. We need full compoundname because node_stack doesn't necessarily
                 # include parent namespaces and we stop here in case it does.
-                names.extend(reversed(node.compoundname.split('::')))
+                names.extend(reversed(node.compoundname.split("::")))
                 break
 
         names.reverse()
 
         if config.breathe_debug_trace_qualification:
-            print("{}res={}".format(_debug_indent * '  ', names))
+            print("{}res={}".format(_debug_indent * "  ", names))
             _debug_indent -= 1
         return names
 
@@ -752,15 +770,15 @@ def get_fully_qualified_name(self):
         node = node_stack[0]
 
         # If the node is a namespace, use its name because namespaces are skipped in the main loop.
-        if node.node_type == 'compound' and node.kind == 'namespace':
+        if node.node_type == "compound" and node.kind == "namespace":
             names.append(node.name)
 
         for node in node_stack:
-            if node.node_type == 'ref' and len(names) == 0:
+            if node.node_type == "ref" and len(names) == 0:
                 return node.valueOf_
-            if (node.node_type == 'compound' and
-                node.kind not in ['file', 'namespace', 'group']) or \
-                    node.node_type == 'memberdef':
+            if (
+                node.node_type == "compound" and node.kind not in ["file", "namespace", "group"]
+            ) or node.node_type == "memberdef":
                 # We skip the 'file' entries because the file name doesn't form part of the
                 # qualified name for the identifier. We skip the 'namespace' entries because if we
                 # find an object through the namespace 'compound' entry in the index.xml then we'll
@@ -768,7 +786,7 @@ def get_fully_qualified_name(self):
                 # need the 'compounddef' entry because if we find the object through the 'file'
                 # entry in the index.xml file then we need to get the namespace name from somewhere
                 names.insert(0, node.name)
-            if (node.node_type == 'compounddef' and node.kind == 'namespace'):
+            if node.node_type == "compounddef" and node.kind == "namespace":
                 # Nested namespaces include their parent namespace(s) in compoundname. ie,
                 # compoundname is 'foo::bar' instead of just 'bar' for namespace 'bar' nested in
                 # namespace 'foo'. We need full compoundname because node_stack doesn't necessarily
@@ -776,28 +794,31 @@ def get_fully_qualified_name(self):
                 names.insert(0, node.compoundname)
                 break
 
-        return '::'.join(names)
+        return "::".join(names)
 
     def create_template_prefix(self, decl) -> str:
         if not decl.templateparamlist:
             return ""
         nodes = self.render(decl.templateparamlist)
-        return 'template<' + ''.join(n.astext() for n in nodes) + '>'  # type: ignore
+        return "template<" + "".join(n.astext() for n in nodes) + ">"  # type: ignore
 
     def run_domain_directive(self, kind, names):
         domain_directive = DomainDirectiveFactory.create(
-            self.context.domain, [kind, names] + self.context.directive_args[2:])
+            self.context.domain, [kind, names] + self.context.directive_args[2:]
+        )
 
         # Translate Breathe's no-link option into the standard noindex option.
-        if 'no-link' in self.context.directive_args[2]:
-            domain_directive.options['noindex'] = True
+        if "no-link" in self.context.directive_args[2]:
+            domain_directive.options["noindex"] = True
 
         config = self.app.env.config
         if config.breathe_debug_trace_directives:
             global _debug_indent
-            print("{}Running directive (old): .. {}:: {}".format(
-                '  ' * _debug_indent,
-                domain_directive.name, ''.join(names)))
+            print(
+                "{}Running directive (old): .. {}:: {}".format(
+                    "  " * _debug_indent, domain_directive.name, "".join(names)
+                )
+            )
             _debug_indent += 1
 
         nodes = domain_directive.run()
@@ -813,7 +834,7 @@ def run_domain_directive(self, kind, names):
         signode = finder.declarator
 
         if len(names) > 0 and self.context.child:
-            signode.children = [n for n in signode.children if not n.tagname == 'desc_addname']
+            signode.children = [n for n in signode.children if not n.tagname == "desc_addname"]
         return nodes
 
     def create_doxygen_target(self, node) -> List[Element]:
@@ -855,8 +876,11 @@ def pullup(node, typ, dest):
             pullup(candNode, nodes.warning, admonitions)
             # and collapse paragraphs
             for para in candNode.traverse(nodes.paragraph):
-                if para.parent and len(para.parent) == 1 \
-                        and isinstance(para.parent, nodes.paragraph):
+                if (
+                    para.parent
+                    and len(para.parent) == 1
+                    and isinstance(para.parent, nodes.paragraph)
+                ):
                     para.replace_self(para.children)
 
             # and remove empty top-level paragraphs
@@ -887,25 +911,25 @@ def pullup(node, typ, dest):
                 for fn, fb in retvals:
                     # we created the retvals before, so we made this prefix
                     assert fn.astext().startswith("returns ")
-                    val = nodes.strong('', fn.astext()[8:])
+                    val = nodes.strong("", fn.astext()[8:])
                     # assumption from visit_docparamlist: fb is a single paragraph or nothing
                     assert len(fb) <= 1, fb
-                    bodyNodes = [val, nodes.Text(' -- ')]
+                    bodyNodes = [val, nodes.Text(" -- ")]
                     if len(fb) == 1:
                         assert isinstance(fb[0], nodes.paragraph)
                         bodyNodes.extend(fb[0])
-                    items.append(nodes.paragraph('', '', *bodyNodes))
+                    items.append(nodes.paragraph("", "", *bodyNodes))
                 # only make a bullet list if there are multiple retvals
                 if len(items) == 1:
                     body = items[0]
                 else:
                     body = nodes.bullet_list()
                     for i in items:
-                        body.append(nodes.list_item('', i))
-                fRetvals = nodes.field('',
-                                       nodes.field_name('', 'returns'),
-                                       nodes.field_body('', body))
-                fl = nodes.field_list('', *others, fRetvals)
+                        body.append(nodes.list_item("", i))
+                fRetvals = nodes.field(
+                    "", nodes.field_name("", "returns"), nodes.field_body("", body)
+                )
+                fl = nodes.field_list("", *others, fRetvals)
                 fieldLists = [fl]
 
         if self.app.config.breathe_order_parameters_first:  # type: ignore
@@ -915,9 +939,9 @@ def pullup(node, typ, dest):
 
     def update_signature(self, signature, obj_type):
         """Update the signature node if necessary, e.g. add qualifiers."""
-        prefix = obj_type + ' '
+        prefix = obj_type + " "
         annotation = addnodes.desc_annotation(prefix, prefix)
-        if signature[0].tagname != 'desc_name':
+        if signature[0].tagname != "desc_name":
             signature[0] = annotation
         else:
             signature.insert(0, annotation)
@@ -925,16 +949,16 @@ def update_signature(self, signature, obj_type):
     def render_declaration(self, node, declaration=None, description=None, **kwargs):
         if declaration is None:
             declaration = self.get_fully_qualified_name()
-        obj_type = kwargs.get('objtype', None)
+        obj_type = kwargs.get("objtype", None)
         if obj_type is None:
             obj_type = node.kind
-        nodes = self.run_domain_directive(obj_type, [declaration.replace('\n', ' ')])
+        nodes = self.run_domain_directive(obj_type, [declaration.replace("\n", " ")])
         if self.app.env.config.breathe_debug_trace_doxygen_ids:
             target = self.create_doxygen_target(node)
             if len(target) == 0:
-                print("{}Doxygen target (old): (none)".format('  ' * _debug_indent))
+                print("{}Doxygen target (old): (none)".format("  " * _debug_indent))
             else:
-                print("{}Doxygen target (old): {}".format('  ' * _debug_indent, target[0]['ids']))
+                print("{}Doxygen target (old): {}".format("  " * _debug_indent, target[0]["ids"]))
 
         rst_node = nodes[1]
         finder = NodeFinder(rst_node.document)
@@ -943,7 +967,7 @@ def render_declaration(self, node, declaration=None, description=None, **kwargs)
         signode = finder.declarator
         contentnode = finder.content
 
-        update_signature = kwargs.get('update_signature', None)
+        update_signature = kwargs.get("update_signature", None)
         if update_signature is not None:
             update_signature(signode, obj_type)
         if description is None:
@@ -977,16 +1001,17 @@ def visit_union(self, node) -> List[Node]:
         with WithContext(self, new_context):
             names = self.get_qualification()
             if self.nesting_level == 0:
-                names.extend(nodeDef.compoundname.split('::'))
+                names.extend(nodeDef.compoundname.split("::"))
             else:
-                names.append(nodeDef.compoundname.split('::')[-1])
+                names.append(nodeDef.compoundname.split("::")[-1])
             declaration = self.join_nested_name(names)
 
             def content(contentnode):
                 if nodeDef.includes:
                     for include in nodeDef.includes:
-                        contentnode.extend(self.render(include,
-                                                       new_context.create_child_context(include)))
+                        contentnode.extend(
+                            self.render(include, new_context.create_child_context(include))
+                        )
                 rendered_data = self.render(file_data, parent_context)
                 contentnode.extend(rendered_data)
 
@@ -1012,42 +1037,44 @@ def visit_class(self, node) -> List[Node]:
             # TODO: this breaks if it's a template specialization
             #       and one of the arguments contain '::'
             if self.nesting_level == 0:
-                names.extend(nodeDef.compoundname.split('::'))
+                names.extend(nodeDef.compoundname.split("::"))
             else:
-                names.append(nodeDef.compoundname.split('::')[-1])
+                names.append(nodeDef.compoundname.split("::")[-1])
             decls = [
                 self.create_template_prefix(nodeDef),
                 self.join_nested_name(names),
             ]
             # add base classes
             if len(nodeDef.basecompoundref) != 0:
-                decls.append(':')
+                decls.append(":")
             first = True
             for base in nodeDef.basecompoundref:
                 if not first:
-                    decls.append(',')
+                    decls.append(",")
                 else:
                     first = False
                 if base.prot is not None:
                     decls.append(base.prot)
-                if base.virt == 'virtual':
-                    decls.append('virtual')
+                if base.virt == "virtual":
+                    decls.append("virtual")
                 decls.append(base.content_[0].value)
-            declaration = ' '.join(decls)
+            declaration = " ".join(decls)
 
             def content(contentnode) -> None:
                 if nodeDef.includes:
                     for include in nodeDef.includes:
-                        contentnode.extend(self.render(include,
-                                                       new_context.create_child_context(include)))
+                        contentnode.extend(
+                            self.render(include, new_context.create_child_context(include))
+                        )
                 rendered_data = self.render(file_data, parent_context)
                 contentnode.extend(rendered_data)
 
-            assert kind in ('class', 'struct', 'interface')
-            display_obj_type = 'interface' if kind == 'interface' else None
-            nodes = self.handle_declaration(nodeDef, declaration, content_callback=content,
-                                            display_obj_type=display_obj_type)
-            if 'members-only' in self.context.directive_args[2]:
+            assert kind in ("class", "struct", "interface")
+            display_obj_type = "interface" if kind == "interface" else None
+            nodes = self.handle_declaration(
+                nodeDef, declaration, content_callback=content, display_obj_type=display_obj_type
+            )
+            if "members-only" in self.context.directive_args[2]:
                 assert len(nodes) >= 2
                 assert isinstance(nodes[1], addnodes.desc)
                 assert len(nodes[1]) >= 2
@@ -1069,22 +1096,24 @@ def visit_namespace(self, node) -> List[Node]:
             # definition, for proper domain detection
             names = self.get_qualification()
             if self.nesting_level == 0:
-                names.extend(nodeDef.compoundname.split('::'))
+                names.extend(nodeDef.compoundname.split("::"))
             else:
-                names.append(nodeDef.compoundname.split('::')[-1])
+                names.append(nodeDef.compoundname.split("::")[-1])
             declaration = self.join_nested_name(names)
 
             def content(contentnode):
                 if nodeDef.includes:
                     for include in nodeDef.includes:
-                        contentnode.extend(self.render(include,
-                                                       new_context.create_child_context(include)))
+                        contentnode.extend(
+                            self.render(include, new_context.create_child_context(include))
+                        )
                 rendered_data = self.render(file_data, parent_context)
                 contentnode.extend(rendered_data)
 
-            display_obj_type = 'namespace' if self.get_domain() != 'py' else 'module'
-            nodes = self.handle_declaration(nodeDef, declaration, content_callback=content,
-                                            display_obj_type=display_obj_type)
+            display_obj_type = "namespace" if self.get_domain() != "py" else "module"
+            nodes = self.handle_declaration(
+                nodeDef, declaration, content_callback=content, display_obj_type=display_obj_type
+            )
         return nodes
 
     def visit_compound(self, node, render_empty_node=True, **kwargs) -> List[Node]:
@@ -1094,18 +1123,18 @@ def visit_compound(self, node, render_empty_node=True, **kwargs) -> List[Node]:
         def get_node_info(file_data):
             return node.name, node.kind
 
-        name, kind = kwargs.get('get_node_info', get_node_info)(file_data)
-        if kind == 'union':
+        name, kind = kwargs.get("get_node_info", get_node_info)(file_data)
+        if kind == "union":
             dom = self.get_domain()
-            assert not dom or dom in ('c', 'cpp')
+            assert not dom or dom in ("c", "cpp")
             return self.visit_union(node)
-        elif kind in ('struct', 'class', 'interface'):
+        elif kind in ("struct", "class", "interface"):
             dom = self.get_domain()
-            if not dom or dom in ('c', 'cpp', 'py', 'cs'):
+            if not dom or dom in ("c", "cpp", "py", "cs"):
                 return self.visit_class(node)
-        elif kind == 'namespace':
+        elif kind == "namespace":
             dom = self.get_domain()
-            if not dom or dom in ('c', 'cpp', 'py', 'cs'):
+            if not dom or dom in ("c", "cpp", "py", "cs"):
                 return self.visit_namespace(node)
 
         self.context = cast(RenderContext, self.context)
@@ -1123,13 +1152,13 @@ def render_signature(file_data, doxygen_target, name, kind):
             arg = "%s %s" % (templatePrefix, self.get_fully_qualified_name())
 
             # add base classes
-            if kind in ('class', 'struct'):
+            if kind in ("class", "struct"):
                 bs = []
                 for base in file_data.compounddef.basecompoundref:
                     b = []
                     if base.prot is not None:
                         b.append(base.prot)
-                    if base.virt == 'virtual':
+                    if base.virt == "virtual":
                         b.append("virtual")
                     b.append(base.content_[0].value)
                     bs.append(" ".join(b))
@@ -1145,22 +1174,22 @@ def render_signature(file_data, doxygen_target, name, kind):
             finder = NodeFinder(rst_node.document)
             rst_node.walk(finder)
 
-            if kind in ('interface', 'namespace'):
+            if kind in ("interface", "namespace"):
                 # This is not a real C++ declaration type that Sphinx supports,
                 # so we hax the replacement of it.
-                finder.declarator[0] = addnodes.desc_annotation(kind + ' ', kind + ' ')
+                finder.declarator[0] = addnodes.desc_annotation(kind + " ", kind + " ")
 
             rst_node.children[0].insert(0, doxygen_target)
             return nodes, finder.content
 
         refid = self.get_refid(node.refid)
-        render_sig = kwargs.get('render_signature', render_signature)
+        render_sig = kwargs.get("render_signature", render_signature)
         with WithContext(self, new_context):
             # Pretend that the signature is being rendered in context of the
             # definition, for proper domain detection
             nodes, contentnode = render_sig(
-                file_data, self.target_handler.create_target(refid),
-                name, kind)
+                file_data, self.target_handler.create_target(refid), name, kind
+            )
 
         if file_data.compounddef.includes:
             for include in file_data.compounddef.includes:
@@ -1194,8 +1223,8 @@ def render_signature(file_data, doxygen_target, name, kind):
                 rst_node.append(title_signode)
 
             rst_node.document = self.state.document
-            rst_node['objtype'] = kind
-            rst_node['domain'] = self.get_domain() if self.get_domain() else 'cpp'
+            rst_node["objtype"] = kind
+            rst_node["domain"] = self.get_domain() if self.get_domain() else "cpp"
 
             contentnode = addnodes.desc_content()
             rst_node.append(contentnode)
@@ -1253,11 +1282,11 @@ def visit_compounddef(self, node) -> List[Node]:
         self.context = cast(RenderContext, self.context)
         options = self.context.directive_args[2]
         section_order = None
-        if 'sections' in options:
-            section_order = {sec: i for i, sec in enumerate(options['sections'].split(' '))}
+        if "sections" in options:
+            section_order = {sec: i for i, sec in enumerate(options["sections"].split(" "))}
         membergroup_order = None
-        if 'membergroups' in options:
-            membergroup_order = {sec: i for i, sec in enumerate(options['membergroups'].split(' '))}
+        if "membergroups" in options:
+            membergroup_order = {sec: i for i, sec in enumerate(options["membergroups"].split(" "))}
         nodemap = {}  # type: Dict[int, List[Node]]
 
         def addnode(kind, lam):
@@ -1266,9 +1295,9 @@ def addnode(kind, lam):
             elif kind in section_order:
                 nodemap.setdefault(section_order[kind], []).extend(lam())
 
-        if 'members-only' not in options:
-            addnode('briefdescription', lambda: self.render_optional(node.briefdescription))
-            addnode('detaileddescription', lambda: self.render_optional(node.detaileddescription))
+        if "members-only" not in options:
+            addnode("briefdescription", lambda: self.render_optional(node.briefdescription))
+            addnode("detaileddescription", lambda: self.render_optional(node.detaileddescription))
 
             def render_derivedcompoundref(node):
                 if node is None:
@@ -1276,15 +1305,15 @@ def render_derivedcompoundref(node):
                 output = self.render_iterable(node)
                 if not output:
                     return []
-                return [nodes.paragraph(
-                    '',
-                    '',
-                    nodes.Text('Subclassed by '),
-                    *intersperse(output, nodes.Text(', '))
-                )]
-
-            addnode('derivedcompoundref',
-                    lambda: render_derivedcompoundref(node.derivedcompoundref))
+                return [
+                    nodes.paragraph(
+                        "", "", nodes.Text("Subclassed by "), *intersperse(output, nodes.Text(", "))
+                    )
+                ]
+
+            addnode(
+                "derivedcompoundref", lambda: render_derivedcompoundref(node.derivedcompoundref)
+            )
 
         section_nodelists = {}  # type: Dict[str, List[Node]]
 
@@ -1300,9 +1329,9 @@ def render_derivedcompoundref(node):
             if not child_nodes:
                 # Skip empty section
                 continue
-            rst_node = nodes.container(classes=['breathe-sectiondef'])
+            rst_node = nodes.container(classes=["breathe-sectiondef"])
             rst_node.document = self.state.document
-            rst_node['objtype'] = kind
+            rst_node["objtype"] = kind
             rst_node.extend(child_nodes)
             # We store the nodes as a list against the kind in a dictionary as the kind can be
             # 'user-edited' and that can repeat so this allows us to collect all the 'user-edited'
@@ -1314,14 +1343,14 @@ def render_derivedcompoundref(node):
             addnode(kind, lambda: section_nodelists.get(kind, []))
 
         # Take care of innerclasses
-        addnode('innerclass', lambda: self.render_iterable(node.innerclass))
-        addnode('innernamespace', lambda: self.render_iterable(node.innernamespace))
+        addnode("innerclass", lambda: self.render_iterable(node.innerclass))
+        addnode("innernamespace", lambda: self.render_iterable(node.innernamespace))
 
-        if 'inner' in options:
+        if "inner" in options:
             for node in node.innergroup:
                 file_data = self.compound_parser.parse(node.refid)
                 inner = file_data.compounddef
-                addnode('innergroup', lambda: self.visit_compounddef(inner))
+                addnode("innergroup", lambda: self.visit_compounddef(inner))
 
         nodelist = []
         for i, nodes_ in sorted(nodemap.items()):
@@ -1341,7 +1370,7 @@ def visit_sectiondef(self, node) -> List[Node]:
         node_list.extend(self.render_iterable(node.memberdef))
 
         if node_list:
-            if 'members-only' in options:
+            if "members-only" in options:
                 return node_list
 
             text = self.section_titles[node.kind]
@@ -1357,10 +1386,12 @@ def visit_sectiondef(self, node) -> List[Node]:
 
             # Use rubric for the title because, unlike the docutils element "section",
             # it doesn't interfere with the document structure.
-            idtext = text.replace(' ', '-').lower()
-            rubric = nodes.rubric(text=text,
-                                  classes=['breathe-sectiondef-title'],
-                                  ids=['breathe-section-title-' + idtext])
+            idtext = text.replace(" ", "-").lower()
+            rubric = nodes.rubric(
+                text=text,
+                classes=["breathe-sectiondef-title"],
+                ids=["breathe-section-title-" + idtext],
+            )
             res = [rubric]  # type: List[Node]
             return res + node_list
         return []
@@ -1433,7 +1464,7 @@ def visit_docpara(self, node) -> List[Node]:
 
             # note: all these gets pulled up and reordered in description()
             if len(defs) != 0:
-                deflist = nodes.definition_list('', *defs)
+                deflist = nodes.definition_list("", *defs)
                 nodelist.append(deflist)
             nodelist.extend(paramList)
             nodelist.extend(fields)
@@ -1476,15 +1507,15 @@ def visit_docmarkup(self, node) -> List[Node]:
         return [creator("", "", *nodelist)]
 
     def visit_docsectN(self, node) -> List[Node]:
-        '''
+        """
         Docutils titles are defined by their level inside the document so
         the proper structure is only guaranteed by the Doxygen XML.
 
         Doxygen command mapping to XML element name:
         @section == sect1, @subsection == sect2, @subsubsection == sect3
-        '''
+        """
         section = nodes.section()
-        section['ids'].append(self.get_refid(node.id))
+        section["ids"].append(self.get_refid(node.id))
         section += nodes.title(node.title, node.title)
         section += self.create_doxygen_target(node)
         section += self.render_iterable(node.content_)
@@ -1498,16 +1529,21 @@ def visit_docsimplesect(self, node) -> List[Node]:
 
         nodelist = self.render_iterable(node.para)
 
-        if node.kind in ('pre', 'post', 'return'):
-            return [nodes.field_list('', nodes.field(
-                '',
-                nodes.field_name('', nodes.Text(node.kind)),
-                nodes.field_body('', *nodelist)
-            ))]
-        elif node.kind == 'warning':
-            return [nodes.warning('', *nodelist)]
-        elif node.kind == 'note':
-            return [nodes.note('', *nodelist)]
+        if node.kind in ("pre", "post", "return"):
+            return [
+                nodes.field_list(
+                    "",
+                    nodes.field(
+                        "",
+                        nodes.field_name("", nodes.Text(node.kind)),
+                        nodes.field_body("", *nodelist),
+                    ),
+                )
+            ]
+        elif node.kind == "warning":
+            return [nodes.warning("", *nodelist)]
+        elif node.kind == "note":
+            return [nodes.note("", *nodelist)]
 
         if node.kind == "par":
             text = self.render(node.title)
@@ -1533,21 +1569,19 @@ def visit_docformula(self, node) -> List[Node]:
             # Either inline
             if latex.startswith("$") and latex.endswith("$"):
                 latex = latex[1:-1]
-                nodelist.append(nodes.math(text=latex,
-                                           label=None,
-                                           nowrap=False,
-                                           docname=docname,
-                                           number=None))
+                nodelist.append(
+                    nodes.math(text=latex, label=None, nowrap=False, docname=docname, number=None)
+                )
             # Else we're multiline
             else:
                 if latex.startswith("\\[") and latex.endswith("\\]"):
                     latex = latex[2:-2:]
 
-                nodelist.append(nodes.math_block(text=latex,
-                                                 label=None,
-                                                 nowrap=False,
-                                                 docname=docname,
-                                                 number=None))
+                nodelist.append(
+                    nodes.math_block(
+                        text=latex, label=None, nowrap=False, docname=docname, number=None
+                    )
+                )
         return nodelist
 
     def visit_listing(self, node) -> List[Node]:
@@ -1560,11 +1594,7 @@ def visit_listing(self, node) -> List[Node]:
 
         # Add blank string at the start otherwise for some reason it renders
         # the pending_xref tags around the kind in plain text
-        block = nodes.literal_block(
-            "",
-            "",
-            *nodelist
-        )
+        block = nodes.literal_block("", "", *nodelist)
         return [block]
 
     def visit_codeline(self, node) -> List[Node]:
@@ -1583,11 +1613,16 @@ def _nested_inline_parse_with_titles(self, content, node) -> str:
         self.state.memo.title_styles = []
         self.state.memo.section_level = 0
         try:
-            return self.state.nested_parse(content, 0, node, match_titles=1,
-                                           state_machine_kwargs={
-                                               'state_classes': (InlineText,),
-                                               'initial_state': 'InlineText'
-                                           })
+            return self.state.nested_parse(
+                content,
+                0,
+                node,
+                match_titles=1,
+                state_machine_kwargs={
+                    "state_classes": (InlineText,),
+                    "initial_state": "InlineText",
+                },
+            )
         finally:
             self.state.memo.title_styles = surrounding_title_styles
             self.state.memo.section_level = surrounding_section_level
@@ -1658,7 +1693,7 @@ def visit_inc(self, node) -> List[Node]:
         if node.local == u"yes":
             text = '#include "%s"' % node.content_[0].getValue()
         else:
-            text = '#include <%s>' % node.content_[0].getValue()
+            text = "#include <%s>" % node.content_[0].getValue()
 
         return [nodes.emphasis(text=text)]
 
@@ -1677,7 +1712,7 @@ def visit_doclistitem(self, node) -> List[Node]:
         nodelist = self.render_iterable(node.para)
         return [nodes.list_item("", *nodelist)]
 
-    numeral_kind = ['arabic', 'loweralpha', 'lowerroman', 'upperalpha', 'upperroman']
+    numeral_kind = ["arabic", "loweralpha", "lowerroman", "upperalpha", "upperroman"]
 
     def render_unordered(self, children) -> List[Node]:
         nodelist_list = nodes.bullet_list("", *children)
@@ -1686,9 +1721,9 @@ def render_unordered(self, children) -> List[Node]:
     def render_enumerated(self, children, nesting_level) -> List[Node]:
         nodelist_list = nodes.enumerated_list("", *children)
         idx = nesting_level % len(SphinxRenderer.numeral_kind)
-        nodelist_list['enumtype'] = SphinxRenderer.numeral_kind[idx]
-        nodelist_list['prefix'] = ''
-        nodelist_list['suffix'] = '.'
+        nodelist_list["enumtype"] = SphinxRenderer.numeral_kind[idx]
+        nodelist_list["prefix"] = ""
+        nodelist_list["suffix"] = "."
         return [nodelist_list]
 
     def visit_doclist(self, node) -> List[Node]:
@@ -1696,7 +1731,7 @@ def visit_doclist(self, node) -> List[Node]:
 
         The specifics of the actual list rendering are handled by the
         decorator around the generic render function.
-        Render all the children depth-first. """
+        Render all the children depth-first."""
         """ Call the wrapped render function. Update the nesting level for the enumerated lists. """
         if node.node_subtype == "itemized":
             val = self.render_iterable(node.listitem)
@@ -1729,7 +1764,7 @@ def visit_docxrefsect(self, node) -> List[Node]:
         assert self.app.env is not None
 
         signode = addnodes.desc_signature()
-        title = node.xreftitle[0] + ':'
+        title = node.xreftitle[0] + ":"
         titlenode = nodes.emphasis(text=title)
         ref = addnodes.pending_xref(
             "",
@@ -1738,7 +1773,8 @@ def visit_docxrefsect(self, node) -> List[Node]:
             refexplicit=True,
             reftarget=node.id,
             refdoc=self.app.env.docname,
-            *[titlenode])
+            *[titlenode]
+        )
         signode += ref
 
         nodelist = self.render(node.xrefdescription)
@@ -1746,8 +1782,8 @@ def visit_docxrefsect(self, node) -> List[Node]:
         contentnode += nodelist
 
         descnode = addnodes.desc()
-        descnode['objtype'] = 'xrefsect'
-        descnode['domain'] = self.get_domain() if self.get_domain() else 'cpp'
+        descnode["objtype"] = "xrefsect"
+        descnode["domain"] = self.get_domain() if self.get_domain() else "cpp"
         descnode += signode
         descnode += contentnode
 
@@ -1757,8 +1793,8 @@ def visit_docvariablelist(self, node) -> List[Node]:
         output = []
         for varlistentry, listitem in zip(node.varlistentries, node.listitems):
             descnode = addnodes.desc()
-            descnode['objtype'] = 'varentry'
-            descnode['domain'] = self.get_domain() if self.get_domain() else 'cpp'
+            descnode["objtype"] = "varentry"
+            descnode["domain"] = self.get_domain() if self.get_domain() else "cpp"
             signode = addnodes.desc_signature()
             signode += self.render_optional(varlistentry)
             descnode += signode
@@ -1778,18 +1814,18 @@ def visit_docanchor(self, node) -> List[Node]:
     def visit_docentry(self, node) -> List[Node]:
         col = nodes.entry()
         col += self.render_iterable(node.para)
-        if node.thead == 'yes':
-            col['heading'] = True
+        if node.thead == "yes":
+            col["heading"] = True
         if node.rowspan:
-            col['morerows'] = int(node.rowspan) - 1
+            col["morerows"] = int(node.rowspan) - 1
         if node.colspan:
-            col['morecols'] = int(node.colspan) - 1
+            col["morecols"] = int(node.colspan) - 1
         return [col]
 
     def visit_docrow(self, node) -> List[Node]:
         row = nodes.row()
         cols = self.render_iterable(node.entry)
-        if all(col.get('heading', False) for col in cols):
+        if all(col.get("heading", False) for col in cols):
             elem = nodes.thead()
         else:
             elem = nodes.tbody()
@@ -1799,11 +1835,11 @@ def visit_docrow(self, node) -> List[Node]:
 
     def visit_doctable(self, node) -> List[Node]:
         table = nodes.table()
-        table['classes'] += ['colwidths-auto']
+        table["classes"] += ["colwidths-auto"]
         tgroup = nodes.tgroup(cols=node.cols)
         for _ in range(node.cols):
             colspec = nodes.colspec()
-            colspec.attributes['colwidth'] = 'auto'
+            colspec.attributes["colwidth"] = "auto"
             tgroup += colspec
         table += tgroup
         rows = self.render_iterable(node.row)
@@ -1839,41 +1875,43 @@ def visit_linkedtext(self, node) -> List[Node]:
 
     def visit_function(self, node) -> List[Node]:
         dom = self.get_domain()
-        if not dom or dom in ('c', 'cpp', 'py', 'cs'):
+        if not dom or dom in ("c", "cpp", "py", "cs"):
             names = self.get_qualification()
             names.append(node.get_name())
             name = self.join_nested_name(names)
-            if dom == 'py':
+            if dom == "py":
                 declaration = name + node.get_argsstring()
-            elif dom == 'cs':
-                declaration = ' '.join([
-                    self.create_template_prefix(node),
-                    ''.join(n.astext() for n in self.render(node.get_type())),  # type: ignore
-                    name,
-                    node.get_argsstring()
-                ])
+            elif dom == "cs":
+                declaration = " ".join(
+                    [
+                        self.create_template_prefix(node),
+                        "".join(n.astext() for n in self.render(node.get_type())),  # type: ignore
+                        name,
+                        node.get_argsstring(),
+                    ]
+                )
             else:
                 elements = [self.create_template_prefix(node)]
-                if node.static == 'yes':
-                    elements.append('static')
-                if node.inline == 'yes':
-                    elements.append('inline')
-                if node.kind == 'friend':
-                    elements.append('friend')
-                if node.virt in ('virtual', 'pure-virtual'):
-                    elements.append('virtual')
-                if node.explicit == 'yes':
-                    elements.append('explicit')
+                if node.static == "yes":
+                    elements.append("static")
+                if node.inline == "yes":
+                    elements.append("inline")
+                if node.kind == "friend":
+                    elements.append("friend")
+                if node.virt in ("virtual", "pure-virtual"):
+                    elements.append("virtual")
+                if node.explicit == "yes":
+                    elements.append("explicit")
                 # TODO: handle constexpr when parser has been updated
                 #       but Doxygen seems to leave it in the type anyway
-                typ = ''.join(n.astext() for n in self.render(node.get_type()))
+                typ = "".join(n.astext() for n in self.render(node.get_type()))
                 # Doxygen sometimes leaves 'static' in the type,
                 # e.g., for "constexpr static auto f()"
-                typ = typ.replace('static ', '')
+                typ = typ.replace("static ", "")
                 elements.append(typ)
                 elements.append(name)
                 elements.append(node.get_argsstring())
-                declaration = ' '.join(elements)
+                declaration = " ".join(elements)
             nodes = self.handle_declaration(node, declaration)
             return nodes
         else:
@@ -1885,28 +1923,27 @@ def visit_function(self, node) -> List[Node]:
                 param_decl = get_param_decl(param)
                 param_list.append(param_decl)
             templatePrefix = self.create_template_prefix(node)
-            signature = '{0}{1}({2})'.format(
-                templatePrefix,
-                get_definition_without_template_args(node),
-                ', '.join(param_list))
+            signature = "{0}{1}({2})".format(
+                templatePrefix, get_definition_without_template_args(node), ", ".join(param_list)
+            )
 
             # Add CV-qualifiers.
-            if node.const == 'yes':
-                signature += ' const'
+            if node.const == "yes":
+                signature += " const"
             # The doxygen xml output doesn't register 'volatile' as the xml attribute for functions
             # until version 1.8.8 so we also check argsstring:
             #     https://bugzilla.gnome.org/show_bug.cgi?id=733451
-            if node.volatile == 'yes' or node.argsstring.endswith('volatile'):
-                signature += ' volatile'
+            if node.volatile == "yes" or node.argsstring.endswith("volatile"):
+                signature += " volatile"
 
-            if node.refqual == 'lvalue':
-                signature += '&'
-            elif node.refqual == 'rvalue':
-                signature += '&&'
+            if node.refqual == "lvalue":
+                signature += "&"
+            elif node.refqual == "rvalue":
+                signature += "&&"
 
             # Add `= 0` for pure virtual members.
-            if node.virt == 'pure-virtual':
-                signature += '= 0'
+            if node.virt == "pure-virtual":
+                signature += "= 0"
 
             self.context = cast(RenderContext, self.context)
             self.context.directive_args[1] = [signature]
@@ -1917,10 +1954,11 @@ def visit_function(self, node) -> List[Node]:
             if self.app.env.config.breathe_debug_trace_doxygen_ids:
                 target = self.create_doxygen_target(node)
                 if len(target) == 0:
-                    print("{}Doxygen target (old): (none)".format('  ' * _debug_indent))
+                    print("{}Doxygen target (old): (none)".format("  " * _debug_indent))
                 else:
-                    print("{}Doxygen target (old): {}".format('  ' * _debug_indent,
-                                                              target[0]['ids']))
+                    print(
+                        "{}Doxygen target (old): {}".format("  " * _debug_indent, target[0]["ids"])
+                    )
 
             rst_node = nodes[1]
             finder = NodeFinder(rst_node.document)
@@ -1974,25 +2012,25 @@ def visit_enumvalue(self, node) -> List[Node]:
             declaration = node.name + self.make_initializer(node)
         else:
             declaration = node.name
-        return self.handle_declaration(node, declaration, obj_type='enumvalue')
+        return self.handle_declaration(node, declaration, obj_type="enumvalue")
 
     def visit_typedef(self, node) -> List[Node]:
-        type_ = ''.join(n.astext() for n in self.render(node.get_type()))  # type: ignore
+        type_ = "".join(n.astext() for n in self.render(node.get_type()))  # type: ignore
         names = self.get_qualification()
         names.append(node.get_name())
         name = self.join_nested_name(names)
-        if node.definition.startswith('using '):
+        if node.definition.startswith("using "):
             # TODO: looks like Doxygen does not generate the proper XML
             #       for the template parameter list
             declaration = self.create_template_prefix(node)
-            declaration += ' ' + name + " = " + type_
+            declaration += " " + name + " = " + type_
         else:
             # TODO: Both "using" and "typedef" keywords get into this function,
             #   and if no @typedef comment was added, the definition should
             #   contain the full text. If a @typedef was used instead, the
             #   definition has only the typename, which makes it impossible to
             #   distinguish between them so fallback to "typedef" behavior here.
-            declaration = ' '.join([type_, name, node.get_argsstring()])
+            declaration = " ".join([type_, name, node.get_argsstring()])
         return self.handle_declaration(node, declaration)
 
     def make_initializer(self, node) -> str:
@@ -2002,12 +2040,12 @@ def make_initializer(self, node) -> str:
             render_nodes = self.render(initializer)
             # Do not append separators for paragraphs.
             if not isinstance(render_nodes[0], nodes.paragraph):
-                separator = ' '
-                if not render_nodes[0].startswith('='):  # type: ignore
-                    separator += '= '
+                separator = " "
+                if not render_nodes[0].startswith("="):  # type: ignore
+                    separator += "= "
                 signature.append(nodes.Text(separator))
             signature.extend(render_nodes)
-        return ''.join(n.astext() for n in signature)  # type: ignore
+        return "".join(n.astext() for n in signature)  # type: ignore
 
     def visit_variable(self, node) -> List[Node]:
         names = self.get_qualification()
@@ -2015,63 +2053,65 @@ def visit_variable(self, node) -> List[Node]:
         name = self.join_nested_name(names)
         dom = self.get_domain()
         options = {}
-        if dom == 'py':
+        if dom == "py":
             declaration = name
-            initializer = self.make_initializer(node).strip().lstrip('=').strip()
+            initializer = self.make_initializer(node).strip().lstrip("=").strip()
             if len(initializer) != 0:
-                options['value'] = initializer
-        elif dom == 'cs':
-            declaration = ' '.join([
-                self.create_template_prefix(node),
-                ''.join(n.astext() for n in self.render(node.get_type())),  # type: ignore
-                name,
-                node.get_argsstring()
-            ])
+                options["value"] = initializer
+        elif dom == "cs":
+            declaration = " ".join(
+                [
+                    self.create_template_prefix(node),
+                    "".join(n.astext() for n in self.render(node.get_type())),  # type: ignore
+                    name,
+                    node.get_argsstring(),
+                ]
+            )
             if node.get_gettable() or node.get_settable():
-                declaration += '{'
+                declaration += "{"
                 if node.get_gettable():
-                    declaration += 'get;'
+                    declaration += "get;"
                 if node.get_settable():
-                    declaration += 'set;'
-                declaration += '}'
+                    declaration += "set;"
+                declaration += "}"
             declaration += self.make_initializer(node)
         else:
             elements = [self.create_template_prefix(node)]
-            if node.static == 'yes':
-                elements.append('static')
-            if node.mutable == 'yes':
-                elements.append('mutable')
-            typename = ''.join(n.astext() for n in self.render(node.get_type()))
-            if dom == 'c' and '::' in typename:
-                typename = typename.replace('::', '.')
+            if node.static == "yes":
+                elements.append("static")
+            if node.mutable == "yes":
+                elements.append("mutable")
+            typename = "".join(n.astext() for n in self.render(node.get_type()))
+            if dom == "c" and "::" in typename:
+                typename = typename.replace("::", ".")
             elements.append(typename)
             elements.append(name)
             elements.append(node.get_argsstring())
             elements.append(self.make_initializer(node))
-            declaration = ' '.join(elements)
-        if not dom or dom in ('c', 'cpp', 'py', 'cs'):
+            declaration = " ".join(elements)
+        if not dom or dom in ("c", "cpp", "py", "cs"):
             return self.handle_declaration(node, declaration, options=options)
         else:
             return self.render_declaration(node, declaration)
 
     def visit_friendclass(self, node) -> List[Node]:
         dom = self.get_domain()
-        assert not dom or dom == 'cpp'
+        assert not dom or dom == "cpp"
 
         desc = addnodes.desc()
-        desc['objtype'] = 'friendclass'
-        desc['domain'] = self.get_domain() if self.get_domain() else 'cpp'
+        desc["objtype"] = "friendclass"
+        desc["domain"] = self.get_domain() if self.get_domain() else "cpp"
         signode = addnodes.desc_signature()
         desc += signode
 
-        typ = ''.join(n.astext() for n in self.render(node.get_type()))  # type: ignore
+        typ = "".join(n.astext() for n in self.render(node.get_type()))  # type: ignore
         # in Doxygen < 1.9 the 'friend' part is there, but afterwards not
         # https://github.com/michaeljones/breathe/issues/616
         assert typ in ("friend class", "friend struct", "class", "struct")
-        if not typ.startswith('friend '):
-            typ = 'friend ' + typ
+        if not typ.startswith("friend "):
+            typ = "friend " + typ
         signode += addnodes.desc_annotation(typ, typ)
-        signode += nodes.Text(' ')
+        signode += nodes.Text(" ")
         # expr = cpp.CPPExprRole(asCode=False)
         # expr.text = node.name
         # TODO: set most of the things that SphinxRole.__call__ sets
@@ -2079,8 +2119,9 @@ def visit_friendclass(self, node) -> List[Node]:
         signode += nodes.Text(node.name)
         return [desc]
 
-    def visit_templateparam(self, node: compound.paramTypeSub, *,
-                            insertDeclNameByParsing: bool = False) -> List[Node]:
+    def visit_templateparam(
+        self, node: compound.paramTypeSub, *, insertDeclNameByParsing: bool = False
+    ) -> List[Node]:
         nodelist = []
 
         # Parameter type
@@ -2089,9 +2130,9 @@ def visit_templateparam(self, node: compound.paramTypeSub, *,
             # Render keywords as annotations for consistency with the cpp domain.
             if len(type_nodes) > 0 and isinstance(type_nodes[0], str):
                 first_node = type_nodes[0]
-                for keyword in ['typename', 'class']:
-                    if first_node.startswith(keyword + ' '):
-                        type_nodes[0] = nodes.Text(first_node.replace(keyword, '', 1))
+                for keyword in ["typename", "class"]:
+                    if first_node.startswith(keyword + " "):
+                        type_nodes[0] = nodes.Text(first_node.replace(keyword, "", 1))
                         type_nodes.insert(0, addnodes.desc_annotation(keyword, keyword))
                         break
             nodelist.extend(type_nodes)
@@ -2100,23 +2141,27 @@ def visit_templateparam(self, node: compound.paramTypeSub, *,
         if node.declname:
             dom = self.get_domain()
             if not dom:
-                dom = 'cpp'
+                dom = "cpp"
             appendDeclName = True
             if insertDeclNameByParsing:
-                if dom == 'cpp' and sphinx.version_info >= (4, 1, 0):
+                if dom == "cpp" and sphinx.version_info >= (4, 1, 0):
                     parser = cpp.DefinitionParser(
-                        ''.join(n.astext() for n in nodelist),
+                        "".join(n.astext() for n in nodelist),
                         location=self.state.state_machine.get_source_and_line(),
-                        config=self.app.config)
+                        config=self.app.config,
+                    )
                     try:
                         # we really should use _parse_template_parameter()
                         # but setting a name there is non-trivial, so we use type
-                        ast = parser._parse_type(named='single', outer='templateParam')
+                        ast = parser._parse_type(named="single", outer="templateParam")
                         assert ast.name is None
                         nn = cpp.ASTNestedName(
-                            names=[cpp.ASTNestedNameElement(
-                                cpp.ASTIdentifier(node.declname), None)],
-                            templates=[False], rooted=False)
+                            names=[
+                                cpp.ASTNestedNameElement(cpp.ASTIdentifier(node.declname), None)
+                            ],
+                            templates=[False],
+                            rooted=False,
+                        )
                         ast.name = nn
                         # the actual nodes don't matter, as it is astext()-ed later
                         nodelist = [nodes.Text(str(ast))]
@@ -2186,11 +2231,12 @@ def visit_docparamlist(self, node) -> List[Node]:
                         assert len(content) == 1 or len(content) == 2, content
                         thisName = self.render(content[-1])
                         if len(nameNodes) != 0:
-                            if node.kind == 'exception':
+                            if node.kind == "exception":
                                 msg = "Doxygen \\exception commands with multiple names can not be"
                                 msg += " converted to a single :throws: field in Sphinx."
                                 msg += " Exception '{}' suppresed from output.".format(
-                                    ''.join(n.astext() for n in thisName))
+                                    "".join(n.astext() for n in thisName)
+                                )
                                 self.state.document.reporter.warning(msg)
                                 continue
                             nameNodes.append(nodes.Text(", "))
@@ -2198,27 +2244,25 @@ def visit_docparamlist(self, node) -> List[Node]:
                         if len(content) == 2:
                             # note, each paramName node seems to have the same direction,
                             # so just use the last one
-                            dir = ''.join(n.astext() for n in self.render(content[0])).strip()
-                            assert dir in ('[in]', '[out]', '[inout]'), ">" + dir + "<"
-                            parameterDirectionNodes = [
-                                nodes.strong(dir, dir),
-                                nodes.Text(' ', ' ')
-                            ]
+                            dir = "".join(n.astext() for n in self.render(content[0])).strip()
+                            assert dir in ("[in]", "[out]", "[inout]"), ">" + dir + "<"
+                            parameterDirectionNodes = [nodes.strong(dir, dir), nodes.Text(" ", " ")]
             # it seems that Sphinx expects the name to be a single node,
             # so let's make it that
-            txt = fieldListName[node.kind] + ' '
+            txt = fieldListName[node.kind] + " "
             for n in nameNodes:
                 txt += n.astext()
-            name = nodes.field_name('', nodes.Text(txt))
+            name = nodes.field_name("", nodes.Text(txt))
             bodyNodes = self.render_optional(item.parameterdescription)
             # TODO: is it correct that bodyNodes is either empty or a single paragraph?
             assert len(bodyNodes) <= 1, bodyNodes
             if len(bodyNodes) == 1:
                 assert isinstance(bodyNodes[0], nodes.paragraph)
-                bodyNodes = [nodes.paragraph(
-                    '', '', *(parameterDirectionNodes + bodyNodes[0].children))]
-            body = nodes.field_body('', *bodyNodes)
-            field = nodes.field('', name, body)
+                bodyNodes = [
+                    nodes.paragraph("", "", *(parameterDirectionNodes + bodyNodes[0].children))
+                ]
+            body = nodes.field_body("", *bodyNodes)
+            field = nodes.field("", name, body)
             fieldList += field
         return [fieldList]
 
@@ -2234,8 +2278,9 @@ def dispatch_compound(self, node) -> List[Node]:
 
     def dispatch_memberdef(self, node) -> List[Node]:
         """Dispatch handling of a memberdef node to a suitable visit method."""
-        if node.kind in ("function", "signal", "slot") or \
-                (node.kind == 'friend' and node.argsstring):
+        if node.kind in ("function", "signal", "slot") or (
+            node.kind == "friend" and node.argsstring
+        ):
             return self.visit_function(node)
         if node.kind == "enum":
             return self.visit_enum(node)
@@ -2320,8 +2365,11 @@ def render_string(self, node: str) -> List[Union[nodes.Text, nodes.paragraph]]:
                 delimiter = "\n"
             if delimiter:
                 # Render lines as paragraphs because RST doesn't have line breaks.
-                return [nodes.paragraph('', '', nodes.Text(line.strip()))
-                        for line in node.split(delimiter) if line.strip()]
+                return [
+                    nodes.paragraph("", "", nodes.Text(line.strip()))
+                    for line in node.split(delimiter)
+                    if line.strip()
+                ]
             # importantly, don't strip whitespace as visit_docpara uses it to collapse
             # consecutive nodes.Text and rerender them with this function.
             return [nodes.Text(node)]
@@ -2357,6 +2405,6 @@ def render_iterable(self, iterable: List[Node]) -> List[Node]:
 
 
 def setup(app: Sphinx) -> None:
-    app.add_config_value('breathe_debug_trace_directives', False, '')
-    app.add_config_value('breathe_debug_trace_doxygen_ids', False, '')
-    app.add_config_value('breathe_debug_trace_qualification', False, '')
+    app.add_config_value("breathe_debug_trace_directives", False, "")
+    app.add_config_value("breathe_debug_trace_doxygen_ids", False, "")
+    app.add_config_value("breathe_debug_trace_qualification", False, "")
diff --git a/breathe/renderer/target.py b/breathe/renderer/target.py
index e08f772ff..57c810380 100644
--- a/breathe/renderer/target.py
+++ b/breathe/renderer/target.py
@@ -33,8 +33,9 @@ def create_target(self, refid: str) -> List[Element]:
         return []
 
 
-def create_target_handler(options: Dict[str, Any], project_info: ProjectInfo,
-                          document: nodes.document) -> TargetHandler:
+def create_target_handler(
+    options: Dict[str, Any], project_info: ProjectInfo, document: nodes.document
+) -> TargetHandler:
     if "no-link" in options:
         return _NullTargetHandler()
     return _RealTargetHandler(project_info, document)
diff --git a/documentation/source/conf.py b/documentation/source/conf.py
index ebaeb8645..82dc903b9 100644
--- a/documentation/source/conf.py
+++ b/documentation/source/conf.py
@@ -22,86 +22,86 @@
 # If your extensions are in another directory, add it here. If the directory
 # is relative to the documentation root, use os.path.abspath to make it
 # absolute, like shown here.
-#sys.path.append(os.path.abspath('.'))
+# sys.path.append(os.path.abspath('.'))
 
 # General configuration
 # ---------------------
 
 # Add any Sphinx extension module names here, as strings. They can be extensions
 # coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
-extensions = [ 'breathe', 'sphinx.ext.mathjax', 'sphinx.ext.ifconfig' ]
+extensions = ["breathe", "sphinx.ext.mathjax", "sphinx.ext.ifconfig"]
 
-read_the_docs_build = os.environ.get('READTHEDOCS', None) == 'True'
-travis_build = os.environ.get('TRAVIS_CI', None) == 'True'
+read_the_docs_build = os.environ.get("READTHEDOCS", None) == "True"
+travis_build = os.environ.get("TRAVIS_CI", None) == "True"
 
 # Get a description of the current position. Use Popen for 2.6 compat
-git_tag = subprocess.Popen(['git', 'describe', '--tags'], stdout=subprocess.PIPE).communicate()[0]
+git_tag = subprocess.Popen(["git", "describe", "--tags"], stdout=subprocess.PIPE).communicate()[0]
 
 # convert from bytes to string
-git_tag = git_tag.decode('ascii')
+git_tag = git_tag.decode("ascii")
 
 if travis_build:
 
     # Don't attempt to set the path as breathe is installed to virtualenv on travis
 
     # Set values with simple strings
-    version = '\'travis\''
-    release = '\'travis\''
-    documentation_build = 'travis'
+    version = "'travis'"
+    release = "'travis'"
+    documentation_build = "travis"
 
 elif read_the_docs_build:
 
     # On RTD we'll be in the 'source' directory
-    sys.path.append('../../')
+    sys.path.append("../../")
 
     # The version info for the project you're documenting, acts as replacement for
     # |version| and |release|, also used in various other places throughout the
     # built documents.
     #
     # The short X.Y version.
-    version = '\'unknown\''
+    version = "'unknown'"
     # The full version, including alpha/beta/rc tags.
-    release = '\'unknown\''
+    release = "'unknown'"
 
     # Check if it matches a pure tag number vX.Y.Z, rather than vX.Y.Z-91-g8676988 which is how
     # non-tagged commits are described (ie. relative to the last tag)
-    if re.match(r'^v\d+\.\d+\.\d+$', git_tag):
+    if re.match(r"^v\d+\.\d+\.\d+$", git_tag):
         # git_tag is a pure version number (no subsequent commits)
         version = git_tag
         release = git_tag
         documentation_build = "readthedocs"
 
     else:
-        version = '\'latest\''
-        release = '\'latest\''
+        version = "'latest'"
+        release = "'latest'"
         documentation_build = "readthedocs_latest"
 
 else:
 
     # For our usual dev build we'll be in the 'documentation' directory but Sphinx seems to set the
     # current working directory to 'source' so we append relative to that
-    sys.path.append('../../')
+    sys.path.append("../../")
 
     # Check if it matches a pure tag number vX.Y.Z, rather than vX.Y.Z-91-g8676988 which is how
     # non-tagged commits are described (ie. relative to the last tag)
-    if re.match(r'^v\d+\.\d+\.\d+$', git_tag):
+    if re.match(r"^v\d+\.\d+\.\d+$", git_tag):
         # git_tag is a pure version number (no subsequent commits)
         version = git_tag
         release = git_tag
     else:
-        version = '\'latest\''
-        release = '\'latest\''
+        version = "'latest'"
+        release = "'latest'"
 
     documentation_build = "development"
 
 
 # If we're doing a comparison then set the version & release to 'compare' so that they are always
 # the same otherwise they can come up as changes when we really don't care if they are different.
-comparison = os.environ.get('BREATHE_COMPARE', None) == 'True'
+comparison = os.environ.get("BREATHE_COMPARE", None) == "True"
 
 if comparison:
-    version = 'compare'
-    release = 'compare'
+    version = "compare"
+    release = "compare"
 
 # Only add spelling extension if it is available. We don't know if it is installed as we don't want
 # to put it in the setup.py file as a dependency as we don't want Breathe to be dependent on it as
@@ -109,14 +109,15 @@
 # this.
 try:
     import sphinxcontrib.spelling
-    extensions.append('sphinxcontrib.spelling')
+
+    extensions.append("sphinxcontrib.spelling")
 except ImportError:
     pass
 
 
 # Configuration for spelling extension
-spelling_word_list_filename='spelling_wordlist.txt'
-spelling_lang='en_US'
+spelling_word_list_filename = "spelling_wordlist.txt"
+spelling_lang = "en_US"
 
 
 # Configuration for mathjax extension
@@ -124,135 +125,134 @@
 # Set path for mathjax js to a https URL as sometimes the Breathe docs are displayed under https
 # and we can't load an http mathjax file from an https view of the docs. So we change to a https
 # mathjax file which we can load from http or https. We break the url over two lines.
-mathjax_path = 'https://c328740.ssl.cf1.rackcdn.com/' \
-               'mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML'
+mathjax_path = (
+    "https://c328740.ssl.cf1.rackcdn.com/" "mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML"
+)
 
 
 # Add any paths that contain templates here, relative to this directory.
-templates_path = ['_templates']
+templates_path = ["_templates"]
 
 # The suffix of source filenames.
-source_suffix = '.rst'
+source_suffix = ".rst"
 
 # The encoding of source files.
-#source_encoding = 'utf-8'
+# source_encoding = 'utf-8'
 
 # The master toctree document.
-master_doc = 'index'
+master_doc = "index"
 
 # General information about the project.
-project = u'Breathe'
-copyright = u'2009-2014, Michael Jones'
+project = u"Breathe"
+copyright = u"2009-2014, Michael Jones"
 
 
 # The language for content autogenerated by Sphinx. Refer to documentation
 # for a list of supported languages.
-#language = None
+# language = None
 
 # There are two options for replacing |today|: either, you set today to some
 # non-false value, then it is used:
-#today = ''
+# today = ''
 # Else, today_fmt is used as the format for a strftime call.
-#today_fmt = '%B %d, %Y'
+# today_fmt = '%B %d, %Y'
 
 # List of documents that shouldn't be included in the build.
-#unused_docs = []
+# unused_docs = []
 
 # List of directories, relative to source directory, that shouldn't be searched
 # for source files.
 exclude_trees = []
 
 # The reST default role (used for this markup: `text`) to use for all documents.
-#default_role = None
+# default_role = None
 
 # If true, '()' will be appended to :func: etc. cross-reference text.
-#add_function_parentheses = True
+# add_function_parentheses = True
 
 # If true, the current module name will be prepended to all description
 # unit titles (such as .. function::).
-#add_module_names = True
+# add_module_names = True
 
 # If true, sectionauthor and moduleauthor directives will be shown in the
 # output. They are ignored by default.
-#show_authors = False
+# show_authors = False
 
 # The name of the Pygments (syntax highlighting) style to use.
-pygments_style = 'sphinx'
+pygments_style = "sphinx"
 
 # Options for breathe extension
 # -----------------------------
 
 breathe_projects = {
-    "class":"../../examples/doxygen/class/xml/",
-    "classtest":"../../examples/specific/class/xml/",
-    "struct":"../../examples/specific/struct/xml/",
-    "interface":"../../examples/specific/interface/xml/",
-    "decl_impl":"../../examples/specific/decl_impl/xml/",
-    "structcmd":"../../examples/doxygen/structcmd/xml/",
-    "tinyxml":"../../examples/tinyxml/tinyxml/xml/",
-    "restypedef":"../../examples/doxygen/restypedef/xml/",
-    "nutshell":"../../examples/specific/nutshell/xml/",
-    "rst":"../../examples/specific/rst/xml/",
-    "c_file":"../../examples/specific/c_file/xml/",
-    "namespace":"../../examples/specific/namespacefile/xml/",
-    "userdefined":"../../examples/specific/userdefined/xml/",
-    "template_function":"../../examples/specific/template_function/xml/",
-    "template_class":"../../examples/specific/template_class/xml/",
+    "class": "../../examples/doxygen/class/xml/",
+    "classtest": "../../examples/specific/class/xml/",
+    "struct": "../../examples/specific/struct/xml/",
+    "interface": "../../examples/specific/interface/xml/",
+    "decl_impl": "../../examples/specific/decl_impl/xml/",
+    "structcmd": "../../examples/doxygen/structcmd/xml/",
+    "tinyxml": "../../examples/tinyxml/tinyxml/xml/",
+    "restypedef": "../../examples/doxygen/restypedef/xml/",
+    "nutshell": "../../examples/specific/nutshell/xml/",
+    "rst": "../../examples/specific/rst/xml/",
+    "c_file": "../../examples/specific/c_file/xml/",
+    "namespace": "../../examples/specific/namespacefile/xml/",
+    "userdefined": "../../examples/specific/userdefined/xml/",
+    "template_function": "../../examples/specific/template_function/xml/",
+    "template_class": "../../examples/specific/template_class/xml/",
     "template_class_non_type": "../../examples/specific/template_class_non_type/xml/",
     "template_specialisation": "../../examples/specific/template_specialisation/xml/",
-    "latexmath":"../../examples/specific/latexmath/xml/",
-    "functionOverload":"../../examples/specific/functionOverload/xml/",
-    "programlisting":"../../examples/specific/programlisting/xml/",
-    "image":"../../examples/specific/image/xml/",
-    "lists":"../../examples/specific/lists/xml/",
-    "tables":"../../examples/specific/tables/xml/",
-    "group":"../../examples/specific/group/xml/",
-    "union":"../../examples/specific/union/xml/",
-    "qtsignalsandslots":"../../examples/specific/qtsignalsandslots/xml/",
-    "array":"../../examples/specific/array/xml/",
-    "c_struct":"../../examples/specific/c_struct/xml/",
-    "c_enum":"../../examples/specific/c_enum/xml/",
-    "c_typedef":"../../examples/specific/c_typedef/xml/",
-    "c_macro":"../../examples/specific/c_macro/xml/",
-    "c_union":"../../examples/specific/c_union/xml/",
-    "define":"../../examples/specific/define/xml/",
-    "multifile":"../../examples/specific/multifilexml/xml/",
-    "cpp_anon":"../../examples/specific/cpp_anon/xml/",
-    "cpp_enum":"../../examples/specific/cpp_enum/xml/",
-    "cpp_union":"../../examples/specific/cpp_union/xml/",
-    "cpp_function":"../../examples/specific/cpp_function/xml/",
-    "cpp_function_lookup":"../../examples/specific/cpp_function_lookup/xml/",
-    "cpp_friendclass":"../../examples/specific/cpp_friendclass/xml/",
-    "cpp_inherited_members":"../../examples/specific/cpp_inherited_members/xml/",
-    "cpp_trailing_return_type":"../../examples/specific/cpp_trailing_return_type/xml/",
-    "cpp_constexpr_hax":"../../examples/specific/cpp_constexpr_hax/xml/",
+    "latexmath": "../../examples/specific/latexmath/xml/",
+    "functionOverload": "../../examples/specific/functionOverload/xml/",
+    "programlisting": "../../examples/specific/programlisting/xml/",
+    "image": "../../examples/specific/image/xml/",
+    "lists": "../../examples/specific/lists/xml/",
+    "tables": "../../examples/specific/tables/xml/",
+    "group": "../../examples/specific/group/xml/",
+    "union": "../../examples/specific/union/xml/",
+    "qtsignalsandslots": "../../examples/specific/qtsignalsandslots/xml/",
+    "array": "../../examples/specific/array/xml/",
+    "c_struct": "../../examples/specific/c_struct/xml/",
+    "c_enum": "../../examples/specific/c_enum/xml/",
+    "c_typedef": "../../examples/specific/c_typedef/xml/",
+    "c_macro": "../../examples/specific/c_macro/xml/",
+    "c_union": "../../examples/specific/c_union/xml/",
+    "define": "../../examples/specific/define/xml/",
+    "multifile": "../../examples/specific/multifilexml/xml/",
+    "cpp_anon": "../../examples/specific/cpp_anon/xml/",
+    "cpp_enum": "../../examples/specific/cpp_enum/xml/",
+    "cpp_union": "../../examples/specific/cpp_union/xml/",
+    "cpp_function": "../../examples/specific/cpp_function/xml/",
+    "cpp_function_lookup": "../../examples/specific/cpp_function_lookup/xml/",
+    "cpp_friendclass": "../../examples/specific/cpp_friendclass/xml/",
+    "cpp_inherited_members": "../../examples/specific/cpp_inherited_members/xml/",
+    "cpp_trailing_return_type": "../../examples/specific/cpp_trailing_return_type/xml/",
+    "cpp_constexpr_hax": "../../examples/specific/cpp_constexpr_hax/xml/",
     "xrefsect": "../../examples/specific/xrefsect/xml/",
     "membergroups": "../../examples/specific/membergroups/xml/",
-    }
+}
 
-breathe_projects_source = {
-    "auto" : ( "../../examples/specific", [ "auto_function.h", "auto_class.h" ] )
-    }
+breathe_projects_source = {"auto": ("../../examples/specific", ["auto_function.h", "auto_class.h"])}
 
 breathe_default_project = "tinyxml"
 
 breathe_domain_by_extension = {
-        "h" : "cpp",
-        "py": "py",
-        }
+    "h": "cpp",
+    "py": "py",
+}
 
 breathe_domain_by_file_pattern = {
-        "class.h" : "cpp",
-        "alias.h" : "c",
-        "array.h" : "c",
-        "c_*.h" : "c",
-        }
+    "class.h": "cpp",
+    "alias.h": "c",
+    "array.h": "c",
+    "c_*.h": "c",
+}
 
 breathe_use_project_refids = True
 
-#breathe_debug_trace_directives = True
-#breathe_debug_trace_doxygen_ids = True
-#breathe_debug_trace_qualification = True
+# breathe_debug_trace_directives = True
+# breathe_debug_trace_doxygen_ids = True
+# breathe_debug_trace_qualification = True
 
 
 # Options for HTML output
@@ -267,96 +267,96 @@
 
 # The name for this set of Sphinx documents.  If None, it defaults to
 # "<project> v<release> documentation".
-#html_title = None
+# html_title = None
 
 # A shorter title for the navigation bar.  Default is the same as html_title.
-#html_short_title = None
+# html_short_title = None
 
 # The name of an image file (relative to this directory) to place at the top
 # of the sidebar.
-#html_logo = None
+# html_logo = None
 
 # The name of an image file (within the static path) to use as favicon of the
 # docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32
 # pixels large.
-#html_favicon = None
+# html_favicon = None
 
 # Add any paths that contain custom static files (such as style sheets) here,
 # relative to this directory. They are copied after the builtin static files,
 # so a file named "default.css" will overwrite the builtin "default.css".
-html_static_path = ['_static']
+html_static_path = ["_static"]
 
 # If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
 # using the given strftime format.
-#html_last_updated_fmt = '%b %d, %Y'
+# html_last_updated_fmt = '%b %d, %Y'
 
 # If true, SmartyPants will be used to convert quotes and dashes to
 # typographically correct entities.
-#html_use_smartypants = True
+# html_use_smartypants = True
 
 # Custom sidebar templates, maps document names to template names.
-#html_sidebars = {}
+# html_sidebars = {}
 
 # Additional templates that should be rendered to pages, maps page names to
 # template names.
-#html_additional_pages = {}
+# html_additional_pages = {}
 
 # If false, no module index is generated.
-#html_use_modindex = True
+# html_use_modindex = True
 
 # If false, no index is generated.
-#html_use_index = True
+# html_use_index = True
 
 # If true, the index is split into individual pages for each letter.
-#html_split_index = False
+# html_split_index = False
 
 # If true, the reST sources are included in the HTML build as _sources/<name>.
-#html_copy_source = True
+# html_copy_source = True
 
 # If true, an OpenSearch description file will be output, and all pages will
 # contain a <link> tag referring to it.  The value of this option must be the
 # base URL from which the finished HTML is served.
-#html_use_opensearch = ''
+# html_use_opensearch = ''
 
 # If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml").
-#html_file_suffix = ''
+# html_file_suffix = ''
 
 # Output file base name for HTML help builder.
-htmlhelp_basename = 'BreatheExampledoc'
+htmlhelp_basename = "BreatheExampledoc"
 
 
 # Options for LaTeX output
 # ------------------------
 
 # The paper size ('letter' or 'a4').
-#latex_paper_size = 'letter'
+# latex_paper_size = 'letter'
 
 # The font size ('10pt', '11pt' or '12pt').
-#latex_font_size = '10pt'
+# latex_font_size = '10pt'
 
 # Grouping the document tree into LaTeX files. List of tuples
 # (source start file, target name, title, author, document class [howto/manual]).
 latex_documents = [
-  ('index', 'BreatheExample.tex', 'BreatheExample Documentation',
-   'Michael Jones', 'manual'),
+    ("index", "BreatheExample.tex", "BreatheExample Documentation", "Michael Jones", "manual"),
 ]
 
 # The name of an image file (relative to this directory) to place at the top of
 # the title page.
-#latex_logo = None
+# latex_logo = None
 
 # For "manual" documents, if this is true, then toplevel headings are parts,
 # not chapters.
-#latex_use_parts = False
+# latex_use_parts = False
 
 # Additional stuff for the LaTeX preamble.
-#latex_preamble = ''
+# latex_preamble = ''
 
 # Documents to append as an appendix to all manuals.
-#latex_appendices = []
+# latex_appendices = []
 
 # If false, no module index is generated.
-#latex_use_modindex = True
+# latex_use_modindex = True
+
 
 def run_doxygen(folder):
     """Run the doxygen make command in the designated folder"""
@@ -372,7 +372,7 @@ def run_doxygen(folder):
 def generate_doxygen_xml(app):
     """Run the doxygen make commands if we're on the ReadTheDocs server"""
 
-    read_the_docs_build = os.environ.get('READTHEDOCS', None) == 'True'
+    read_the_docs_build = os.environ.get("READTHEDOCS", None) == "True"
 
     if read_the_docs_build:
 
@@ -388,14 +388,13 @@ def setup(app):
 
     # Approach borrowed from the Sphinx docs
     app.add_object_type(
-            'confval',
-            'confval',
-            objname='configuration value',
-            indextemplate='pair: %s; configuration value'
-            )
+        "confval",
+        "confval",
+        objname="configuration value",
+        indextemplate="pair: %s; configuration value",
+    )
 
     # Add hook for building doxygen xml when needed
     app.connect("builder-inited", generate_doxygen_xml)
 
-    app.add_config_value('documentation_build', 'development', True)
-
+    app.add_config_value("documentation_build", "development", True)
diff --git a/examples/doxygen/docstring.py b/examples/doxygen/docstring.py
index 07b13e01b..7972e3d24 100644
--- a/examples/doxygen/docstring.py
+++ b/examples/doxygen/docstring.py
@@ -4,6 +4,7 @@
 More details.
 """
 
+
 def func():
     """Documentation for a function.
 
@@ -11,17 +12,17 @@ def func():
     """
     pass
 
+
 class PyClass:
     """Documentation for a class.
 
     More details.
     """
-   
+
     def __init__(self):
         """The constructor."""
-        self._memVar = 0;
-   
+        self._memVar = 0
+
     def PyMethod(self):
         """Documentation for a method."""
         pass
-     
diff --git a/examples/doxygen/pyexample.py b/examples/doxygen/pyexample.py
index a75cc60ec..34c09925d 100644
--- a/examples/doxygen/pyexample.py
+++ b/examples/doxygen/pyexample.py
@@ -9,20 +9,21 @@
 def func():
     pass
 
+
 ## Documentation for a class.
 #
 #  More details.
 class PyClass:
-   
+
     ## The constructor.
     def __init__(self):
-        self._memVar = 0;
-   
+        self._memVar = 0
+
     ## Documentation for a method.
     #  @param self The object pointer.
     def PyMethod(self):
         pass
-     
+
     ## A class variable.
     classVar = 0
 
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 000000000..15f421bb2
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,5 @@
+[tool.black]
+line-length = 100
+extend-exclude = '''
+^/breathe/parser/.*
+'''
diff --git a/requirements/development.txt b/requirements/development.txt
index 5daec922d..69363b23f 100644
--- a/requirements/development.txt
+++ b/requirements/development.txt
@@ -6,3 +6,5 @@ pytest
 
 mypy>=0.900
 types-docutils>=0.17.0
+
+black
diff --git a/setup.cfg b/setup.cfg
index e2af17b50..35e4a561f 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -4,6 +4,7 @@ license_file = LICENSE
 [flake8]
 max-line-length = 100
 exclude=compoundsuper.py,indexsuper.py
+extend-ignore = E203, E231
 
 [bdist_wheel]
 universal = 0
diff --git a/setup.py b/setup.py
index 045bcd5aa..abc02998d 100644
--- a/setup.py
+++ b/setup.py
@@ -3,54 +3,55 @@
     from setuptools import setup, find_packages
 except ImportError:
     import distribute_setup
+
     distribute_setup.use_setuptools()
     from setuptools import setup, find_packages
 
 import sys
 from breathe import __version__
 
-long_desc = '''
+long_desc = """
 Breathe is an extension to reStructuredText and Sphinx to be able to read and
  render `Doxygen <http://www.doxygen.org>`__ xml output.
-'''
+"""
 
-requires = ['Sphinx>=3.0,<5', 'docutils>=0.12']
+requires = ["Sphinx>=3.0,<5", "docutils>=0.12"]
 
 if sys.version_info < (3, 6):
-    print('ERROR: Sphinx requires at least Python 3.6 to run.')
+    print("ERROR: Sphinx requires at least Python 3.6 to run.")
     sys.exit(1)
 
 
 setup(
-    name='breathe',
+    name="breathe",
     version=__version__,
-    url='https://github.com/michaeljones/breathe',
-    download_url='https://github.com/michaeljones/breathe',
-    license='BSD',
-    author='Michael Jones',
-    author_email='m.pricejones@gmail.com',
-    description='Sphinx Doxygen renderer',
+    url="https://github.com/michaeljones/breathe",
+    download_url="https://github.com/michaeljones/breathe",
+    license="BSD",
+    author="Michael Jones",
+    author_email="m.pricejones@gmail.com",
+    description="Sphinx Doxygen renderer",
     long_description=long_desc,
     zip_safe=False,
     classifiers=[
-        'Development Status :: 4 - Beta',
-        'Environment :: Console',
-        'Environment :: Web Environment',
-        'Intended Audience :: Developers',
-        'Intended Audience :: Education',
-        'License :: OSI Approved :: BSD License',
-        'Operating System :: OS Independent',
-        'Programming Language :: Python :: 3',
-        'Topic :: Documentation',
-        'Topic :: Text Processing',
-        'Topic :: Utilities',
+        "Development Status :: 4 - Beta",
+        "Environment :: Console",
+        "Environment :: Web Environment",
+        "Intended Audience :: Developers",
+        "Intended Audience :: Education",
+        "License :: OSI Approved :: BSD License",
+        "Operating System :: OS Independent",
+        "Programming Language :: Python :: 3",
+        "Topic :: Documentation",
+        "Topic :: Text Processing",
+        "Topic :: Utilities",
     ],
-    platforms='any',
+    platforms="any",
     packages=find_packages(),
     include_package_data=True,
     entry_points={
-        'console_scripts': [
-            'breathe-apidoc = breathe.apidoc:main',
+        "console_scripts": [
+            "breathe-apidoc = breathe.apidoc:main",
         ],
     },
     install_requires=requires,


Test patch:
diff --git a/tests/test_renderer.py b/tests/test_renderer.py
index cf6ab7ff6..c349867c8 100644
--- a/tests/test_renderer.py
+++ b/tests/test_renderer.py
@@ -4,43 +4,51 @@
 import sphinx.addnodes
 import sphinx.environment
 from breathe.parser.compound import (
-    compounddefTypeSub, linkedTextTypeSub, memberdefTypeSub, paramTypeSub,
-    refTypeSub, MixedContainer
+    compounddefTypeSub,
+    linkedTextTypeSub,
+    memberdefTypeSub,
+    paramTypeSub,
+    refTypeSub,
+    MixedContainer,
 )
 from breathe.renderer.sphinxrenderer import SphinxRenderer
 from breathe.renderer.filter import OpenFilter
 from docutils import frontend, nodes, parsers, utils
 
 from sphinx.testing.fixtures import (
-    test_params, app_params, make_app, shared_result,
-    sphinx_test_tempdir, rootdir
+    test_params,
+    app_params,
+    make_app,
+    shared_result,
+    sphinx_test_tempdir,
+    rootdir,
 )
 from sphinx.testing.path import path
 
-sphinx.locale.init([], '')
+sphinx.locale.init([], "")
 
 
-@pytest.fixture(scope='function')
+@pytest.fixture(scope="function")
 def app(test_params, app_params, make_app, shared_result):
     """
     Based on sphinx.testing.fixtures.app
     """
     args, kwargs = app_params
-    assert 'srcdir' in kwargs
-    kwargs['srcdir'].makedirs(exist_ok=True)
-    (kwargs['srcdir'] / 'conf.py').write_text('')
+    assert "srcdir" in kwargs
+    kwargs["srcdir"].makedirs(exist_ok=True)
+    (kwargs["srcdir"] / "conf.py").write_text("")
     app_ = make_app(*args, **kwargs)
     yield app_
 
-    print('# testroot:', kwargs.get('testroot', 'root'))
-    print('# builder:', app_.builder.name)
-    print('# srcdir:', app_.srcdir)
-    print('# outdir:', app_.outdir)
-    print('# status:', '\n' + app_._status.getvalue())
-    print('# warning:', '\n' + app_._warning.getvalue())
+    print("# testroot:", kwargs.get("testroot", "root"))
+    print("# builder:", app_.builder.name)
+    print("# srcdir:", app_.srcdir)
+    print("# outdir:", app_.outdir)
+    print("# status:", "\n" + app_._status.getvalue())
+    print("# warning:", "\n" + app_._warning.getvalue())
 
-    if test_params['shared_result']:
-        shared_result.store(test_params['shared_result'], app_)
+    if test_params["shared_result"]:
+        shared_result.store(test_params["shared_result"], app_)
 
 
 class WrappedDoxygenNode:
@@ -48,17 +56,19 @@ class WrappedDoxygenNode:
     A base class for test wrappers of Doxygen nodes. It allows setting all attributes via keyword arguments
     in the constructor.
     """
+
     def __init__(self, cls, *args, **kwargs):
         if cls:
             cls.__init__(self, args)
         for name, value in kwargs.items():
             if not hasattr(self, name):
-                raise AttributeError('invalid attribute ' + name)
+                raise AttributeError("invalid attribute " + name)
             setattr(self, name, value)
 
 
 class WrappedMixedContainer(MixedContainer, WrappedDoxygenNode):
     """A test wrapper of Doxygen mixed container."""
+
     def __init__(self, **kwargs):
         MixedContainer.__init__(self, None, None, None, None)
         WrappedDoxygenNode.__init__(self, None, **kwargs)
@@ -66,30 +76,35 @@ def __init__(self, **kwargs):
 
 class WrappedLinkedText(linkedTextTypeSub, WrappedDoxygenNode):
     """A test wrapper of Doxygen linked text."""
+
     def __init__(self, **kwargs):
         WrappedDoxygenNode.__init__(self, linkedTextTypeSub, **kwargs)
 
 
 class WrappedMemberDef(memberdefTypeSub, WrappedDoxygenNode):
     """A test wrapper of Doxygen class/file/namespace member symbol such as a function declaration."""
+
     def __init__(self, **kwargs):
         WrappedDoxygenNode.__init__(self, memberdefTypeSub, **kwargs)
 
 
 class WrappedParam(paramTypeSub, WrappedDoxygenNode):
     """A test wrapper of Doxygen parameter."""
+
     def __init__(self, **kwargs):
         WrappedDoxygenNode.__init__(self, paramTypeSub, **kwargs)
 
 
 class WrappedRef(refTypeSub, WrappedDoxygenNode):
     """A test wrapper of Doxygen ref."""
+
     def __init__(self, node_name, **kwargs):
         WrappedDoxygenNode.__init__(self, refTypeSub, node_name, **kwargs)
 
 
 class WrappedCompoundDef(compounddefTypeSub, WrappedDoxygenNode):
     """A test wrapper of Doxygen compound definition."""
+
     def __init__(self, **kwargs):
         WrappedDoxygenNode.__init__(self, compounddefTypeSub, **kwargs)
 
@@ -98,11 +113,10 @@ class MockState:
     def __init__(self, app):
         env = sphinx.environment.BuildEnvironment(app)
         env.setup(app)
-        env.temp_data['docname'] = 'mock-doc'
-        settings = frontend.OptionParser(
-            components=(parsers.rst.Parser,)).get_default_values()
+        env.temp_data["docname"] = "mock-doc"
+        settings = frontend.OptionParser(components=(parsers.rst.Parser,)).get_default_values()
         settings.env = env
-        self.document = utils.new_document('', settings)
+        self.document = utils.new_document("", settings)
 
     def nested_parse(self, content, content_offset, contentnode):
         pass
@@ -126,7 +140,7 @@ def __init__(self):
     def get_source_and_line(self, lineno: int):
         if lineno is None:
             lineno = 42
-        return 'mock-doc', lineno
+        return "mock-doc", lineno
 
 
 class MockMaskFactory:
@@ -142,14 +156,16 @@ def __init__(self, app, node_stack, domain=None, options=[]):
         self.domain = domain
         self.node_stack = node_stack
         self.directive_args = [
-            None,     # name
-            None,     # arguments
+            None,  # name
+            None,  # arguments
             options,  # options
-            None,     # content
-            None,     # lineno
-            None,     # content_offset
-            None,     # block_text
-            MockState(app), MockStateMachine()]
+            None,  # content
+            None,  # lineno
+            None,  # content_offset
+            None,  # block_text
+            MockState(app),
+            MockStateMachine(),
+        ]
         self.child = None
         self.mask_factory = MockMaskFactory()
 
@@ -175,6 +191,7 @@ class MockCompoundParser:
     A compound parser reads a doxygen XML file from disk; this mock implements
     a mapping of what would be the file name on disk to data using a dict.
     """
+
     def __init__(self, compound_dict):
         self.compound_dict = compound_dict
 
@@ -189,6 +206,7 @@ def parse(self, compoundname):
 
 class NodeFinder(nodes.NodeVisitor):
     """Find node with specified class name."""
+
     def __init__(self, name, document):
         nodes.NodeVisitor.__init__(self, document)
         self.name = name
@@ -214,21 +232,21 @@ def find_node(nodes, name):
     """
     found_nodes = find_nodes(nodes, name)
     if len(found_nodes) != 1:
-        raise Exception('the number of nodes {0} is {1}'.format(name, len(found_nodes)))
+        raise Exception("the number of nodes {0} is {1}".format(name, len(found_nodes)))
     return found_nodes[0]
 
 
 def test_find_nodes():
     section = nodes.section()
-    foo = nodes.Text('foo')
+    foo = nodes.Text("foo")
     desc = nodes.description()
-    bar = nodes.Text('bar')
+    bar = nodes.Text("bar")
     section.children = [foo, desc, bar]
-    assert(find_nodes(section, 'description') == [desc])
-    assert(find_nodes([section, desc], 'description') == [desc, desc])
-    assert(find_nodes([], 'description') == [])
-    assert(find_nodes(section, 'unknown') == [])
-    assert(find_nodes(section, 'Text') == [foo, bar])
+    assert find_nodes(section, "description") == [desc]
+    assert find_nodes([section, desc], "description") == [desc, desc]
+    assert find_nodes([], "description") == []
+    assert find_nodes(section, "unknown") == []
+    assert find_nodes(section, "Text") == [foo, bar]
 
 
 def check_exception(func, message):
@@ -244,23 +262,22 @@ def check_exception(func, message):
 
 def test_find_node():
     section = nodes.section()
-    foo = nodes.Text('foo')
+    foo = nodes.Text("foo")
     desc = nodes.description()
-    bar = nodes.Text('bar')
+    bar = nodes.Text("bar")
     section.children = [foo, desc, bar]
-    assert(find_node(section, 'description') == desc)
-    check_exception(lambda: find_node([section, desc], 'description'),
-                    'the number of nodes description is 2')
-    check_exception(lambda: find_node([], 'description'),
-                    'the number of nodes description is 0')
-    check_exception(lambda: find_node([section], 'unknown'),
-                    'the number of nodes unknown is 0')
-    check_exception(lambda: find_node([section], 'Text'),
-                    'the number of nodes Text is 2')
-
-
-def render(app, member_def, domain=None, show_define_initializer=False,
-           compound_parser=None, options=[]):
+    assert find_node(section, "description") == desc
+    check_exception(
+        lambda: find_node([section, desc], "description"), "the number of nodes description is 2"
+    )
+    check_exception(lambda: find_node([], "description"), "the number of nodes description is 0")
+    check_exception(lambda: find_node([section], "unknown"), "the number of nodes unknown is 0")
+    check_exception(lambda: find_node([section], "Text"), "the number of nodes Text is 2")
+
+
+def render(
+    app, member_def, domain=None, show_define_initializer=False, compound_parser=None, options=[]
+):
     """Render Doxygen *member_def* with *renderer_class*."""
 
     app.config.breathe_separate_member_pages = False
@@ -270,60 +287,79 @@ def render(app, member_def, domain=None, show_define_initializer=False,
     app.config.breathe_debug_trace_directives = False
     app.config.breathe_debug_trace_doxygen_ids = False
     app.config.breathe_debug_trace_qualification = False
-    renderer = SphinxRenderer(app,
-                              None,  # project_info
-                              [],    # node_stack
-                              None,  # state
-                              None,  # document
-                              MockTargetHandler(),
-                              compound_parser,
-                              OpenFilter())
+    renderer = SphinxRenderer(
+        app,
+        None,  # project_info
+        [],  # node_stack
+        None,  # state
+        None,  # document
+        MockTargetHandler(),
+        compound_parser,
+        OpenFilter(),
+    )
     renderer.context = MockContext(app, [member_def], domain, options)
     return renderer.render(member_def)
 
 
 def test_render_func(app):
-    member_def = WrappedMemberDef(kind='function', definition='void foo', type_='void', name='foo', argsstring='(int)',
-                                  virt='non-virtual',
-                                  param=[WrappedParam(type_=WrappedLinkedText(content_=[WrappedMixedContainer(value=u'int')]))])
-    signature = find_node(render(app, member_def), 'desc_signature')
-    assert signature.astext().startswith('void')
+    member_def = WrappedMemberDef(
+        kind="function",
+        definition="void foo",
+        type_="void",
+        name="foo",
+        argsstring="(int)",
+        virt="non-virtual",
+        param=[
+            WrappedParam(type_=WrappedLinkedText(content_=[WrappedMixedContainer(value=u"int")]))
+        ],
+    )
+    signature = find_node(render(app, member_def), "desc_signature")
+    assert signature.astext().startswith("void")
     if sphinx.version_info[0] < 4:
-        assert find_node(signature, 'desc_name')[0] == 'foo'
+        assert find_node(signature, "desc_name")[0] == "foo"
     else:
-        n = find_node(signature, 'desc_name')[0]
+        n = find_node(signature, "desc_name")[0]
         assert isinstance(n, sphinx.addnodes.desc_sig_name)
         assert len(n) == 1
-        assert n[0] == 'foo'
-    params = find_node(signature, 'desc_parameterlist')
+        assert n[0] == "foo"
+    params = find_node(signature, "desc_parameterlist")
     assert len(params) == 1
     param = params[0]
     if sphinx.version_info[0] < 4:
-        assert param[0] == 'int'
+        assert param[0] == "int"
     else:
         assert isinstance(param[0], sphinx.addnodes.desc_sig_keyword_type)
-        assert param[0][0] == 'int'
+        assert param[0][0] == "int"
 
 
 def test_render_typedef(app):
-    member_def = WrappedMemberDef(kind='typedef', definition='typedef int foo', type_='int', name='foo')
-    signature = find_node(render(app, member_def), 'desc_signature')
-    assert signature.astext() == 'typedef int foo'
+    member_def = WrappedMemberDef(
+        kind="typedef", definition="typedef int foo", type_="int", name="foo"
+    )
+    signature = find_node(render(app, member_def), "desc_signature")
+    assert signature.astext() == "typedef int foo"
 
 
 def test_render_c_typedef(app):
-    member_def = WrappedMemberDef(kind='typedef', definition='typedef unsigned int bar', type_='unsigned int', name='bar')
-    signature = find_node(render(app, member_def, domain='c'), 'desc_signature')
-    assert signature.astext() == 'typedef unsigned int bar'
+    member_def = WrappedMemberDef(
+        kind="typedef", definition="typedef unsigned int bar", type_="unsigned int", name="bar"
+    )
+    signature = find_node(render(app, member_def, domain="c"), "desc_signature")
+    assert signature.astext() == "typedef unsigned int bar"
 
 
 def test_render_c_function_typedef(app):
-    member_def = WrappedMemberDef(kind='typedef', definition='typedef void* (*voidFuncPtr)(float, int)',
-                                  type_='void* (*', name='voidFuncPtr', argsstring=')(float, int)')
-    signature = find_node(render(app, member_def, domain='c'), 'desc_signature')
-    assert signature.astext().startswith('typedef void *')
+    member_def = WrappedMemberDef(
+        kind="typedef",
+        definition="typedef void* (*voidFuncPtr)(float, int)",
+        type_="void* (*",
+        name="voidFuncPtr",
+        argsstring=")(float, int)",
+    )
+    signature = find_node(render(app, member_def, domain="c"), "desc_signature")
+    assert signature.astext().startswith("typedef void *")
     if sphinx.version_info[0] < 4:
-        params = find_node(signature, 'desc_parameterlist')
+        params = find_node(signature, "desc_parameterlist")
         assert len(params) == 2
         assert params[0].astext() == "float"
         assert params[1].astext() == "int"
@@ -334,92 +370,149 @@ def test_render_c_function_typedef(app):
 
 
 def test_render_using_alias(app):
-    member_def = WrappedMemberDef(kind='typedef', definition='using foo = int', type_='int', name='foo')
-    signature = find_node(render(app, member_def), 'desc_signature')
-    assert signature.astext() == 'using foo = int'
+    member_def = WrappedMemberDef(
+        kind="typedef", definition="using foo = int", type_="int", name="foo"
+    )
+    signature = find_node(render(app, member_def), "desc_signature")
+    assert signature.astext() == "using foo = int"
 
 
 def test_render_const_func(app):
-    member_def = WrappedMemberDef(kind='function', definition='void f', type_='void', name='f', argsstring='() const',
-                               virt='non-virtual', const='yes')
-    signature = find_node(render(app, member_def), 'desc_signature')
-    assert '_CPPv2NK1fEv' in signature['ids']
+    member_def = WrappedMemberDef(
+        kind="function",
+        definition="void f",
+        type_="void",
+        name="f",
+        argsstring="() const",
+        virt="non-virtual",
+        const="yes",
+    )
+    signature = find_node(render(app, member_def), "desc_signature")
+    assert "_CPPv2NK1fEv" in signature["ids"]
 
 
 def test_render_lvalue_func(app):
-    member_def = WrappedMemberDef(kind='function', definition='void f', type_='void', name='f', argsstring='() &',
-                               virt='non-virtual', refqual='lvalue')
-    signature = find_node(render(app, member_def), 'desc_signature')
-    assert signature.astext().endswith('&')
+    member_def = WrappedMemberDef(
+        kind="function",
+        definition="void f",
+        type_="void",
+        name="f",
+        argsstring="() &",
+        virt="non-virtual",
+        refqual="lvalue",
+    )
+    signature = find_node(render(app, member_def), "desc_signature")
+    assert signature.astext().endswith("&")
 
 
 def test_render_rvalue_func(app):
-    member_def = WrappedMemberDef(kind='function', definition='void f', type_='void', name='f', argsstring='() &&',
-                               virt='non-virtual', refqual='rvalue')
-    signature = find_node(render(app, member_def), 'desc_signature')
-    assert signature.astext().endswith('&&')
+    member_def = WrappedMemberDef(
+        kind="function",
+        definition="void f",
+        type_="void",
+        name="f",
+        argsstring="() &&",
+        virt="non-virtual",
+        refqual="rvalue",
+    )
+    signature = find_node(render(app, member_def), "desc_signature")
+    assert signature.astext().endswith("&&")
 
 
 def test_render_const_lvalue_func(app):
-    member_def = WrappedMemberDef(kind='function', definition='void f', type_='void', name='f',argsstring='() const &',
-                               virt='non-virtual', const='yes', refqual='lvalue')
-    signature = find_node(render(app, member_def), 'desc_signature')
-    assert signature.astext().endswith('const &')
+    member_def = WrappedMemberDef(
+        kind="function",
+        definition="void f",
+        type_="void",
+        name="f",
+        argsstring="() const &",
+        virt="non-virtual",
+        const="yes",
+        refqual="lvalue",
+    )
+    signature = find_node(render(app, member_def), "desc_signature")
+    assert signature.astext().endswith("const &")
 
 
 def test_render_const_rvalue_func(app):
-    member_def = WrappedMemberDef(kind='function', definition='void f', type_='void', name='f', argsstring='() const &&',
-                               virt='non-virtual', const='yes', refqual='rvalue')
-    signature = find_node(render(app, member_def), 'desc_signature')
-    assert signature.astext().endswith('const &&')
+    member_def = WrappedMemberDef(
+        kind="function",
+        definition="void f",
+        type_="void",
+        name="f",
+        argsstring="() const &&",
+        virt="non-virtual",
+        const="yes",
+        refqual="rvalue",
+    )
+    signature = find_node(render(app, member_def), "desc_signature")
+    assert signature.astext().endswith("const &&")
 
 
 def test_render_variable_initializer(app):
-    member_def = WrappedMemberDef(kind='variable', definition='const int EOF', type_='const int', name='EOF',
-                                  initializer=WrappedMixedContainer(value=u'= -1'))
-    signature = find_node(render(app, member_def), 'desc_signature')
-    assert signature.astext() == 'const int EOF = -1'
+    member_def = WrappedMemberDef(
+        kind="variable",
+        definition="const int EOF",
+        type_="const int",
+        name="EOF",
+        initializer=WrappedMixedContainer(value=u"= -1"),
+    )
+    signature = find_node(render(app, member_def), "desc_signature")
+    assert signature.astext() == "const int EOF = -1"
 
 
 def test_render_define_initializer(app):
-    member_def = WrappedMemberDef(kind='define', name='MAX_LENGTH',
-                               initializer=WrappedLinkedText(content_=[WrappedMixedContainer(value=u'100')]))
-    signature_w_initializer = find_node(render(app, member_def, show_define_initializer=True), 'desc_signature')
-    assert signature_w_initializer.astext() == 'MAX_LENGTH 100'
-
-    member_def_no_show = WrappedMemberDef(kind='define', name='MAX_LENGTH_NO_INITIALIZER',
-                               initializer=WrappedLinkedText(content_=[WrappedMixedContainer(value=u'100')]))
-
-    signature_wo_initializer = find_node(render(app, member_def_no_show, show_define_initializer=False), 'desc_signature')
-    assert signature_wo_initializer.astext() == 'MAX_LENGTH_NO_INITIALIZER'
+    member_def = WrappedMemberDef(
+        kind="define",
+        name="MAX_LENGTH",
+        initializer=WrappedLinkedText(content_=[WrappedMixedContainer(value=u"100")]),
+    )
+    signature_w_initializer = find_node(
+        render(app, member_def, show_define_initializer=True), "desc_signature"
+    )
+    assert signature_w_initializer.astext() == "MAX_LENGTH 100"
+
+    member_def_no_show = WrappedMemberDef(
+        kind="define",
+        name="MAX_LENGTH_NO_INITIALIZER",
+        initializer=WrappedLinkedText(content_=[WrappedMixedContainer(value=u"100")]),
+    )
+
+    signature_wo_initializer = find_node(
+        render(app, member_def_no_show, show_define_initializer=False), "desc_signature"
+    )
+    assert signature_wo_initializer.astext() == "MAX_LENGTH_NO_INITIALIZER"
 
 
 def test_render_define_no_initializer(app):
     sphinx.addnodes.setup(app)
-    member_def = WrappedMemberDef(kind='define', name='USE_MILK')
-    signature = find_node(render(app, member_def), 'desc_signature')
-    assert signature.astext() == 'USE_MILK'
+    member_def = WrappedMemberDef(kind="define", name="USE_MILK")
+    signature = find_node(render(app, member_def), "desc_signature")
+    assert signature.astext() == "USE_MILK"
 
 
 def test_render_innergroup(app):
-    refid = 'group__innergroup'
-    mock_compound_parser = MockCompoundParser({
-        refid: WrappedCompoundDef(kind='group',
-                                  compoundname='InnerGroup',
-                                  briefdescription='InnerGroup')
-    })
-    ref = WrappedRef('InnerGroup', refid=refid)
-    compound_def = WrappedCompoundDef(kind='group',
-                                      compoundname='OuterGroup',
-                                      briefdescription='OuterGroup',
-                                      innergroup=[ref])
-    assert all(el.astext() != 'InnerGroup'
-               for el in render(app, compound_def,
-                                compound_parser=mock_compound_parser))
-    assert any(el.astext() == 'InnerGroup'
-               for el in render(app, compound_def,
-                                compound_parser=mock_compound_parser,
-                                options=['inner']))
+    refid = "group__innergroup"
+    mock_compound_parser = MockCompoundParser(
+        {
+            refid: WrappedCompoundDef(
+                kind="group", compoundname="InnerGroup", briefdescription="InnerGroup"
+            )
+        }
+    )
+    ref = WrappedRef("InnerGroup", refid=refid)
+    compound_def = WrappedCompoundDef(
+        kind="group", compoundname="OuterGroup", briefdescription="OuterGroup", innergroup=[ref]
+    )
+    assert all(
+        el.astext() != "InnerGroup"
+        for el in render(app, compound_def, compound_parser=mock_compound_parser)
+    )
+    assert any(
+        el.astext() == "InnerGroup"
+        for el in render(app, compound_def, compound_parser=mock_compound_parser, options=["inner"])
+    )
+
 
 def get_directive(app):
     from breathe.directives.function import DoxygenFunctionDirective
@@ -427,23 +520,29 @@ def get_directive(app):
     from breathe.parser import DoxygenParserFactory
     from breathe.finder.factory import FinderFactory
     from docutils.statemachine import StringList
+
     app.config.breathe_separate_member_pages = False
-    app.config.breathe_default_project = 'test_project'
+    app.config.breathe_default_project = "test_project"
     app.config.breathe_domain_by_extension = {}
     app.config.breathe_domain_by_file_pattern = {}
     app.config.breathe_use_project_refids = False
     project_info_factory = ProjectInfoFactory(app)
     parser_factory = DoxygenParserFactory(app)
     finder_factory = FinderFactory(app, parser_factory)
-    cls_args = ('doxygenclass', ['at::Tensor'],
-                {'members': '', 'protected-members': None, 'undoc-members': None},
-                StringList([], items=[]),
-                20, 24,
-                ('.. doxygenclass:: at::Tensor\n   :members:\n'
-                        '   :protected-members:\n   :undoc-members:'),
-                MockState(app),
-                MockStateMachine(),
-               )
+    cls_args = (
+        "doxygenclass",
+        ["at::Tensor"],
+        {"members": "", "protected-members": None, "undoc-members": None},
+        StringList([], items=[]),
+        20,
+        24,
+        (
+            ".. doxygenclass:: at::Tensor\n   :members:\n"
+            "   :protected-members:\n   :undoc-members:"
+        ),
+        MockState(app),
+        MockStateMachine(),
+    )
     return DoxygenFunctionDirective(finder_factory, project_info_factory, parser_factory, *cls_args)
 
 
@@ -452,23 +551,23 @@ def get_matches(datafile):
     from xml.dom import minidom
 
     argsstrings = []
-    with open(os.path.join(os.path.dirname(__file__), 'data', datafile)) as fid:
+    with open(os.path.join(os.path.dirname(__file__), "data", datafile)) as fid:
         xml = fid.read()
     doc = minidom.parseString(xml)
 
     sectiondef = sectiondefType.factory()
     for child in doc.documentElement.childNodes:
-        sectiondef.buildChildren(child, 'memberdef')
-        if getattr(child, 'tagName', None) == 'memberdef':
+        sectiondef.buildChildren(child, "memberdef")
+        if getattr(child, "tagName", None) == "memberdef":
             # Get the argsstring function declaration
-            argsstrings.append(child.getElementsByTagName('argsstring')[0].childNodes[0].data)
+            argsstrings.append(child.getElementsByTagName("argsstring")[0].childNodes[0].data)
     matches = [[m, sectiondef] for m in sectiondef.memberdef]
     return argsstrings, matches
 
 
 def test_resolve_overrides(app):
     # Test that multiple function overrides works
-    argsstrings, matches = get_matches('arange.xml')
+    argsstrings, matches = get_matches("arange.xml")
     cls = get_directive(app)
 
     # Verify that the exact arguments returns one override
@@ -476,11 +575,11 @@ def test_resolve_overrides(app):
         ast_param = cls._parse_args(args)
         ret = cls._resolve_function(matches, ast_param, None)
 
+
 def test_ellipsis(app):
-    argsstrings, matches = get_matches('ellipsis.xml')
+    argsstrings, matches = get_matches("ellipsis.xml")
     cls = get_directive(app)
 
     # Verify that parsing an ellipsis works
     ast_param = cls._parse_args(argsstrings[0])
     ret = cls._resolve_function(matches, ast_param, None)
-
diff --git a/tests/test_utils.py b/tests/test_utils.py
index 7fba8e1ce..3096d45f5 100644
--- a/tests/test_utils.py
+++ b/tests/test_utils.py
@@ -1,4 +1,3 @@
-
 from unittest import TestCase
 from xml.dom import minidom  # type: ignore
 
@@ -6,8 +5,8 @@
 from breathe.parser.compoundsuper import memberdefType
 from breathe import path_handler
 
-class TestUtils(TestCase):
 
+class TestUtils(TestCase):
     def test_param_decl(self):
 
         # From xml from: examples/specific/parameters.h
@@ -50,41 +49,38 @@ def test_param_decl(self):
 
         memberdef = memberdefType.factory()
         for child in doc.documentElement.childNodes:
-            memberdef.buildChildren(child, 'param')
+            memberdef.buildChildren(child, "param")
 
-        self.assertEqual(get_param_decl(memberdef.param[0]), 'int a')
-        self.assertEqual(get_param_decl(memberdef.param[1]), 'float b')
-        self.assertEqual(get_param_decl(memberdef.param[2]), 'int * c')
-        self.assertEqual(get_param_decl(memberdef.param[3]), 'int(**p)[3]')
-        self.assertEqual(get_param_decl(memberdef.param[4]), 'MyClass a')
-        self.assertEqual(get_param_decl(memberdef.param[5]), 'MyClass  * b')
-        self.assertEqual(get_param_decl(memberdef.param[6]), 'int(&r)[3]')
+        self.assertEqual(get_param_decl(memberdef.param[0]), "int a")
+        self.assertEqual(get_param_decl(memberdef.param[1]), "float b")
+        self.assertEqual(get_param_decl(memberdef.param[2]), "int * c")
+        self.assertEqual(get_param_decl(memberdef.param[3]), "int(**p)[3]")
+        self.assertEqual(get_param_decl(memberdef.param[4]), "MyClass a")
+        self.assertEqual(get_param_decl(memberdef.param[5]), "MyClass  * b")
+        self.assertEqual(get_param_decl(memberdef.param[6]), "int(&r)[3]")
 
     def test_definition_without_template_args(self):
-
-        def get_definition(definition, name, bitfield=''):
+        def get_definition(definition, name, bitfield=""):
             class MockDataObject:
                 def __init__(self, definition, name, bitfield):
                     self.definition = definition
                     self.name = name
                     self.bitfield = bitfield
 
-            return get_definition_without_template_args(
-                MockDataObject(definition, name, bitfield)
-            )
+            return get_definition_without_template_args(MockDataObject(definition, name, bitfield))
 
-        self.assertEqual('void A::foo', get_definition('void A<T>::foo', 'foo'))
+        self.assertEqual("void A::foo", get_definition("void A<T>::foo", "foo"))
         # Template arguments in the return type should be preserved:
-        self.assertEqual('Result<T> A::f', get_definition('Result<T> A::f', 'f'))
+        self.assertEqual("Result<T> A::f", get_definition("Result<T> A::f", "f"))
         # Nested template arguments:
-        self.assertEqual('Result<T> A::f', get_definition('Result<T> A< B<C> >::f', 'f'))
+        self.assertEqual("Result<T> A::f", get_definition("Result<T> A< B<C> >::f", "f"))
 
         # Bit fields
-        self.assertEqual('int f : 3', get_definition('int f', 'f', '3'))
+        self.assertEqual("int f : 3", get_definition("int f", "f", "3"))
 
 
 class TestPathHandler(TestCase):
     def test_path_handler(self):
-        self.assertEqual(path_handler.includes_directory('directory/file.h'), True)
-        self.assertEqual(path_handler.includes_directory('directory\\file.h'), True)
-        self.assertEqual(path_handler.includes_directory('file.h'), False)
+        self.assertEqual(path_handler.includes_directory("directory/file.h"), True)
+        self.assertEqual(path_handler.includes_directory("directory\\file.h"), True)
+        self.assertEqual(path_handler.includes_directory("file.h"), False)
diff --git a/tests/warnings/source/conf.py b/tests/warnings/source/conf.py
index a282b7579..8c356c372 100644
--- a/tests/warnings/source/conf.py
+++ b/tests/warnings/source/conf.py
@@ -20,62 +20,62 @@
 # If extensions (or modules to document with autodoc) are in another directory,
 # add these directories to sys.path here. If the directory is relative to the
 # documentation root, use os.path.abspath to make it absolute, like shown here.
-#sys.path.insert(0, os.path.abspath('.'))
+# sys.path.insert(0, os.path.abspath('.'))
 
 # -- General configuration ------------------------------------------------
 
 # If your documentation needs a minimal Sphinx version, state it here.
-#needs_sphinx = '1.0'
+# needs_sphinx = '1.0'
 
-sys.path.append('../../')
+sys.path.append("../../")
 
 # Add any Sphinx extension module names here, as strings. They can be
 # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
 # ones.
-extensions = ['breathe']
+extensions = ["breathe"]
 
 # Breathe configuration parameters
 breathe_projects = {
-    "class" : "../../../examples/doxygen/class/xml/",
-    "function" : "../../../examples/specific/functionOverload/xml/",
-    "group" : "../../../examples/specific/group/xml/",
-    "invalidproject" : "invalid/path/",
-    }
+    "class": "../../../examples/doxygen/class/xml/",
+    "function": "../../../examples/specific/functionOverload/xml/",
+    "group": "../../../examples/specific/group/xml/",
+    "invalidproject": "invalid/path/",
+}
 
 # Add any paths that contain templates here, relative to this directory.
-templates_path = ['_templates']
+templates_path = ["_templates"]
 
 # The suffix of source filenames.
-source_suffix = '.rst'
+source_suffix = ".rst"
 
 # The encoding of source files.
-#source_encoding = 'utf-8-sig'
+# source_encoding = 'utf-8-sig'
 
 # The master toctree document.
-master_doc = 'index'
+master_doc = "index"
 
 # General information about the project.
-project = u'Test Breathe Warnings'
-copyright = u'2014, Michael Jones'
+project = u"Test Breathe Warnings"
+copyright = u"2014, Michael Jones"
 
 # The version info for the project you're documenting, acts as replacement for
 # |version| and |release|, also used in various other places throughout the
 # built documents.
 #
 # The short X.Y version.
-version = '1.0'
+version = "1.0"
 # The full version, including alpha/beta/rc tags.
-release = '1.0'
+release = "1.0"
 
 # The language for content autogenerated by Sphinx. Refer to documentation
 # for a list of supported languages.
-#language = None
+# language = None
 
 # There are two options for replacing |today|: either, you set today to some
 # non-false value, then it is used:
-#today = ''
+# today = ''
 # Else, today_fmt is used as the format for a strftime call.
-#today_fmt = '%B %d, %Y'
+# today_fmt = '%B %d, %Y'
 
 # List of patterns, relative to source directory, that match files and
 # directories to ignore when looking for source files.
@@ -83,154 +83,157 @@
 
 # The reST default role (used for this markup: `text`) to use for all
 # documents.
-#default_role = None
+# default_role = None
 
 # If true, '()' will be appended to :func: etc. cross-reference text.
-#add_function_parentheses = True
+# add_function_parentheses = True
 
 # If true, the current module name will be prepended to all description
 # unit titles (such as .. function::).
-#add_module_names = True
+# add_module_names = True
 
 # If true, sectionauthor and moduleauthor directives will be shown in the
 # output. They are ignored by default.
-#show_authors = False
+# show_authors = False
 
 # The name of the Pygments (syntax highlighting) style to use.
-pygments_style = 'sphinx'
+pygments_style = "sphinx"
 
 # A list of ignored prefixes for module index sorting.
-#modindex_common_prefix = []
+# modindex_common_prefix = []
 
 # If true, keep warnings as "system message" paragraphs in the built documents.
-#keep_warnings = False
+# keep_warnings = False
 
 
 # -- Options for HTML output ----------------------------------------------
 
 # The theme to use for HTML and HTML Help pages.  See the documentation for
 # a list of builtin themes.
-html_theme = 'default'
+html_theme = "default"
 
 # Theme options are theme-specific and customize the look and feel of a theme
 # further.  For a list of options available for each theme, see the
 # documentation.
-#html_theme_options = {}
+# html_theme_options = {}
 
 # Add any paths that contain custom themes here, relative to this directory.
-#html_theme_path = []
+# html_theme_path = []
 
 # The name for this set of Sphinx documents.  If None, it defaults to
 # "<project> v<release> documentation".
-#html_title = None
+# html_title = None
 
 # A shorter title for the navigation bar.  Default is the same as html_title.
-#html_short_title = None
+# html_short_title = None
 
 # The name of an image file (relative to this directory) to place at the top
 # of the sidebar.
-#html_logo = None
+# html_logo = None
 
 # The name of an image file (within the static path) to use as favicon of the
 # docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32
 # pixels large.
-#html_favicon = None
+# html_favicon = None
 
 # Add any paths that contain custom static files (such as style sheets) here,
 # relative to this directory. They are copied after the builtin static files,
 # so a file named "default.css" will overwrite the builtin "default.css".
-html_static_path = ['_static']
+html_static_path = ["_static"]
 
 # Add any extra paths that contain custom files (such as robots.txt or
 # .htaccess) here, relative to this directory. These files are copied
 # directly to the root of the documentation.
-#html_extra_path = []
+# html_extra_path = []
 
 # If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
 # using the given strftime format.
-#html_last_updated_fmt = '%b %d, %Y'
+# html_last_updated_fmt = '%b %d, %Y'
 
 # If true, SmartyPants will be used to convert quotes and dashes to
 # typographically correct entities.
-#html_use_smartypants = True
+# html_use_smartypants = True
 
 # Custom sidebar templates, maps document names to template names.
-#html_sidebars = {}
+# html_sidebars = {}
 
 # Additional templates that should be rendered to pages, maps page names to
 # template names.
-#html_additional_pages = {}
+# html_additional_pages = {}
 
 # If false, no module index is generated.
-#html_domain_indices = True
+# html_domain_indices = True
 
 # If false, no index is generated.
-#html_use_index = True
+# html_use_index = True
 
 # If true, the index is split into individual pages for each letter.
-#html_split_index = False
+# html_split_index = False
 
 # If true, links to the reST sources are added to the pages.
-#html_show_sourcelink = True
+# html_show_sourcelink = True
 
 # If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
-#html_show_sphinx = True
+# html_show_sphinx = True
 
 # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
-#html_show_copyright = True
+# html_show_copyright = True
 
 # If true, an OpenSearch description file will be output, and all pages will
 # contain a <link> tag referring to it.  The value of this option must be the
 # base URL from which the finished HTML is served.
-#html_use_opensearch = ''
+# html_use_opensearch = ''
 
 # This is the file name suffix for HTML files (e.g. ".xhtml").
-#html_file_suffix = None
+# html_file_suffix = None
 
 # Output file base name for HTML help builder.
-htmlhelp_basename = 'TestBreatheWarningsdoc'
+htmlhelp_basename = "TestBreatheWarningsdoc"
 
 
 # -- Options for LaTeX output ---------------------------------------------
 
 latex_elements = {
-# The paper size ('letterpaper' or 'a4paper').
-#'papersize': 'letterpaper',
-
-# The font size ('10pt', '11pt' or '12pt').
-#'pointsize': '10pt',
-
-# Additional stuff for the LaTeX preamble.
-#'preamble': '',
+    # The paper size ('letterpaper' or 'a4paper').
+    #'papersize': 'letterpaper',
+    # The font size ('10pt', '11pt' or '12pt').
+    #'pointsize': '10pt',
+    # Additional stuff for the LaTeX preamble.
+    #'preamble': '',
 }
 
 # Grouping the document tree into LaTeX files. List of tuples
 # (source start file, target name, title,
 #  author, documentclass [howto, manual, or own class]).
 latex_documents = [
-  ('index', 'TestBreatheWarnings.tex', u'Test Breathe Warnings Documentation',
-   u'Michael Jones', 'manual'),
+    (
+        "index",
+        "TestBreatheWarnings.tex",
+        u"Test Breathe Warnings Documentation",
+        u"Michael Jones",
+        "manual",
+    ),
 ]
 
 # The name of an image file (relative to this directory) to place at the top of
 # the title page.
-#latex_logo = None
+# latex_logo = None
 
 # For "manual" documents, if this is true, then toplevel headings are parts,
 # not chapters.
-#latex_use_parts = False
+# latex_use_parts = False
 
 # If true, show page references after internal links.
-#latex_show_pagerefs = False
+# latex_show_pagerefs = False
 
 # If true, show URL addresses after external links.
-#latex_show_urls = False
+# latex_show_urls = False
 
 # Documents to append as an appendix to all manuals.
-#latex_appendices = []
+# latex_appendices = []
 
 # If false, no module index is generated.
-#latex_domain_indices = True
+# latex_domain_indices = True
 
 
 # -- Options for manual page output ---------------------------------------
@@ -238,12 +241,11 @@
 # One entry per manual page. List of tuples
 # (source start file, name, description, authors, manual section).
 man_pages = [
-    ('index', 'testbreathewarnings', u'Test Breathe Warnings Documentation',
-     [u'Michael Jones'], 1)
+    ("index", "testbreathewarnings", u"Test Breathe Warnings Documentation", [u"Michael Jones"], 1)
 ]
 
 # If true, show URL addresses after external links.
-#man_show_urls = False
+# man_show_urls = False
 
 
 # -- Options for Texinfo output -------------------------------------------
@@ -252,19 +254,25 @@
 # (source start file, target name, title, author,
 #  dir menu entry, description, category)
 texinfo_documents = [
-  ('index', 'TestBreatheWarnings', u'Test Breathe Warnings Documentation',
-   u'Michael Jones', 'TestBreatheWarnings', 'One line description of project.',
-   'Miscellaneous'),
+    (
+        "index",
+        "TestBreatheWarnings",
+        u"Test Breathe Warnings Documentation",
+        u"Michael Jones",
+        "TestBreatheWarnings",
+        "One line description of project.",
+        "Miscellaneous",
+    ),
 ]
 
 # Documents to append as an appendix to all manuals.
-#texinfo_appendices = []
+# texinfo_appendices = []
 
 # If false, no module index is generated.
-#texinfo_domain_indices = True
+# texinfo_domain_indices = True
 
 # How to display URL addresses: 'footnote', 'no', or 'inline'.
-#texinfo_show_urls = 'footnote'
+# texinfo_show_urls = 'footnote'
 
 # If true, do not generate a @detailmenu in the "Top" node's menu.
-#texinfo_no_detailmenu = False
+# texinfo_no_detailmenu = False