[hawkmoth] [PATCH 2/4] hawkmoth: add support for propagating diagnostics

  • From: Bruno Santos <brunomanuelsantos@xxxxxxxxxxxxxxxxxx>
  • To: hawkmoth mailing list <hawkmoth@xxxxxxxxxxxxx>, Jani Nikula <jani@xxxxxxxxxx>
  • Date: Sun, 28 Apr 2019 11:42:38 +0200

At the same time, the first diagnostics are implemented or rather
forwarded from libclang parse. Clang diagnostics are limited to warnings
and higher as these are the levels at which documentation breaking
issues are found.

Original concept: Marius Vlad
---
 hawkmoth/__init__.py  | 26 +++++++++++++++++++++++--
 hawkmoth/__main__.py  |  8 +++++---
 hawkmoth/parser.py    | 44 ++++++++++++++++++++++++++++++++++++-------
 test/test_hawkmoth.py |  4 +++-
 4 files changed, 69 insertions(+), 13 deletions(-)

diff --git a/hawkmoth/__init__.py b/hawkmoth/__init__.py
index 3355922..9141a0d 100644
--- a/hawkmoth/__init__.py
+++ b/hawkmoth/__init__.py
@@ -21,7 +21,7 @@
 from sphinx.util.docutils import switch_source_input
 from sphinx.util import logging
 
-from hawkmoth.parser import parse
+from hawkmoth.parser import parse, ErrorLevel
 
 with open(os.path.join(os.path.abspath(os.path.dirname(__file__)),
                        'VERSION')) as version_file:
@@ -42,13 +42,35 @@ class CAutoDocDirective(Directive):
     }
     has_content = False
 
+    def __display_parser_diagnostics(self, errors):
+        env = self.state.document.settings.env
+
+        for (severity, filename, lineno, msg) in errors:
+            toprint = '{}:{}: {}'.format(filename, lineno, msg)
+
+            if severity is ErrorLevel.INFO:
+                if env.app.verbosity >= 2:
+                    self.logger.info(toprint,
+                                     location=(env.docname, self.lineno))
+            elif severity is ErrorLevel.WARNING:
+                if env.app.verbosity >= 1:
+                    self.logger.warning(toprint,
+                                        location=(env.docname, self.lineno))
+            elif severity is ErrorLevel.ERROR:
+                self.logger.error(toprint, location=(env.docname, self.lineno))
+            else:
+                self.logger.critical(toprint,
+                                     location=(env.docname, self.lineno))
+
     def __parse(self, viewlist, filename):
         env = self.state.document.settings.env
 
         compat = self.options.get('compat', env.config.cautodoc_compat)
         clang = self.options.get('clang', env.config.cautodoc_clang)
 
-        comments = parse(filename, compat=compat, clang=clang)
+        comments, errors = parse(filename, compat=compat, clang=clang)
+
+        self.__display_parser_diagnostics(errors)
 
         for (comment, meta) in comments:
             lineoffset = meta['line'] - 1
diff --git a/hawkmoth/__main__.py b/hawkmoth/__main__.py
index 8d237d7..6689777 100644
--- a/hawkmoth/__main__.py
+++ b/hawkmoth/__main__.py
@@ -31,8 +31,10 @@ def main():
                         help='Verbose output.')
     args = parser.parse_args()
 
-    comments = parse_to_string(args.file, args.verbose,
-                               compat=args.compat, clang=args.clang)
-    sys.stdout.write(comments)
+    comments, errors = parse_to_string(args.file, args.verbose,
+                                       compat=args.compat, clang=args.clang)
+
+    print(comments)
+    print(errors, file=sys.stderr)
 
 main()
diff --git a/hawkmoth/parser.py b/hawkmoth/parser.py
index 5f2509b..20ca327 100644
--- a/hawkmoth/parser.py
+++ b/hawkmoth/parser.py
@@ -35,6 +35,7 @@
 
 import itertools
 import sys
+from enum import Enum, auto
 
 from clang.cindex import CursorKind
 from clang.cindex import Index, TranslationUnit
@@ -43,6 +44,12 @@
 
 from hawkmoth.util import docstr, doccompat
 
+class ErrorLevel(Enum):
+    """Enumeration of supported log levels."""
+    INFO = auto()
+    WARNING = auto()
+    ERROR = auto()
+
 def comment_extract(tu):
 
     # FIXME: How to handle top level comments above a cursor that it does *not*
@@ -239,10 +246,24 @@ def _recursive_parse(comments, cursor, nest, compat):
 
     return [(doc, meta)]
 
+def clang_diagnostics(errors, diagnostics):
+    sev = {0: ErrorLevel.INFO,
+           1: ErrorLevel.INFO,
+           2: ErrorLevel.WARNING,
+           3: ErrorLevel.ERROR,
+           4: ErrorLevel.ERROR}
+
+    for diag in diagnostics:
+        # Discard any Clang error lower than a warning.
+        if diag.severity >= 2:
+            errors.extend([(sev[diag.severity], diag.location.file.name,
+                            diag.location.line, diag.spelling)])
+
 # return a list of (comment, metadata) tuples
 # options - dictionary with directive options
 def parse(filename, **options):
 
+    errors = []
     args = options.get('clang')
     if args is not None:
         args = [s.strip() for s in args.split(',') if len(s.strip()) > 0]
@@ -255,6 +276,8 @@ def parse(filename, **options):
                      TranslationUnit.PARSE_DETAILED_PROCESSING_RECORD |
                      TranslationUnit.PARSE_SKIP_FUNCTION_BODIES)
 
+    clang_diagnostics(errors, tu.diagnostics)
+
     top_level_comments, comments = comment_extract(tu)
 
     result = []
@@ -270,13 +293,20 @@ def parse(filename, **options):
     # Sort all elements by order of appearance.
     result.sort(key=lambda r: r[1]['line'])
 
-    return result
+    return result, errors
 
 def parse_to_string(filename, verbose, **options):
-    s = ''
-    comments = parse(filename, **options)
-    for (comment, meta) in comments:
+    docs_str = ''
+    errors_str = ''
+    docs, errors = parse(filename, **options)
+
+    for (doc, meta) in docs:
         if verbose:
-            s += ('# ' + str(meta) + '\n')
-        s += comment + '\n'
-    return s
+            docs_str += ('# ' + str(meta) + '\n')
+        docs_str += doc + '\n'
+
+    for (severity, filename, lineno, msg) in errors:
+        errors_str += '{}: {}:{}: {}\n'.format(severity.name,
+                                               filename, lineno, msg)
+
+    return docs_str, errors_str
diff --git a/test/test_hawkmoth.py b/test/test_hawkmoth.py
index 84101c3..61c0f08 100755
--- a/test/test_hawkmoth.py
+++ b/test/test_hawkmoth.py
@@ -9,7 +9,9 @@
 from hawkmoth.parser import parse_to_string
 
 def _get_output(input_filename, **options):
-    return parse_to_string(input_filename, False, **options)
+    output, errors = parse_to_string(input_filename, False, **options)
+    # FIXME: should handle errors
+    return output
 
 def _get_expected(input_filename, **options):
     return testenv.read_file(input_filename, ext='rst')
-- 
2.21.0


Other related posts: