[hawkmoth] [PATCH 11/13] hawkmoth: fix documentation of anonymous types

  • From: Bruno Santos <brunomanuelsantos@xxxxxxxxxxxxxxxxxx>
  • To: hawkmoth@xxxxxxxxxxxxx
  • Date: Tue, 8 Jan 2019 03:11:08 +0100

There is no quirk-less solution for some cases, this patch makes fair
assumptions as to how someone would actually document the code.

Cases handled are for structures, unions and enumerators. A single approach is
used for all three in spite of the fact that an anonymous enumerator without an
instance may actually be desired (?). However, it's not a heavy burden on the
user to name the enumerator in question if it's not being instantiated and it
does provide better scoping than alternatives by nesting the elements under the
type or the instance name.

It should be noted that the proposed solution differs from Doxygen (which
remains as the de facto standard for C documentation) since, in my experience,
it's counter-intuitive and leads to documentation not matching the intended
output. In particular, in the example:

/** Document foo without declaring a type. */
struct {
        ...
} foo;

Doxygen takes the documentation to belong to the structure, which is unnamed and
therefore not issued and mangles the output. With this patch, Hawkmoth will do
the expected thing and document 'foo' as a variable, nesting all the documented
elements that would otherwise have nowhere to go. This is still compatible with
Doxygen's behaviour, it merely expands Doxygen behaviour to cover this case.
---
 hawkmoth/hawkmoth.py | 85 ++++++++++++++++++++++++++++++++------------
 1 file changed, 62 insertions(+), 23 deletions(-)

diff --git a/hawkmoth/hawkmoth.py b/hawkmoth/hawkmoth.py
index b1424b2..a58fee1 100755
--- a/hawkmoth/hawkmoth.py
+++ b/hawkmoth/hawkmoth.py
@@ -139,12 +139,15 @@ def _get_macro_args(cursor):

 def _recursive_parse(comments, cursor, nest, **options):
     compat = options.get('compat')
-    comment = comments[cursor.hash]
     name = cursor.spelling
     ttype = cursor.type.spelling

     if cursor.kind == CursorKind.MACRO_DEFINITION:
+        if cursor.hash not in comments:
+            return []
+
         # FIXME: check args against comment
+        comment = comments[cursor.hash]
         args = _get_macro_args(cursor)
         fmt = docstr.Type.MACRO if args is None else docstr.Type.MACRO_FUNC

@@ -153,55 +156,91 @@ def _recursive_parse(comments, cursor, nest, **options):

     if cursor.kind == CursorKind.VAR_DECL:
         fmt = docstr.Type.VAR
+        c = cursor

-        return _result(comment, cursor=cursor, fmt=fmt,
-                       nest=nest, name=name, ttype=ttype, compat=compat)
+        # If the type is anonymous, then the variable was declared together 
with
+        # the type. We then take the documentation as if it was meant for the
+        # variable.
+        if ttype.find('(anonymous ') >= 0:
+            c = next(cursor.get_children())
+            ttype = ' '.join([ttype.split()[0], '<anonymous>'])
+
+        if c.hash not in comments:
+            # Not inheriting documentation from an anonymous type definition 
nor
+            # documented on its own. Do nothing.
+            return []
+
+        comment = comments[c.hash]
+        text = comment.spelling
+
+        return _result(comment, cursor=c, fmt=fmt, nest=nest,
+                       name=name, ttype=ttype, compat=compat)

     if cursor.kind == CursorKind.TYPEDEF_DECL:
+        if cursor.hash not in comments:
+            return []
+
         # FIXME: function pointers typedefs.
         fmt = docstr.Type.TYPE
+        comment = comments[cursor.hash]

         return _result(comment, cursor=cursor, fmt=fmt,
                        nest=nest, name=ttype, compat=compat)

     if cursor.kind in [CursorKind.STRUCT_DECL, CursorKind.UNION_DECL,
                        CursorKind.ENUM_DECL]:
+        if cursor.hash not in comments:
+            return []

-        # FIXME:
-        # Handle cases where variables are instantiated on type declaration,
-        # including anonymous cases. Idea is that if there is a variable
-        # instantiation, the documentation should be applied to the variable if
-        # the structure is anonymous or to the type otherwise.
-        #
-        # Due to the new recursiveness of the parser, fixing this here, 
_should_
-        # handle all cases (struct, union, enum).
-
-        # FIXME: Handle anonymous enumerators.
-
-        fmt = docstr.Type.TYPE
-        result = _result(comment, cursor=cursor, fmt=fmt,
-                         nest=nest, name=ttype, compat=compat)
+        # Document this cursor only if it's not anonymous.
+        if cursor.type.spelling.find('(anonymous at ') < 0:
+            fmt = docstr.Type.TYPE
+            comment = comments[cursor.hash]
+            result = _result(comment, cursor=cursor, fmt=fmt,
+                             nest=nest, name=ttype, compat=compat)
+        else:
+            result = []

         nest += 1
         for c in cursor.get_children():
-            if c.hash in comments:
-                result.extend(_recursive_parse(comments, c, nest, **options))
+            result.extend(_recursive_parse(comments, c, nest, **options))

         return result

     if cursor.kind == CursorKind.ENUM_CONSTANT_DECL:
+        if cursor.hash not in comments:
+            return []
+
         fmt = docstr.Type.ENUM_VAL
+        comment = comments[cursor.hash]

         return _result(comment, cursor=cursor, fmt=fmt,
                        nest=nest, name=name, compat=compat)

     if cursor.kind == CursorKind.FIELD_DECL:
+        c = cursor
         fmt = docstr.Type.MEMBER

-        return _result(comment, cursor=cursor, fmt=fmt,
-                       nest=nest, name=name, ttype=ttype, compat=compat)
+        # If the type is anonymous, then the variable was declared together 
with
+        # the type. We then take the documentation as if it was meant for the
+        # variable.
+        if ttype.find('(anonymous ') >= 0:
+            c = next(cursor.get_children())
+            ttype = ' '.join([ttype.split()[0], '<anonymous>'])
+
+        if c.hash not in comments:
+            # Not inheriting documentation from an anonymous type definition 
nor
+            # documented on its own. Do nothing.
+            return []
+
+        comment = comments[c.hash]
+        return _result(comment, cursor=c, fmt=fmt, nest=nest,
+                       name=name, ttype=ttype, compat=compat)

     if cursor.kind == CursorKind.FUNCTION_DECL:
+        if cursor.hash not in comments:
+            return []
+
         # FIXME: check args against comment
         # FIXME: children may contain extra stuff if the return type is a
         # typedef, for example
@@ -213,6 +252,7 @@ def _recursive_parse(comments, cursor, nest, **options):
                                                    arg=c.spelling))

         fmt = docstr.Type.FUNC
+        comment = comments[cursor.hash]
         ttype = cursor.result_type.spelling

         return _result(comment, cursor=cursor, fmt=fmt, nest=nest,
@@ -250,8 +290,7 @@ def parse(filename, **options):

     # Bootstrap the individual parsers.
     for cursor in tu.cursor.get_children():
-        if cursor.hash in comments:
-            result.extend(_recursive_parse(comments, cursor, 0, **options))
+        result.extend(_recursive_parse(comments, cursor, 0, **options))

     # Sort all elements by order of appearance.
     result.sort(key=lambda r: r[1]['line'])
--
2.20.1



Other related posts:

  • » [hawkmoth] [PATCH 11/13] hawkmoth: fix documentation of anonymous types - Bruno Santos