193 lines
7.4 KiB
EmacsLisp
193 lines
7.4 KiB
EmacsLisp
;;; 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>
|
|
;; URL: https://github.com/ch1bo/flycheck-clang-tidy
|
|
;; Keywords: convenience languages tools
|
|
;; Package-Version: 20191030.814
|
|
;; Package-X-Original-Version: 0.3.0
|
|
;; Package-Requires: ((flycheck "0.30"))
|
|
|
|
;; This file is NOT part of GNU Emacs.
|
|
;; See LICENSE
|
|
|
|
;;; Commentary:
|
|
|
|
;; Adds a Flycheck syntax checker for C/C++ based on clang-tidy.
|
|
|
|
;;; Usage:
|
|
|
|
;; (eval-after-load 'flycheck
|
|
;; '(add-hook 'flycheck-mode-hook #'flycheck-clang-tidy-setup))
|
|
|
|
|
|
;;; Code:
|
|
|
|
(require 'flycheck)
|
|
(require 'dom)
|
|
|
|
;; To keep variable names consistent.
|
|
(defvaralias 'flycheck-clang-tidy-executable 'flycheck-c/c++-clang-tidy-executable)
|
|
|
|
(flycheck-def-config-file-var flycheck-clang-tidy c/c++-clang-tidy ".clang-tidy"
|
|
:type 'string
|
|
:safe #'stringp)
|
|
|
|
(flycheck-def-option-var flycheck-clang-tidy-build-path "build" c/c++-clang-tidy
|
|
"Build path to read a compile command database.
|
|
|
|
For example, it can be a CMake build directory in which a file named
|
|
compile_commands.json exists (use -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
|
|
CMake option to get this output)."
|
|
:type 'string
|
|
:safe #'stringp)
|
|
|
|
(flycheck-def-option-var flycheck-clang-tidy-extra-options nil c/c++-clang-tidy
|
|
"Extra options to pass to clang-tidy. Set to `nil' to disable."
|
|
:type 'string
|
|
:safe #'stringp)
|
|
|
|
(defun flycheck-clang-tidy-find-project-root (checker)
|
|
"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)))
|
|
(unless project-root
|
|
(setq project-root (vc-root-dir)))
|
|
(unless project-root
|
|
(let ((config_file_location (flycheck-locate-config-file flycheck-clang-tidy checker)))
|
|
(if config_file_location
|
|
(setq project-root (file-name-directory config_file_location)))))
|
|
(unless project-root
|
|
(message "Could not determine project root, trying current directory.")
|
|
(setq project-root (flycheck-clang-tidy-current-source-dir)))
|
|
project-root))
|
|
|
|
(defun flycheck-clang-tidy-current-source-dir ()
|
|
"Directory of current source file."
|
|
(file-name-directory (buffer-file-name)))
|
|
|
|
(defun flycheck-clang-tidy-get-config ()
|
|
"Find and read .clang-tidy."
|
|
(let ((config-file (flycheck-locate-config-file flycheck-clang-tidy 0)))
|
|
(when config-file
|
|
(with-temp-buffer
|
|
(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.
|
|
|
|
See URL `https://github.com/ch1bo/flycheck-clang-tidy'."
|
|
:command ("clang-tidy"
|
|
(option "-p" flycheck-clang-tidy-build-path)
|
|
(eval (concat "-extra-arg=-I" (flycheck-clang-tidy-current-source-dir)))
|
|
(eval (concat "-config=" (flycheck-clang-tidy-get-config)))
|
|
(eval flycheck-clang-tidy-extra-options)
|
|
source)
|
|
:error-patterns
|
|
((error line-start (file-name) ":" line ":" column ": error: "
|
|
(message) line-end)
|
|
(warning line-start (file-name) ":" line ":" column ": warning: "
|
|
(message) line-end)
|
|
(info line-start (file-name) ":" line ":" column ": note: "
|
|
(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)))
|
|
|
|
;;;###autoload
|
|
(defun flycheck-clang-tidy-setup ()
|
|
"Setup Flycheck clang-tidy."
|
|
(add-to-list 'flycheck-checkers 'c/c++-clang-tidy))
|
|
|
|
(provide 'flycheck-clang-tidy)
|
|
;;; flycheck-clang-tidy.el ends here
|