From 6616fe86b362022a690ea6c5d0ff0f00037ba111 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Tue, 14 Aug 2018 23:20:28 +0200 Subject: [PATCH] doxygen: support friend functions. Not friend classes/structs/unions, in that case Doxygen provides way too little info to be at all useful. --- doc/doxygen.rst | 14 +++- doxygen/dox2html5.py | 41 ++++++++++- doxygen/templates/base-class-reference.html | 20 +++++- doxygen/test/cpp_friends/Doxyfile | 14 ++++ doxygen/test/cpp_friends/File.h | 57 +++++++++++++++ doxygen/test/cpp_friends/classClass.html | 62 ++++++++++++++++ doxygen/test/cpp_friends/classTemplate.html | 78 +++++++++++++++++++++ doxygen/test/test_cpp.py | 11 +++ 8 files changed, 290 insertions(+), 7 deletions(-) create mode 100644 doxygen/test/cpp_friends/Doxyfile create mode 100644 doxygen/test/cpp_friends/File.h create mode 100644 doxygen/test/cpp_friends/classClass.html create mode 100644 doxygen/test/cpp_friends/classTemplate.html diff --git a/doc/doxygen.rst b/doc/doxygen.rst index 7789110b..da428dcf 100644 --- a/doc/doxygen.rst +++ b/doc/doxygen.rst @@ -238,6 +238,11 @@ amount of generated content for no added value. - Clickable symbols in code snippets. Doxygen has quite a lot of false positives while a lot of symbols stay unmatched. I need to find a way around that. +- Documented friend classes, structs and unions. Doxygen is unable to + cross-link the declarations with the definitions. +- Proper scoping for friend and related functions/classes/variables etc. + Doxygen doesn't provide any namespace scoping for these and at the moment + I have no way to deduct that information. `Configuration`_ ================ @@ -1267,6 +1272,9 @@ Property Description :py:`compound.private_funcs` List of documented private virtual functions. Set only for classes. See `Function properties`_ for details. +:py:`compound.friend_funcs` List of documented friend functions. + Set only for classes. See + `Function properties`_ for details. :py:`compound.related` List of related non-member symbols. Set only for classes. See `Related properties`_ for details. @@ -1492,9 +1500,9 @@ Property Description The :py:`commpound.funcs`, :py:`compound.public_static_funcs`, :py:`compound.public_funcs`, :py:`compound.protected_static_funcs`, -:py:`compound.protected_funcs`, :py:`compound.private_funcs` and -:py:`compound.related_funcs` properties contain a list of functions, where -every item has the following properties: +:py:`compound.protected_funcs`, :py:`compound.private_funcs`, +:py:`compound.friend_funcs` and :py:`compound.related_funcs` properties contain +a list of functions, where every item has the following properties: .. class:: m-table m-fullwidth diff --git a/doxygen/dox2html5.py b/doxygen/dox2html5.py index 1bc8a381..c3d68f50 100755 --- a/doxygen/dox2html5.py +++ b/doxygen/dox2html5.py @@ -1570,7 +1570,7 @@ def parse_typedef(state: State, element: ET.Element): return None def parse_func(state: State, element: ET.Element): - assert element.tag == 'memberdef' and element.attrib['kind'] == 'function' + assert element.tag == 'memberdef' and element.attrib['kind'] in ['function', 'friend'] func = Empty() state.current_definition_url_base, func.base_url, func.id = parse_id(element) @@ -1579,6 +1579,10 @@ def parse_func(state: State, element: ET.Element): func.brief = parse_desc(state, element.find('briefdescription')) func.description, templates, params, func.return_value, func.return_values, func.exceptions, search_keywords, func.is_deprecated = parse_func_desc(state, element) + # Friend functions have friend as type. That's just awful. COME ON. + if func.type.startswith('friend '): + func.type = func.type[7:] + # Extract function signature to prefix, suffix and various flags. Important # things affecting caller such as static or const (and rvalue overloads) # are put into signature prefix/suffix, other things to various is_* @@ -1619,8 +1623,10 @@ def parse_func(state: State, element: ET.Element): func.is_pure_virtual = False func.suffix = html.escape(signature[signature.rindex(')') + 1:].strip()) if func.suffix: func.suffix = ' ' + func.suffix - func.is_protected = element.attrib['prot'] == 'protected' - func.is_private = element.attrib['prot'] == 'private' + # Protected / private makes no sense for friend functions + if element.attrib['kind'] != 'friend': + func.is_protected = element.attrib['prot'] == 'protected' + func.is_private = element.attrib['prot'] == 'private' func.has_template_details, func.templates = parse_template_params(state, element.find('templateparamlist'), templates) @@ -2085,6 +2091,7 @@ def parse_xml(state: State, xml: str): compound.protected_vars = [] compound.private_funcs = [] compound.related = [] + compound.friend_funcs = [] compound.groups = [] compound.has_enum_details = False compound.has_typedef_details = False @@ -2434,6 +2441,22 @@ def parse_xml(state: State, xml: str): else: # pragma: no cover logging.warning("{}: unknown related kind {}".format(state.current, memberdef.attrib['kind'])) + elif compounddef_child.attrib['kind'] == 'friend': + for memberdef in compounddef_child: + # Ignore friend classes. This does not ignore friend + # classes written as `friend Foo;`, those are parsed as + # variables (ugh). + if memberdef.find('type').text in ['friend class', 'friend struct', 'friend union']: + # Print a warning in case these are documented + if (''.join(memberdef.find('briefdescription').itertext()).strip() or ''.join(memberdef.find('detaileddescription').itertext()).strip()): + logging.warning("{}: doxygen is unable to cross-link {}, ignoring, sorry".format(state.current, memberdef.find('definition').text)) + # Only friend functions left, hopefully, parse as a func + else: + func = parse_func(state, memberdef) + if func: + compound.friend_funcs += [func] + if func.has_details: compound.has_func_details = True + elif compounddef_child.attrib['kind'] == 'user-defined': list = [] @@ -2464,6 +2487,18 @@ def parse_xml(state: State, xml: str): if define: list += [('define', define)] if define.has_details: compound.has_define_details = True + elif memberdef.attrib['kind'] == 'friend': + # Ignore friend classes. This does not ignore friend + # classes written as `friend Foo;`, those are parsed as + # variables (ugh). + if memberdef.find('type').text in ['friend class', 'friend struct', 'friend union'] and (memberdef.find('briefdescription').text or memberdef.find('detaileddescription').text): + logging.warning("{}: doxygen is unable to cross-link {}, ignoring, sorry".format(state.current, memberdef.find('definition').text)) + # Only friend functions left, hopefully, parse as a func + else: + func = parse_func(state, memberdef) + if func: + list += [('func', func)] + if func.has_details: compound.has_func_details = True else: # pragma: no cover logging.warning("{}: unknown user-defined kind {}".format(state.current, memberdef.attrib['kind'])) diff --git a/doxygen/templates/base-class-reference.html b/doxygen/templates/base-class-reference.html index c94ba64b..f80af0cb 100644 --- a/doxygen/templates/base-class-reference.html +++ b/doxygen/templates/base-class-reference.html @@ -41,7 +41,7 @@ {% endif %} - {% if compound.sections or compound.public_types or compound.public_static_funcs or compound.typeless_funcs or compound.public_funcs or compound.public_static_vars or compound.public_vars or compound.protected_types or compound.protected_static_funcs or compound.protected_funcs or compound.protected_static_vars or compound.protected_vars or compound.private_funcs or compound.groups or compound.relate %} + {% if compound.sections or compound.public_types or compound.public_static_funcs or compound.typeless_funcs or compound.public_funcs or compound.public_static_vars or compound.public_vars or compound.protected_types or compound.protected_static_funcs or compound.protected_funcs or compound.protected_static_vars or compound.protected_vars or compound.private_funcs or compound.groups or compound.friend_funcs or compound.related %}

Contents

    @@ -105,6 +105,9 @@ {% for group in compound.groups %}
  • {{ group.name }}
  • {% endfor %} + {% if compound.friend_funcs %} +
  • Friends
  • + {% endif %} {% if compound.related %}
  • Related
  • {% endif %} @@ -289,6 +292,16 @@ {% endfor %} + {% if compound.friend_funcs %} +
    +

    Friends

    +
    + {% for func in compound.friend_funcs %} +{{ entry_func(func) }} + {% endfor %} +
    +
    + {% endif %} {% if compound.related %}