Skip to content

Commit

Permalink
Merge pull request #11 from danielmartin/add-flycheck-error-explainer
Browse files Browse the repository at this point in the history
Add a Flycheck error explainer
  • Loading branch information
tastytea committed Oct 27, 2019
2 parents eb82f73 + db41bf8 commit d316374
Show file tree
Hide file tree
Showing 2 changed files with 96 additions and 4 deletions.
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,11 @@ file via variable `flycheck-clang-tidy-build-path` which defaults to `build`.
You can pass additional options to `clang-tidy` using the variable
`flycheck-clang-tidy-extra-options`.

This checker includes an error explainer. Invoking `flycheck-explain-error-at-point`
will search clang.llvm.org for the documentation of the clang-tidy check under point
and render the result HTML in a Help buffer. This requires that Emacs is
compiled with XML support.

[flycheck]: https://github.com/flycheck/flycheck
[clang-tidy]: http://clang.llvm.org/extra/clang-tidy
[melpa]: http://melpa.org
Expand Down
95 changes: 91 additions & 4 deletions flycheck-clang-tidy.el
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
;;; flycheck-clang-tidy.el --- Flycheck syntax checker using clang-tidy
;;; flycheck-clang-tidy.el --- Flycheck syntax checker using clang-tidy -*- lexical-binding:t -*-

;; Author: Sebastian Nagel<sebastian.nagel@ncoding.at>
;; Maintainer: tastytea <tastytea@tastytea.de>
Expand All @@ -23,6 +23,7 @@
;;; Code:

(require 'flycheck)
(require 'dom)

;; To keep variable names consistent.
(defvaralias 'flycheck-clang-tidy-executable 'flycheck-c/c++-clang-tidy-executable)
Expand All @@ -46,7 +47,7 @@ CMake option to get this output)."
:safe #'stringp)

(defun flycheck-clang-tidy-find-project-root (checker)
"Find the project root using projectile, vc or the .clang-tidy file."
"Find the project root for CHECKER using Projectile, vc or the .clang-tidy file."
(let ((project-root nil))
(if (member 'projectile-mode minor-mode-list)
(setq project-root (projectile-project-root)))
Expand All @@ -73,6 +74,92 @@ CMake option to get this output)."
(insert-file-contents config-file)
(buffer-string)))))

(defun flycheck-clang-tidy--skip-http-headers ()
"Position point just after HTTP headers."
(re-search-forward "^$"))

(defun flycheck-clang-tidy--narrow-to-http-body ()
"Narrow the current buffer to contain the body of an HTTP response."
(flycheck-clang-tidy--skip-http-headers)
(narrow-to-region (point) (point-max)))

(defun flycheck-clang-tidy--decode-region-as-utf8 (start end)
"Decode a region from START to END in UTF-8."
(condition-case nil
(decode-coding-region start end 'utf-8)
(coding-system-error nil)))

(defun flycheck-clang-tidy--remove-crlf ()
"Remove carriage return and line feeds from the current buffer."
(save-excursion
(while (re-search-forward "\r$" nil t)
(replace-match "" t t))))

(defun flycheck-clang-tidy--extract-relevant-doc-section ()
"Extract the parts of the LLVM clang-tidy documentation that are relevant.
This function assumes that the current buffer contains the result
of browsing 'clang.llvm.org', as returned by `url-retrieve'.
More concretely, this function returns the main <div> element
with class 'section', and also removes 'headerlinks'."
(goto-char (point-min))
(flycheck-clang-tidy--narrow-to-http-body)
(flycheck-clang-tidy--decode-region-as-utf8 (point-min) (point-max))
(flycheck-clang-tidy--remove-crlf)
(let* ((dom (libxml-parse-html-region (point-min) (point-max)))
(section (dom-by-class dom "section")))
(dolist (headerlink (dom-by-class section "headerlink"))
(dom-remove-node section headerlink))
section))

(defun flycheck-clang-tidy--explain-error (explanation &rest args)
"Explain an error in the Flycheck error explanation buffer using EXPLANATION.
EXPLANATION is a function with optional ARGS that, when
evaluated, inserts the content in the appropriate Flycheck
buffer."
(with-current-buffer flycheck-explain-error-buffer
(let ((inhibit-read-only t)
(inhibit-modification-hooks t))
(erase-buffer)
(apply explanation args)
(goto-char (point-min)))))

(defun flycheck-clang-tidy--show-documentation (error-id)
"Show clang-tidy documentation about ERROR-ID.
Information comes from the clang.llvm.org website."
(url-retrieve (format
"https://clang.llvm.org/extra/clang-tidy/checks/%s.html" error-id)
(lambda (status)
(if-let ((error-status (plist-get status :error)))
(flycheck-clang-tidy--explain-error
#'insert
(format
"Error accessing clang-tidy documentation: %s"
(error-message-string error-status)))
(let ((doc-contents
(flycheck-clang-tidy--extract-relevant-doc-section)))
(flycheck-clang-tidy--explain-error
#'shr-insert-document doc-contents)))))
"Loading documentation...")

(defun flycheck-clang-tidy-error-explainer (error)
"Explain a clang-tidy ERROR by scraping documentation from llvm.org."
(unless (fboundp 'libxml-parse-html-region)
(error "This function requires Emacs to be compiled with libxml2"))
(if-let (err-message (flycheck-error-message error))
(if-let (((string-match "\\[\\(.*\\)\\]" err-message))
(clang-tidy-error-id (match-string 1 err-message)))
(condition-case err
(flycheck-clang-tidy--show-documentation clang-tidy-error-id)
(error
(format
"Error accessing clang-tidy documentation: %s"
(error-message-string err))))
(error "The clang-tidy error message does not contain an [error-id]"))
(error "Flycheck error does not contain an error message")))

(flycheck-define-checker c/c++-clang-tidy
"A C/C++ syntax checker using clang-tidy.
Expand All @@ -92,9 +179,9 @@ See URL `https://github.com/ch1bo/flycheck-clang-tidy'."
(message) line-end))
:modes (c-mode c++-mode)
:working-directory flycheck-clang-tidy-find-project-root
:error-explainer flycheck-clang-tidy-error-explainer
:predicate (lambda () (buffer-file-name))
:next-checkers ((error . c/c++-cppcheck))
)
:next-checkers ((error . c/c++-cppcheck)))

;;;###autoload
(defun flycheck-clang-tidy-setup ()
Expand Down

0 comments on commit d316374

Please sign in to comment.