Permalink
9895 lines (8378 sloc) 379 KB
;;; flycheck.el --- On-the-fly syntax checking -*- lexical-binding: t; -*-
;; Copyright (C) 2017 Flycheck contributors
;; Copyright (C) 2012-2016 Sebastian Wiesner and Flycheck contributors
;; Copyright (C) 2013, 2014 Free Software Foundation, Inc.
;;
;; Author: Sebastian Wiesner <swiesner@lunaryorn.com>
;; Maintainer: Clément Pit-Claudel <clement.pitclaudel@live.com>
;; fmdkdd <fmdkdd@gmail.com>
;; URL: http://www.flycheck.org
;; Keywords: convenience, languages, tools
;; Version: 31-cvs
;; Package-Requires: ((dash "2.12.1") (pkg-info "0.4") (let-alist "1.0.4") (seq "1.11") (emacs "24.3"))
;; This file is not part of GNU Emacs.
;; This program is free software: you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
;;; Commentary:
;; On-the-fly syntax checking for GNU Emacs 24.
;;
;; Flycheck is a modern on-the-fly syntax checking extension for GNU Emacs,
;; intended as replacement for the older Flymake extension which is part of GNU
;; Emacs.
;;
;; Flycheck automatically checks buffers for errors while you type, and reports
;; warnings and errors directly in the buffer and in an optional IDE-like error
;; list.
;;
;; It comes with a rich interface for custom syntax checkers and other
;; extensions, and has already many 3rd party extensions adding new features.
;;
;; Please read the online manual at http://www.flycheck.org for more
;; information. You can open the manual directly from Emacs with `M-x
;; flycheck-manual'.
;;
;; # Setup
;;
;; Flycheck works best on Unix systems. It does not officially support Windows,
;; but tries to maintain Windows compatibility and should generally work fine on
;; Windows, too.
;;
;; To enable Flycheck add the following to your init file:
;;
;; (add-hook 'after-init-hook #'global-flycheck-mode)
;;
;; Flycheck will then automatically check buffers in supported languages, as
;; long as all necessary tools are present. Use `flycheck-verify-setup' to
;; troubleshoot your Flycheck setup.
;;; Code:
(eval-when-compile
(require 'let-alist) ; `let-alist'
(require 'compile) ; Compile Mode integration
(require 'jka-compr) ; To inhibit compression of temp files
(require 'pcase) ; `pcase-dolist' (`pcase' itself is autoloaded)
)
(require 'dash)
(require 'seq) ; Sequence functions
(require 'subr-x nil 'no-error) ; Additional utilities, Emacs 24.4 and upwards
(require 'cl-lib) ; `cl-defstruct' and CL utilities
(require 'tabulated-list) ; To list errors
(require 'easymenu) ; Flycheck Mode menu definition
(require 'rx) ; Regexp fanciness in `flycheck-define-checker'
(require 'help-mode) ; `define-button-type'
(require 'find-func) ; `find-function-regexp-alist'
(require 'json) ; `flycheck-parse-tslint'
;; Declare a bunch of dynamic variables that we need from other modes
(defvar sh-shell) ; For shell script checker predicates
(defvar ess-language) ; For r-lintr predicate
;; Tell the byte compiler about autoloaded functions from packages
(declare-function pkg-info-version-info "pkg-info" (package))
;;; Compatibility
(eval-and-compile
(unless (fboundp 'string-suffix-p)
;; TODO: Remove when dropping support for Emacs 24.3 and earlier
(defun string-suffix-p (suffix string &optional ignore-case)
"Return non-nil if SUFFIX is a suffix of STRING.
If IGNORE-CASE is non-nil, the comparison is done without paying
attention to case differences."
(let ((start-pos (- (length string) (length suffix))))
(and (>= start-pos 0)
(eq t (compare-strings suffix nil nil
string start-pos nil ignore-case))))))
;; TODO: Remove when dropping support for Emacs 24.3 and earlier
(unless (featurep 'subr-x)
;; `subr-x' function for Emacs 24.3 and below
(defsubst string-join (strings &optional separator)
"Join all STRINGS using SEPARATOR."
(mapconcat 'identity strings separator))
(defsubst string-trim-left (string)
"Remove leading whitespace from STRING."
(if (string-match "\\`[ \t\n\r]+" string)
(replace-match "" t t string)
string))
(defsubst string-trim-right (string)
"Remove trailing whitespace from STRING."
(if (string-match "[ \t\n\r]+\\'" string)
(replace-match "" t t string)
string))
(defsubst string-trim (string)
"Remove leading and trailing whitespace from STRING."
(string-trim-left (string-trim-right string)))
(defsubst string-empty-p (string)
"Check whether STRING is empty."
(string= string ""))))
;;; Customization
(defgroup flycheck nil
"Modern on-the-fly syntax checking for GNU Emacs."
:prefix "flycheck-"
:group 'tools
:link '(url-link :tag "Website" "http://www.flycheck.org")
:link '(url-link :tag "Github" "https://github.com/flycheck/flycheck"))
(defgroup flycheck-config-files nil
"Configuration files for on-the-fly syntax checkers."
:prefix "flycheck-"
:group 'flycheck)
(defgroup flycheck-options nil
"Options for on-the-fly syntax checkers."
:prefix "flycheck-"
:group 'flycheck)
(defgroup flycheck-executables nil
"Executables of syntax checkers."
:prefix "flycheck-"
:group 'flycheck)
(defgroup flycheck-faces nil
"Faces used by on-the-fly syntax checking."
:prefix "flycheck-"
:group 'flycheck)
(defcustom flycheck-checkers
'(ada-gnat
asciidoctor
asciidoc
c/c++-clang
c/c++-gcc
c/c++-cppcheck
cfengine
chef-foodcritic
coffee
coffee-coffeelint
coq
css-csslint
css-stylelint
d-dmd
dockerfile-hadolint
elixir-dogma
emacs-lisp
emacs-lisp-checkdoc
erlang-rebar3
erlang
eruby-erubis
fortran-gfortran
go-gofmt
go-golint
go-vet
go-build
go-test
go-errcheck
go-unconvert
go-megacheck
groovy
haml
handlebars
haskell-stack-ghc
haskell-ghc
haskell-hlint
html-tidy
javascript-eslint
javascript-jshint
javascript-jscs
javascript-standard
json-jsonlint
json-python-json
less
less-stylelint
lua-luacheck
lua
perl
perl-perlcritic
php
php-phpmd
php-phpcs
processing
protobuf-protoc
pug
puppet-parser
puppet-lint
python-flake8
python-pylint
python-pycompile
r-lintr
racket
rpm-rpmlint
markdown-mdl
nix
rst-sphinx
rst
ruby-rubocop
ruby-reek
ruby-rubylint
ruby
ruby-jruby
rust-cargo
rust
scala
scala-scalastyle
scheme-chicken
scss-lint
scss-stylelint
sass/scss-sass-lint
sass
scss
sh-bash
sh-posix-dash
sh-posix-bash
sh-zsh
sh-shellcheck
slim
slim-lint
sql-sqlint
systemd-analyze
tex-chktex
tex-lacheck
texinfo
typescript-tslint
verilog-verilator
xml-xmlstarlet
xml-xmllint
yaml-jsyaml
yaml-ruby)
"Syntax checkers available for automatic selection.
A list of Flycheck syntax checkers to choose from when syntax
checking a buffer. Flycheck will automatically select a suitable
syntax checker from this list, unless `flycheck-checker' is set,
either directly or with `flycheck-select-checker'.
You should not need to change this variable normally. In order
to disable syntax checkers, please use
`flycheck-disabled-checkers'. This variable is intended for 3rd
party extensions to tell Flycheck about new syntax checkers.
Syntax checkers in this list must be defined with
`flycheck-define-checker'."
:group 'flycheck
:type '(repeat (symbol :tag "Checker"))
:risky t)
(defcustom flycheck-disabled-checkers nil
"Syntax checkers excluded from automatic selection.
A list of Flycheck syntax checkers to exclude from automatic
selection. Flycheck will never automatically select a syntax
checker in this list, regardless of the value of
`flycheck-checkers'.
However, syntax checkers in this list are still available for
manual selection with `flycheck-select-checker'.
Use this variable to disable syntax checkers, instead of removing
the syntax checkers from `flycheck-checkers'. You may also use
this option as a file or directory local variable to disable
specific checkers in individual files and directories
respectively."
:group 'flycheck
:type '(repeat (symbol :tag "Checker"))
:package-version '(flycheck . "0.16")
:safe #'flycheck-symbol-list-p)
(make-variable-buffer-local 'flycheck-disabled-checkers)
(defvar-local flycheck-checker nil
"Syntax checker to use for the current buffer.
If unset or nil, automatically select a suitable syntax checker
from `flycheck-checkers' on every syntax check.
If set to a syntax checker only use this syntax checker and never
select one from `flycheck-checkers' automatically. The syntax
checker is used regardless of whether it is contained in
`flycheck-checkers' or `flycheck-disabled-checkers'. If the
syntax checker is unusable in the current buffer an error is
signaled.
A syntax checker assigned to this variable must be defined with
`flycheck-define-checker'.
Use the command `flycheck-select-checker' to select a syntax
checker for the current buffer, or set this variable as file
local variable to always use a specific syntax checker for a
file. See Info Node `(emacs)Specifying File Variables' for more
information about file variables.")
(put 'flycheck-checker 'safe-local-variable 'flycheck-registered-checker-p)
(defcustom flycheck-locate-config-file-functions nil
"Functions to locate syntax checker configuration files.
Each function in this hook must accept two arguments: The value
of the configuration file variable, and the syntax checker
symbol. It must return either a string with an absolute path to
the configuration file, or nil, if it cannot locate the
configuration file.
The functions in this hook are called in order of appearance, until a
function returns non-nil. The configuration file returned by that
function is then given to the syntax checker if it exists.
This variable is an abnormal hook. See Info
node `(elisp)Hooks'."
:group 'flycheck
:type 'hook
:risky t)
(defcustom flycheck-checker-error-threshold 400
"Maximum errors allowed per syntax checker.
The value of this variable is either an integer denoting the
maximum number of errors per syntax checker and buffer, or nil to
not limit the errors reported from a syntax checker.
If this variable is a number and a syntax checker reports more
errors than the value of this variable, its errors are not
discarded, and not highlighted in the buffer or available in the
error list. The affected syntax checker is also disabled for
future syntax checks of the buffer."
:group 'flycheck
:type '(choice (const :tag "Do not limit reported errors" nil)
(integer :tag "Maximum number of errors"))
:risky t
:package-version '(flycheck . "0.22"))
(defcustom flycheck-process-error-functions nil
"Functions to process errors.
Each function in this hook must accept a single argument: A
Flycheck error to process.
All functions in this hook are called in order of appearance,
until a function returns non-nil. Thus, a function in this hook
may return nil, to allow for further processing of the error, or
any non-nil value, to indicate that the error was fully processed
and inhibit any further processing.
The functions are called for each newly parsed error immediately
after the corresponding syntax checker finished. At this stage,
the overlays from the previous syntax checks are still present,
and there may be further syntax checkers in the chain.
This variable is an abnormal hook. See Info
node `(elisp)Hooks'."
:group 'flycheck
:type 'hook
:package-version '(flycheck . "0.13")
:risky t)
(defcustom flycheck-display-errors-delay 0.9
"Delay in seconds before displaying errors at point.
Use floating point numbers to express fractions of seconds."
:group 'flycheck
:type 'number
:package-version '(flycheck . "0.15")
:safe #'numberp)
(defcustom flycheck-display-errors-function #'flycheck-display-error-messages
"Function to display error messages.
If set to a function, call the function with the list of errors
to display as single argument. Each error is an instance of the
`flycheck-error' struct.
If set to nil, do not display errors at all."
:group 'flycheck
:type '(choice (const :tag "Display error messages"
flycheck-display-error-messages)
(const :tag "Display error messages only if no error list"
flycheck-display-error-messages-unless-error-list)
(function :tag "Error display function"))
:package-version '(flycheck . "0.13")
:risky t)
(defcustom flycheck-help-echo-function #'flycheck-help-echo-all-error-messages
"Function to compute the contents of the error tooltips.
If set to a function, call the function with the list of errors
to display as single argument. Each error is an instance of the
`flycheck-error' struct. The function is used to set the
help-echo property of flycheck error overlays. It should return
a string, which is displayed when the user hovers over an error
or presses \\[display-local-help].
If set to nil, do not show error tooltips."
:group 'flycheck
:type '(choice (const :tag "Concatenate error messages to form a tooltip"
flycheck-help-echo-all-error-messages)
(function :tag "Help echo function"))
:package-version '(flycheck . "0.25")
:risky t)
(defcustom flycheck-command-wrapper-function #'identity
"Function to modify checker commands before execution.
The value of this option is a function which is given a list
containing the full command of a syntax checker after
substitution through `flycheck-substitute-argument' but before
execution. The function may return a new command for Flycheck to
execute.
The default value is `identity' which does not change the
command. You may provide your own function to run Flycheck
commands through `bundle exec', `nix-shell' or similar wrappers."
:group 'flycheck
:type '(choice (const :tag "Do not modify commands" identity)
(function :tag "Modify command with a custom function"))
:package-version '(flycheck . "0.25")
:risky t)
(defcustom flycheck-executable-find #'executable-find
"Function to search for executables.
The value of this option is a function which is given the name or
path of an executable and shall return the full path to the
executable, or nil if the executable does not exit.
The default is the standard `executable-find' function which
searches `exec-path'. You can customize this option to search
for checkers in other environments such as bundle or NixOS
sandboxes."
:group 'flycheck
:type '(choice (const :tag "Search executables in `exec-path'" executable-find)
(function :tag "Search executables with a custom function"))
:package-version '(flycheck . "0.25")
:risky t)
(defcustom flycheck-indication-mode 'left-fringe
"The indication mode for Flycheck errors and warnings.
This variable controls how Flycheck indicates errors in buffers.
May either be `left-fringe', `right-fringe', or nil.
If set to `left-fringe' or `right-fringe', indicate errors and
warnings via icons in the left and right fringe respectively.
If set to nil, do not indicate errors and warnings, but just
highlight them according to `flycheck-highlighting-mode'."
:group 'flycheck
:type '(choice (const :tag "Indicate in the left fringe" left-fringe)
(const :tag "Indicate in the right fringe" right-fringe)
(const :tag "Do not indicate" nil))
:safe #'symbolp)
(defcustom flycheck-highlighting-mode 'symbols
"The highlighting mode for Flycheck errors and warnings.
The highlighting mode controls how Flycheck highlights errors in
buffers. The following modes are known:
`columns'
Highlight the error column. If the error does not have a column,
highlight the whole line.
`symbols'
Highlight the symbol at the error column, if there is any,
otherwise behave like `columns'. This is the default.
`sexps'
Highlight the expression at the error column, if there is
any, otherwise behave like `columns'. Note that this mode
can be *very* slow in some major modes.
`lines'
Highlight the whole line.
nil
Do not highlight errors at all. However, errors will still
be reported in the mode line and in error message popups,
and indicated according to `flycheck-indication-mode'."
:group 'flycheck
:type '(choice (const :tag "Highlight columns only" columns)
(const :tag "Highlight symbols" symbols)
(const :tag "Highlight expressions" sexps)
(const :tag "Highlight whole lines" lines)
(const :tag "Do not highlight errors" nil))
:package-version '(flycheck . "0.14")
:safe #'symbolp)
(defcustom flycheck-check-syntax-automatically '(save
idle-change
new-line
mode-enabled)
"When Flycheck should check syntax automatically.
This variable is a list of events that may trigger syntax checks.
The following events are known:
`save'
Check syntax immediately after the buffer was saved.
`idle-change'
Check syntax a short time (see `flycheck-idle-change-delay')
after the last change to the buffer.
`new-line'
Check syntax immediately after a new line was inserted into
the buffer.
`mode-enabled'
Check syntax immediately when variable `flycheck-mode' is
non-nil.
Flycheck performs a syntax checks only on events, which are
contained in this list. For instance, if the value of this
variable is `(mode-enabled save)', Flycheck will only check if
the mode is enabled or the buffer was saved, but never after
changes to the buffer contents.
If nil, never check syntax automatically. In this case, use
`flycheck-buffer' to start a syntax check manually."
:group 'flycheck
:type '(set (const :tag "After the buffer was saved" save)
(const :tag "After the buffer was changed and idle" idle-change)
(const :tag "After a new line was inserted" new-line)
(const :tag "After `flycheck-mode' was enabled" mode-enabled))
:package-version '(flycheck . "0.12")
:safe #'flycheck-symbol-list-p)
(defcustom flycheck-idle-change-delay 0.5
"How many seconds to wait before checking syntax automatically.
After the buffer was changed, Flycheck will wait as many seconds
as the value of this variable before starting a syntax check. If
the buffer is modified during this time, Flycheck will wait
again.
This variable has no effect, if `idle-change' is not contained in
`flycheck-check-syntax-automatically'."
:group 'flycheck
:type 'number
:package-version '(flycheck . "0.13")
:safe #'numberp)
(defcustom flycheck-standard-error-navigation t
"Whether to support error navigation with `next-error'.
If non-nil, enable navigation of Flycheck errors with
`next-error', `previous-error' and `first-error'. Otherwise,
these functions just navigate errors from compilation modes.
Flycheck error navigation with `flycheck-next-error',
`flycheck-previous-error' and `flycheck-first-error' is always
enabled, regardless of the value of this variable.
Note that this setting only takes effect when variable
`flycheck-mode' is non-nil. Changing it will not affect buffers
where variable `flycheck-mode' is already non-nil."
:group 'flycheck
:type 'boolean
:package-version '(flycheck . "0.15")
:safe #'booleanp)
(define-widget 'flycheck-minimum-level 'lazy
"A radio-type choice of minimum error levels.
See `flycheck-navigation-minimum-level' and
`flycheck-error-list-minimum-level'."
:type '(radio (const :tag "All locations" nil)
(const :tag "Informational messages" info)
(const :tag "Warnings" warning)
(const :tag "Errors" error)
(symbol :tag "Custom error level")))
(defcustom flycheck-navigation-minimum-level nil
"The minimum level of errors to navigate.
If set to an error level, only navigate errors whose error level
is at least as severe as this one. If nil, navigate all errors."
:group 'flycheck
:type 'flycheck-minimum-level
:safe #'flycheck-error-level-p
:package-version '(flycheck . "0.21"))
(defcustom flycheck-error-list-minimum-level nil
"The minimum level of errors to display in the error list.
If set to an error level, only display errors whose error level
is at least as severe as this one in the error list. If nil,
display all errors.
This is the default level, used when the error list is opened.
You can temporarily change the level using
\\[flycheck-error-list-set-filter], or reset it to this value
using \\[flycheck-error-list-reset-filter]."
:group 'flycheck
:type 'flycheck-minimum-level
:safe #'flycheck-error-level-p
:package-version '(flycheck . "0.24"))
(defcustom flycheck-completing-read-function #'completing-read
"Function to read from minibuffer with completion.
The function must be compatible to the built-in `completing-read'
function."
:group 'flycheck
:type '(choice (const :tag "Default" completing-read)
(const :tag "IDO" ido-completing-read)
(function :tag "Custom function"))
:risky t
:package-version '(flycheck . "26"))
(defcustom flycheck-temp-prefix "flycheck"
"Prefix for temporary files created by Flycheck."
:group 'flycheck
:type 'string
:package-version '(flycheck . "0.19")
:risky t)
(defcustom flycheck-mode-hook nil
"Hooks to run after command `flycheck-mode' is toggled."
:group 'flycheck
:type 'hook
:risky t)
(defcustom flycheck-after-syntax-check-hook nil
"Functions to run after each syntax check.
This hook is run after a syntax check was finished.
At this point, *all* chained checkers were run, and all errors
were parsed, highlighted and reported. The variable
`flycheck-current-errors' contains all errors from all syntax
checkers run during the syntax check, so you can apply any error
analysis functions.
Note that this hook does *not* run after each individual syntax
checker in the syntax checker chain, but only after the *last
checker*.
This variable is a normal hook. See Info node `(elisp)Hooks'."
:group 'flycheck
:type 'hook
:risky t)
(defcustom flycheck-before-syntax-check-hook nil
"Functions to run before each syntax check.
This hook is run right before a syntax check starts.
Error information from the previous syntax check is *not*
cleared before this hook runs.
Note that this hook does *not* run before each individual syntax
checker in the syntax checker chain, but only before the *first
checker*.
This variable is a normal hook. See Info node `(elisp)Hooks'."
:group 'flycheck
:type 'hook
:risky t)
(defcustom flycheck-syntax-check-failed-hook nil
"Functions to run if a syntax check failed.
This hook is run whenever an error occurs during Flycheck's
internal processing. No information about the error is given to
this hook.
You should use this hook to conduct additional cleanup actions
when Flycheck failed.
This variable is a normal hook. See Info node `(elisp)Hooks'."
:group 'flycheck
:type 'hook
:risky t)
(defcustom flycheck-status-changed-functions nil
"Functions to run if the Flycheck status changed.
This hook is run whenever the status of Flycheck changes. Each
hook function takes the status symbol as single argument, as
given to `flycheck-report-status', which see.
This variable is a abnormal hook. See Info
node `(elisp)Hooks'."
:group 'flycheck
:type 'hook
:risky t
:package-version '(flycheck . "0.20"))
(defcustom flycheck-error-list-after-refresh-hook nil
"Functions to run after the error list was refreshed.
This hook is run whenever the error list is refreshed.
This variable is a normal hook. See Info node `(elisp)Hooks'."
:group 'flycheck
:type 'hook
:risky t
:package-version '(flycheck . "0.21"))
(defface flycheck-error
'((((supports :underline (:style wave)))
:underline (:style wave :color "Red1"))
(t
:underline t :inherit error))
"Flycheck face for errors."
:package-version '(flycheck . "0.13")
:group 'flycheck-faces)
(defface flycheck-warning
'((((supports :underline (:style wave)))
:underline (:style wave :color "DarkOrange"))
(t
:underline t :inherit warning))
"Flycheck face for warnings."
:package-version '(flycheck . "0.13")
:group 'flycheck-faces)
(defface flycheck-info
'((((supports :underline (:style wave)))
:underline (:style wave :color "ForestGreen"))
(t
:underline t :inherit success))
"Flycheck face for informational messages."
:package-version '(flycheck . "0.15")
:group 'flycheck-faces)
(defface flycheck-fringe-error
'((t :inherit error))
"Flycheck face for fringe error indicators."
:package-version '(flycheck . "0.13")
:group 'flycheck-faces)
(defface flycheck-fringe-warning
'((t :inherit warning))
"Flycheck face for fringe warning indicators."
:package-version '(flycheck . "0.13")
:group 'flycheck-faces)
(defface flycheck-fringe-info
;; Semantically `success' is probably not the right face, but it looks nice as
;; a base face
'((t :inherit success))
"Flycheck face for fringe info indicators."
:package-version '(flycheck . "0.15")
:group 'flycheck-faces)
(defface flycheck-error-list-error
'((t :inherit error))
"Flycheck face for error messages in the error list."
:package-version '(flycheck . "0.16")
:group 'flycheck-faces)
(defface flycheck-error-list-warning
'((t :inherit warning))
"Flycheck face for warning messages in the error list."
:package-version '(flycheck . "0.16")
:group 'flycheck-faces)
(defface flycheck-error-list-info
'((t :inherit success))
"Flycheck face for info messages in the error list."
:package-version '(flycheck . "0.16")
:group 'flycheck-faces)
;; The base faces for the following two faces are inspired by Compilation Mode
(defface flycheck-error-list-line-number
'((t :inherit font-lock-constant-face))
"Face for line numbers in the error list."
:group 'flycheck-faces
:package-version '(flycheck . "0.16"))
(defface flycheck-error-list-column-number
'((t :inherit font-lock-constant-face))
"Face for line numbers in the error list."
:group 'flycheck-faces
:package-version '(flycheck . "0.16"))
(defface flycheck-error-list-id
'((t :inherit font-lock-type-face))
"Face for the error ID in the error list."
:group 'flycheck-faces
:package-version '(flycheck . "0.22"))
(defface flycheck-error-list-id-with-explainer
'((t :inherit flycheck-error-list-id
:box (:style released-button)))
"Face for the error ID in the error list, for errors that have an explainer."
:group 'flycheck-faces
:package-version '(flycheck . "30"))
(defface flycheck-error-list-checker-name
'((t :inherit font-lock-function-name-face))
"Face for the syntax checker name in the error list."
:group 'flycheck-faces
:package-version '(flycheck . "0.21"))
(defface flycheck-error-list-highlight
'((t :inherit highlight))
"Flycheck face to highlight errors in the error list."
:package-version '(flycheck . "0.15")
:group 'flycheck-faces)
(defvar flycheck-command-map
(let ((map (make-sparse-keymap)))
(define-key map "c" #'flycheck-buffer)
(define-key map "C" #'flycheck-clear)
(define-key map (kbd "C-c") #'flycheck-compile)
(define-key map "n" #'flycheck-next-error)
(define-key map "p" #'flycheck-previous-error)
(define-key map "l" #'flycheck-list-errors)
(define-key map (kbd "C-w") #'flycheck-copy-errors-as-kill)
(define-key map "s" #'flycheck-select-checker)
(define-key map "?" #'flycheck-describe-checker)
(define-key map "h" #'flycheck-display-error-at-point)
(define-key map "e" #'flycheck-explain-error-at-point)
(define-key map "H" #'display-local-help)
(define-key map "i" #'flycheck-manual)
(define-key map "V" #'flycheck-version)
(define-key map "v" #'flycheck-verify-setup)
(define-key map "x" #'flycheck-disable-checker)
map)
"Keymap of Flycheck interactive commands.")
(defcustom flycheck-keymap-prefix (kbd "C-c !")
"Prefix for key bindings of Flycheck.
Changing this variable outside Customize does not have any
effect. To change the keymap prefix from Lisp, you need to
explicitly re-define the prefix key:
(define-key flycheck-mode-map flycheck-keymap-prefix nil)
(setq flycheck-keymap-prefix (kbd \"C-c f\"))
(define-key flycheck-mode-map flycheck-keymap-prefix
flycheck-command-map)
Please note that Flycheck's manual documents the default
keybindings. Changing this variable is at your own risk."
:group 'flycheck
:package-version '(flycheck . "0.19")
:type 'string
:risky t
:set
(lambda (variable key)
(when (and (boundp variable) (boundp 'flycheck-mode-map))
(define-key flycheck-mode-map (symbol-value variable) nil)
(define-key flycheck-mode-map key flycheck-command-map))
(set-default variable key)))
(defcustom flycheck-mode-line '(:eval (flycheck-mode-line-status-text))
"Mode line lighter for Flycheck.
The value of this variable is a mode line template as in
`mode-line-format'. See Info Node `(elisp)Mode Line Format' for
more information. Note that it should contain a _single_ mode
line construct only.
Customize this variable to change how Flycheck reports its status
in the mode line. You may use `flycheck-mode-line-status-text'
to obtain a human-readable status text, including an
error/warning count.
You may also assemble your own status text. The current status
of Flycheck is available in `flycheck-last-status-change'. The
errors in the current buffer are stored in
`flycheck-current-errors', and the function
`flycheck-count-errors' may be used to obtain the number of
errors grouped by error level.
Set this variable to nil to disable the mode line completely."
:group 'flycheck
:type 'sexp
:risky t
:package-version '(flycheck . "0.20"))
(defcustom flycheck-mode-line-prefix "FlyC"
"Base mode line lighter for Flycheck.
This will have an effect only with the default
`flycheck-mode-line'.
If you've customized `flycheck-mode-line' then the customized
function must be updated to use this variable."
:group 'flycheck
:type 'string
:package-version '(flycheck . "26"))
(defcustom flycheck-error-list-mode-line
`(,(propertized-buffer-identification "%12b")
" for buffer "
(:eval (flycheck-error-list-propertized-source-name))
(:eval (flycheck-error-list-mode-line-filter-indicator)))
"Mode line construct for Flycheck error list.
The value of this variable is a mode line template as in
`mode-line-format', to be used as
`mode-line-buffer-identification' in `flycheck-error-list-mode'.
See Info Node `(elisp)Mode Line Format' for more information.
Customize this variable to change how the error list appears in
the mode line. The default shows the name of the buffer and the
name of the source buffer, i.e. the buffer whose errors are
currently listed."
:group 'flycheck
:type 'sexp
:risky t
:package-version '(flycheck . "0.20"))
(defcustom flycheck-global-modes t
"Modes for which option `flycheck-mode' is turned on.
If t, Flycheck Mode is turned on for all major modes. If a list,
Flycheck Mode is turned on for all `major-mode' symbols in that
list. If the `car' of the list is `not', Flycheck Mode is turned
on for all `major-mode' symbols _not_ in that list. If nil,
Flycheck Mode is never turned on by command
`global-flycheck-mode'.
Note that Flycheck is never turned on for modes whose
`mode-class' property is `special' (see Info node `(elisp)Major
Mode Conventions'), regardless of the value of this option.
Only has effect when variable `global-flycheck-mode' is non-nil."
:group 'flycheck
:type '(choice (const :tag "none" nil)
(const :tag "all" t)
(set :menu-tag "mode specific" :tag "modes"
:value (not)
(const :tag "Except" not)
(repeat :inline t (symbol :tag "mode"))))
:risky t
:package-version '(flycheck . "0.23"))
;; Add built-in functions to our hooks, via `add-hook', to make sure that our
;; functions are really present, even if the variable was implicitly defined by
;; another call to `add-hook' that occurred before Flycheck was loaded. See
;; http://lists.gnu.org/archive/html/emacs-devel/2015-02/msg01271.html for why
;; we don't initialize the hook variables right away. We append our own
;; functions, because a user likely expects that their functions come first,
;; even if the added them before Flycheck was loaded.
(dolist (hook (list #'flycheck-locate-config-file-by-path
#'flycheck-locate-config-file-ancestor-directories
#'flycheck-locate-config-file-home))
(add-hook 'flycheck-locate-config-file-functions hook 'append))
(add-hook 'flycheck-process-error-functions #'flycheck-add-overlay 'append)
;;; Global Flycheck menu
(defvar flycheck-mode-menu-map
(easy-menu-create-menu
"Syntax Checking"
'(["Enable on-the-fly syntax checking" flycheck-mode
:style toggle :selected flycheck-mode
:enable (or flycheck-mode
;; Don't let users toggle the mode if there is no syntax
;; checker for this buffer
(seq-find #'flycheck-checker-supports-major-mode-p
flycheck-checkers))]
["Check current buffer" flycheck-buffer flycheck-mode]
["Clear errors in buffer" flycheck-clear t]
"---"
["Go to next error" flycheck-next-error flycheck-mode]
["Go to previous error" flycheck-previous-error flycheck-mode]
["Show all errors" flycheck-list-errors flycheck-mode]
"---"
["Copy messages at point" flycheck-copy-errors-as-kill
(flycheck-overlays-at (point))]
["Explain error at point" flycheck-explain-error-at-point]
"---"
["Select syntax checker" flycheck-select-checker flycheck-mode]
["Disable syntax checker" flycheck-disable-checker flycheck-mode]
["Set executable of syntax checker" flycheck-set-checker-executable
flycheck-mode]
"---"
["Describe syntax checker" flycheck-describe-checker t]
["Show Flycheck version" flycheck-version t]
["Read the Flycheck manual" flycheck-info t]))
"Menu of command `flycheck-mode'.")
(easy-menu-add-item nil '("Tools") flycheck-mode-menu-map "Spell Checking")
;;; Version information, manual and loading of Flycheck
(defun flycheck-version (&optional show-version)
"Get the Flycheck version as string.
If called interactively or if SHOW-VERSION is non-nil, show the
version in the echo area and the messages buffer.
The returned string includes both, the version from package.el
and the library version, if both a present and different.
If the version number could not be determined, signal an error,
if called interactively, or if SHOW-VERSION is non-nil, otherwise
just return nil."
(interactive (list t))
(let ((version (pkg-info-version-info 'flycheck)))
(when show-version
(message "Flycheck version: %s" version))
version))
(defun flycheck-unload-function ()
"Unload function for Flycheck."
(global-flycheck-mode -1)
(easy-menu-remove-item nil '("Tools") (cadr flycheck-mode-menu-map))
(remove-hook 'kill-emacs-hook #'flycheck-global-teardown)
(setq find-function-regexp-alist
(assq-delete-all 'flycheck-checker find-function-regexp-alist)))
;;;###autoload
(defun flycheck-manual ()
"Open the Flycheck manual."
(interactive)
(browse-url "http://www.flycheck.org"))
(define-obsolete-function-alias 'flycheck-info
'flycheck-manual "26" "Open the Flycheck manual.")
;;; Utility functions
(defun flycheck-sexp-to-string (sexp)
"Convert SEXP to a string.
Like `prin1-to-string' but ensure that the returned string
is loadable."
(let ((print-quoted t)
(print-length nil)
(print-level nil))
(prin1-to-string sexp)))
(defun flycheck-string-to-number-safe (string)
"Safely convert STRING to a number.
If STRING is of string type and a numeric string, convert STRING
to a number and return it. Otherwise return nil."
(let ((number-re (rx string-start (one-or-more (any digit)) string-end)))
(when (and (stringp string) (string-match-p number-re string))
(string-to-number string))))
(defun flycheck-string-list-p (obj)
"Determine if OBJ is a list of strings."
(and (listp obj) (seq-every-p #'stringp obj)))
(defun flycheck-symbol-list-p (obj)
"Determine if OBJ is a list of symbols."
(and (listp obj) (seq-every-p #'symbolp obj)))
(defun flycheck-same-files-p (file-a file-b)
"Determine whether FILE-A and FILE-B refer to the same file."
(let ((file-a (expand-file-name file-a))
(file-b (expand-file-name file-b)))
;; We must resolve symbolic links here, since some syntax checker always
;; output canonical file names with all symbolic links resolved. However,
;; we still do a simple path compassion first, to avoid the comparatively
;; expensive file system call if possible. See
;; https://github.com/flycheck/flycheck/issues/561
(or (string= (directory-file-name file-a) (directory-file-name file-b))
(string= (directory-file-name (file-truename file-a))
(directory-file-name (file-truename file-b))))))
(defvar-local flycheck-temporaries nil
"Temporary files and directories created by Flycheck.")
(defun flycheck-temp-dir-system ()
"Create a unique temporary directory.
Use `flycheck-temp-prefix' as prefix, and add the directory to
`flycheck-temporaries'.
Return the path of the directory"
(let* ((tempdir (make-temp-file flycheck-temp-prefix 'directory)))
(push tempdir flycheck-temporaries)
tempdir))
(defun flycheck-temp-file-system (filename)
"Create a temporary file named after FILENAME.
If FILENAME is non-nil, this function creates a temporary
directory with `flycheck-temp-dir-system', and creates a file
with the same name as FILENAME in this directory.
Otherwise this function creates a temporary file with
`flycheck-temp-prefix' and a random suffix. The path of the file
is added to `flycheck-temporaries'.
Add the path of the file to `flycheck-temporaries'.
Return the path of the file."
(let ((tempfile (convert-standard-filename
(if filename
(expand-file-name (file-name-nondirectory filename)
(flycheck-temp-dir-system))
(make-temp-file flycheck-temp-prefix)))))
(push tempfile flycheck-temporaries)
tempfile))
(defun flycheck-temp-file-inplace (filename)
"Create an in-place copy of FILENAME.
Prefix the file with `flycheck-temp-prefix' and add the path of
the file to `flycheck-temporaries'.
If FILENAME is nil, fall back to `flycheck-temp-file-system'.
Return the path of the file."
(if filename
(let* ((tempname (format "%s_%s"
flycheck-temp-prefix
(file-name-nondirectory filename)))
(tempfile (convert-standard-filename
(expand-file-name tempname
(file-name-directory filename)))))
(push tempfile flycheck-temporaries)
tempfile)
(flycheck-temp-file-system filename)))
(defun flycheck-save-buffer-to-file (file-name)
"Save the contents of the current buffer to FILE-NAME."
(make-directory (file-name-directory file-name) t)
(let ((jka-compr-inhibit t))
(write-region nil nil file-name nil 0)))
(defun flycheck-save-buffer-to-temp (temp-file-fn)
"Save buffer to temp file returned by TEMP-FILE-FN.
Return the name of the temporary file."
(let ((filename (funcall temp-file-fn (buffer-file-name))))
;; Do not flush short-lived temporary files onto disk
(let ((write-region-inhibit-fsync t))
(flycheck-save-buffer-to-file filename))
filename))
(defun flycheck-prepend-with-option (option items &optional prepend-fn)
"Prepend OPTION to each item in ITEMS, using PREPEND-FN.
Prepend OPTION to each item in ITEMS.
ITEMS is a list of strings to pass to the syntax checker. OPTION
is the option, as string. PREPEND-FN is a function called to
prepend OPTION to each item in ITEMS. It receives the option and
a single item from ITEMS as argument, and must return a string or
a list of strings with OPTION prepended to the item. If
PREPEND-FN is nil or omitted, use `list'.
Return a list of strings where OPTION is prepended to each item
in ITEMS using PREPEND-FN. If PREPEND-FN returns a list, it is
spliced into the resulting list."
(unless (stringp option)
(error "Option %S is not a string" option))
(unless prepend-fn
(setq prepend-fn #'list))
(let ((prepend
(lambda (item)
(let ((result (funcall prepend-fn option item)))
(cond
((and (listp result) (seq-every-p #'stringp result)) result)
((stringp result) (list result))
(t (error "Invalid result type for option: %S" result)))))))
(apply #'append (seq-map prepend items))))
(defun flycheck-find-in-buffer (pattern)
"Find PATTERN in the current buffer.
Return the result of the first matching group of PATTERN, or nil,
if PATTERN did not match."
(save-restriction
(widen)
(save-excursion
(goto-char (point-min))
(when (re-search-forward pattern nil 'no-error)
(match-string-no-properties 1)))))
(defun flycheck-buffer-empty-p (&optional buffer)
"Whether a BUFFER is empty.
If buffer is nil or omitted check the current buffer.
Return non-nil if so, or nil if the buffer has content."
(<= (buffer-size buffer) 0))
(defun flycheck-ephemeral-buffer-p ()
"Determine whether the current buffer is an ephemeral buffer.
See Info node `(elisp)Buffer Names' for information about
ephemeral buffers."
(string-prefix-p " " (buffer-name)))
(defun flycheck-encrypted-buffer-p ()
"Determine whether the current buffer is an encrypted file.
See Info node `(epa)Top' for Emacs' interface to encrypted
files."
;; The EPA file handler sets this variable locally to remember the recipients
;; of the encrypted file for re-encryption. Hence, a local binding of this
;; variable is a good indication that the buffer is encrypted. I haven't
;; found any better indicator anyway.
(local-variable-p 'epa-file-encrypt-to))
(defun flycheck-autoloads-file-p ()
"Determine whether the current buffer is a autoloads file.
Autoloads are generated by package.el during installation."
(string-suffix-p "-autoloads.el" (buffer-name)))
(defun flycheck-in-user-emacs-directory-p (filename)
"Whether FILENAME is in `user-emacs-directory'."
(string-prefix-p (file-name-as-directory (file-truename user-emacs-directory))
(file-truename filename)))
(defun flycheck-safe-delete (file-or-dir)
"Safely delete FILE-OR-DIR."
(ignore-errors
(if (file-directory-p file-or-dir)
(delete-directory file-or-dir 'recursive)
(delete-file file-or-dir))))
(defun flycheck-safe-delete-temporaries ()
"Safely delete all temp files and directories of Flycheck.
Safely delete all files and directories listed in
`flycheck-temporaries' and set the variable's value to nil."
(seq-do #'flycheck-safe-delete flycheck-temporaries)
(setq flycheck-temporaries nil))
(defun flycheck-rx-file-name (form)
"Translate the `(file-name)' FORM into a regular expression."
(let ((body (or (cdr form) '((minimal-match
(one-or-more not-newline))))))
(rx-submatch-n `(group-n 1 ,@body))))
(defun flycheck-rx-message (form)
"Translate the `(message)' FORM into a regular expression."
(let ((body (or (cdr form) '((one-or-more not-newline)))))
(rx-submatch-n `(group-n 4 ,@body))))
(defun flycheck-rx-id (form)
"Translate the `(id)' FORM into a regular expression."
(rx-submatch-n `(group-n 5 ,@(cdr form))))
(defun flycheck-rx-to-string (form &optional no-group)
"Like `rx-to-string' for FORM, but with special keywords:
`line'
matches the line number.
`column'
matches the column number.
`(file-name SEXP ...)'
matches the file name. SEXP describes the file name. If no
SEXP is given, use a default body of `(minimal-match
(one-or-more not-newline))'.
`(message SEXP ...)'
matches the message. SEXP constitutes the body of the
message. If no SEXP is given, use a default body
of `(one-or-more not-newline)'.
`(id SEXP ...)'
matches an error ID. SEXP describes the ID.
NO-GROUP is passed to `rx-to-string'.
See `rx' for a complete list of all built-in `rx' forms."
(let ((rx-constituents
(append
`((line . ,(rx (group-n 2 (one-or-more digit))))
(column . ,(rx (group-n 3 (one-or-more digit))))
(file-name flycheck-rx-file-name 0 nil)
(message flycheck-rx-message 0 nil)
(id flycheck-rx-id 0 nil))
rx-constituents nil)))
(rx-to-string form no-group)))
(defun flycheck-current-load-file ()
"Get the source file currently being loaded.
Always return the name of the corresponding source file, never
any byte-compiled file.
Return nil, if the currently loaded file cannot be determined."
(-when-let* ((this-file (cond
(load-in-progress load-file-name)
((bound-and-true-p byte-compile-current-file))
(t (buffer-file-name))))
;; A best guess for the source file of a compiled library. Works
;; well in most cases, and especially for ELPA packages
(source-file (concat (file-name-sans-extension this-file)
".el")))
(when (file-exists-p source-file)
source-file)))
(defun flycheck-module-root-directory (module &optional file-name)
"Get the root directory for a MODULE in FILE-NAME.
MODULE is a qualified module name, either a string with
components separated by a dot, or as list of components.
FILE-NAME is the name of the file or directory containing the
module as string. When nil or omitted, defaults to the return
value of function `buffer-file-name'.
Return the root directory of the module, that is, the directory,
from which FILE-NAME can be reached by descending directories
along each part of MODULE.
If the MODULE name does not match the directory hierarchy upwards
from FILE-NAME, return the directory containing FILE-NAME. When
FILE-NAME is nil, return `default-directory'."
(let ((file-name (or file-name (buffer-file-name)))
(module-components (if (stringp module)
(split-string module (rx "."))
(copy-sequence module))))
(if (and module-components file-name)
(let ((parts (nreverse module-components))
(base-directory (directory-file-name
(file-name-sans-extension file-name))))
(while (and parts
(string= (file-name-nondirectory base-directory)
(car parts)))
(pop parts)
(setq base-directory (directory-file-name
(file-name-directory base-directory))))
(file-name-as-directory base-directory))
(if file-name
(file-name-directory file-name)
(expand-file-name default-directory)))))
;;; Minibuffer tools
(defvar read-flycheck-checker-history nil
"`completing-read' history of `read-flycheck-checker'.")
(defun flycheck-completing-read (prompt candidates default &optional history)
"Read a value from the minibuffer.
Use `flycheck-completing-read-function' to read input from the
minibuffer with completion.
Show PROMPT and read one of CANDIDATES, defaulting to DEFAULT.
HISTORY is passed to `flycheck-completing-read-function'."
(funcall flycheck-completing-read-function
prompt candidates nil 'require-match nil history default))
(defun read-flycheck-checker (prompt &optional default property candidates)
"Read a flycheck checker from minibuffer with PROMPT and DEFAULT.
PROMPT is a string to show in the minibuffer as prompt. It
should end with a single space. DEFAULT is a symbol denoting the
default checker to use, if the user did not select any checker.
PROPERTY is a symbol denoting a syntax checker property. If
non-nil, only complete syntax checkers which have a non-nil value
for PROPERTY. CANDIDATES is an optional list of all syntax
checkers available for completion, defaulting to all defined
checkers. If given, PROPERTY is ignored.
Return the checker as symbol, or DEFAULT if no checker was
chosen. If DEFAULT is nil and no checker was chosen, signal a
`user-error' if the underlying completion system does not provide
a default on its own."
(when (and default (not (flycheck-valid-checker-p default)))
(error "%S is no valid Flycheck checker" default))
(let* ((candidates (seq-map #'symbol-name
(or candidates
(flycheck-defined-checkers property))))
(default (and default (symbol-name default)))
(input (flycheck-completing-read
prompt candidates default
'read-flycheck-checker-history)))
(when (string-empty-p input)
(unless default
(user-error "No syntax checker selected"))
(setq input default))
(let ((checker (intern input)))
(unless (flycheck-valid-checker-p checker)
(error "%S is not a valid Flycheck syntax checker" checker))
checker)))
(defun read-flycheck-error-level (prompt)
"Read an error level from the user with PROMPT.
Only offers level for which errors currently exist, in addition
to the default levels."
(let* ((levels (seq-map #'flycheck-error-level
(flycheck-error-list-current-errors)))
(levels-with-defaults (append '(info warning error) levels))
(uniq-levels (seq-uniq levels-with-defaults))
(level (flycheck-completing-read prompt uniq-levels nil)))
(and (stringp level) (intern level))))
;;; Checker API
(defun flycheck-defined-checkers (&optional property)
"Find all defined syntax checkers, optionally with PROPERTY.
PROPERTY is a symbol. If given, only return syntax checkers with
a non-nil value for PROPERTY.
The returned list is sorted alphapetically by the symbol name of
the syntax checkers."
(let (defined-checkers)
(mapatoms (lambda (symbol)
(when (and (flycheck-valid-checker-p symbol)
(or (null property)
(flycheck-checker-get symbol property)))
(push symbol defined-checkers))))
(sort defined-checkers #'string<)))
(defun flycheck-registered-checker-p (checker)
"Determine whether CHECKER is registered.
A checker is registered if it is contained in
`flycheck-checkers'."
(and (flycheck-valid-checker-p checker)
(memq checker flycheck-checkers)))
(defun flycheck-disabled-checker-p (checker)
"Determine whether CHECKER is disabled.
A checker is disabled if it is contained in
`flycheck-disabled-checkers'."
(memq checker flycheck-disabled-checkers))
;;; Generic syntax checkers
(defconst flycheck-generic-checker-version 2
"The internal version of generic syntax checker declarations.
Flycheck will not use syntax checkers whose generic version is
less than this constant.")
(defsubst flycheck--checker-property-name (property)
"Return the SYMBOL property for checker PROPERTY."
(intern (concat "flycheck-" (symbol-name property))))
(defun flycheck-checker-get (checker property)
"Get the value of CHECKER's PROPERTY."
(get checker (flycheck--checker-property-name property)))
(gv-define-setter flycheck-checker-get (value checker property)
`(setf (get ,checker (flycheck--checker-property-name ,property)) ,value))
(defun flycheck-validate-next-checker (next &optional strict)
"Validate NEXT checker.
With STRICT non-nil, also check whether the syntax checker and
the error level in NEXT are valid. Otherwise just check whether
these are symbols.
Signal an error if NEXT is not a valid entry for
`:next-checkers'."
(when (symbolp next)
(setq next (cons t next)))
(pcase next
(`(,level . ,checker)
(if strict
(progn
(unless (or (eq level t) (flycheck-error-level-p level))
(error "%S is not a valid Flycheck error level" level))
(unless (flycheck-valid-checker-p checker)
(error "%s is not a valid Flycheck syntax checker" checker)))
(unless (symbolp level)
(error "Error level %S must be a symbol" level))
(unless (symbolp checker)
(error "Checker %S must be a symbol" checker))))
(_ (error "%S must be a symbol or cons cell" next)))
t)
(defun flycheck-define-generic-checker (symbol docstring &rest properties)
"Define SYMBOL as generic syntax checker.
Any syntax checker defined with this macro is eligible for manual
syntax checker selection with `flycheck-select-checker'. To make
the new syntax checker available for automatic selection, it must
be registered in `flycheck-checkers'.
DOCSTRING is the documentation of the syntax checker, for
`flycheck-describe-checker'. The following PROPERTIES constitute
a generic syntax checker. Unless otherwise noted, all properties
are mandatory.
`:start FUNCTION'
A function to start the syntax checker.
FUNCTION shall take two arguments and return a context
object if the checker is started successfully. Otherwise it
shall signal an error.
The first argument is the syntax checker being started. The
second is a callback function to report state changes to
Flycheck. The callback takes two arguments STATUS DATA,
where STATUS is a symbol denoting the syntax checker status
and DATA an optional argument with additional data for the
status report. See `flycheck-report-buffer-checker-status'
for more information about STATUS and DATA.
FUNCTION may be synchronous or asynchronous, i.e. it may
call the given callback either immediately, or at some later
point (e.g. from a process sentinel).
A syntax checker _must_ call CALLBACK at least once with a
STATUS that finishes the current syntax checker. Otherwise
Flycheck gets stuck at the current syntax check with this
syntax checker.
The context object returned by FUNCTION is passed to
`:interrupt'.
`:interrupt FUNCTION'
A function to interrupt the syntax check.
FUNCTION is called with the syntax checker and the context
object returned by the `:start' function and shall try to
interrupt the syntax check. The context may be nil, if the
syntax check is interrupted before actually started.
FUNCTION should handle this situation.
If it cannot interrupt the syntax check, it may either
signal an error or silently ignore the attempt to interrupt
the syntax checker, depending on the severity of the
situation.
If interrupting the syntax check failed, Flycheck will let
the syntax check continue, but ignore any status reports.
Notably, it won't highlight any errors reported by the
syntax check in the buffer.
This property is optional. If omitted, Flycheck won't
attempt to interrupt syntax checks wit this syntax checker,
and simply ignore their results.
`:print-doc FUNCTION'
A function to print additional documentation into the Help
buffer of this checker.
FUNCTION is called when creating the Help buffer for the
syntax checker, with the syntax checker as single argument,
after printing the name of the syntax checker and its modes
and predicate, but before printing DOCSTRING. It may insert
additional documentation into the current buffer.
The call occurs within `with-help-window'. Hence
`standard-output' points to the current buffer, so you may
use `princ' and friends to add content. Also, the current
buffer is put into Help mode afterwards, which automatically
turns symbols into references, if possible.
This property is optional. If omitted, no additional
documentation is printed for this syntax checker.
:verify FUNCTION
A function to verify the checker for the current buffer.
FUNCTION is called with the syntax checker as single
argument, and shall return a list of
`flycheck-verification-result' objects indicating whether
the syntax checker could be used in the current buffer, and
highlighting potential setup problems.
This property is optional. If omitted, no additional
verification occurs for this syntax checker. It is however
absolutely recommended that you add a `:verify' function to
your syntax checker, because it will help users to spot
potential setup problems.
`:modes MODES'
A major mode symbol or a list thereof, denoting major modes
to use this syntax checker in.
This syntax checker will only be used in buffers whose
`major-mode' is contained in MODES.
If `:predicate' is also given the syntax checker will only
be used in buffers for which the `:predicate' returns
non-nil.
`:predicate FUNCTION'
A function to determine whether to use the syntax checker in
the current buffer.
FUNCTION is called without arguments and shall return
non-nil if this syntax checker shall be used to check the
current buffer. Otherwise it shall return nil.
If this checker has a `:working-directory' FUNCTION is
called with `default-directory' bound to the checker's
working directory.
FUNCTION is only called in matching major modes.
This property is optional.
`:enabled FUNCTION'
A function to determine whether to use the syntax checker in
the current buffer.
This property behaves as `:predicate', except that it's only
called the first time a syntax checker is to be used in a buffer.
FUNCTION is called without arguments and shall return
non-nil if this syntax checker shall be used to check the
current buffer. Otherwise it shall return nil.
If FUNCTION returns a non-nil value the checker is put in a
whitelist in `flycheck-enabled-checkers' to prevent further
invocations of `:enabled'. Otherwise it is disabled via
`flycheck-disabled-checkers' to prevent any further use of
it.
If this checker has a `:working-directory' FUNCTION is
called with `default-directory' bound to the checker's
working directory.
FUNCTION is only called in matching major modes.
This property is optional.
`:error-filter FUNCTION'
A function to filter the errors returned by this checker.
FUNCTION is called with the list of `flycheck-error' objects
returned by the syntax checker and shall return another list
of `flycheck-error' objects, which is considered the final
result of this syntax checker.
FUNCTION is free to add, remove or modify errors, whether in
place or by copying.
This property is optional. The default filter is
`identity'.
`:error-explainer FUNCTION'
A function to return an explanation text for errors
generated by this checker.
FUNCTION is called with a `flycheck-error' object and shall
return an explanation message for this error as a string, or
nil if there is no explanation for this error.
This property is optional.
`:next-checkers NEXT-CHECKERS'
A list denoting syntax checkers to apply after this syntax
checker, in what we call \"chaining\" of syntax checkers.
Each ITEM is a cons cell `(LEVEL . CHECKER)'. CHECKER is a
syntax checker to run after this syntax checker. LEVEL is
an error level. CHECKER will only be used if there are no
current errors of at least LEVEL. LEVEL may also be t, in
which case CHECKER is used regardless of the current errors.
ITEM may also be a syntax checker symbol, which is
equivalent to `(t . ITEM)'.
Flycheck tries all items in order of declaration, and uses
the first whose LEVEL matches and whose CHECKER is
registered and can be used for the current buffer.
This feature is typically used to apply more than one syntax
checker to a buffer. For instance, you might first use a
compiler to check a buffer for syntax and type errors, and
then run a linting tool that checks for insecure code, or
questionable style.
This property is optional. If omitted, it defaults to the
nil, i.e. no other syntax checkers are applied after this
syntax checker.
`:working-directory FUNCTION'
The value of `default-directory' when invoking `:start'.
FUNCTION is a function taking the syntax checker as sole
argument. It shall return the absolute path to an existing
directory to use as `default-directory' for `:start' or
nil to fall back to the `default-directory' of the current
buffer.
This property is optional. If omitted invoke `:start'
from the `default-directory' of the buffer being checked.
Signal an error, if any property has an invalid value."
(declare (indent 1)
(doc-string 2))
(let ((start (plist-get properties :start))
(interrupt (plist-get properties :interrupt))
(print-doc (plist-get properties :print-doc))
(modes (plist-get properties :modes))
(predicate (plist-get properties :predicate))
(verify (plist-get properties :verify))
(enabled (plist-get properties :enabled))
(filter (or (plist-get properties :error-filter) #'identity))
(explainer (plist-get properties :error-explainer))
(next-checkers (plist-get properties :next-checkers))
(file (flycheck-current-load-file))
(working-directory (plist-get properties :working-directory)))
(unless (listp modes)
(setq modes (list modes)))
(unless (functionp start)
(error ":start %S of syntax checker %s is not a function" start symbol))
(unless (or (null interrupt) (functionp interrupt))
(error ":interrupt %S of syntax checker %s is not a function"
interrupt symbol))
(unless (or (null print-doc) (functionp print-doc))
(error ":print-doc %S of syntax checker %s is not a function"
print-doc symbol))
(unless (or (null verify) (functionp verify))
(error ":verify %S of syntax checker %S is not a function"
verify symbol))
(unless (or (null enabled) (functionp enabled))
(error ":enabled %S of syntax checker %S is not a function"
enabled symbol))
(unless modes
(error "Missing :modes in syntax checker %s" symbol))
(dolist (mode modes)
(unless (symbolp mode)
(error "Invalid :modes %s in syntax checker %s, %s must be a symbol"
modes symbol mode)))
(unless (or (null predicate) (functionp predicate))
(error ":predicate %S of syntax checker %s is not a function"
predicate symbol))
(unless (functionp filter)
(error ":error-filter %S of syntax checker %s is not a function"
filter symbol))
(unless (or (null explainer) (functionp explainer))
(error ":error-explainer %S of syntax checker %S is not a function"
explainer symbol))
(dolist (checker next-checkers)
(flycheck-validate-next-checker checker))
(let ((real-predicate
(and predicate
(lambda ()
;; Run predicate in the checker's default directory
(let ((default-directory
(flycheck-compute-working-directory symbol)))
(funcall predicate)))))
(real-enabled
(lambda ()
(if (flycheck-valid-checker-p symbol)
(or (null enabled)
;; Run enabled in the checker's default directory
(let ((default-directory
(flycheck-compute-working-directory symbol)))
(funcall enabled)))
(lwarn 'flycheck :warning "%S is no valid Flycheck syntax checker.
Try to reinstall the package defining this syntax checker." symbol)
nil))))
(pcase-dolist (`(,prop . ,value)
`((start . ,start)
(interrupt . ,interrupt)
(print-doc . ,print-doc)
(modes . ,modes)
(predicate . ,real-predicate)
(verify . ,verify)
(enabled . ,real-enabled)
(error-filter . ,filter)
(error-explainer . ,explainer)
(next-checkers . ,next-checkers)
(documentation . ,docstring)
(file . ,file)
(working-directory . ,working-directory)))
(setf (flycheck-checker-get symbol prop) value)))
;; Track the version, to avoid breakage if the internal format changes
(setf (flycheck-checker-get symbol 'generic-checker-version)
flycheck-generic-checker-version)))
(defun flycheck-valid-checker-p (checker)
"Check whether a CHECKER is valid.
A valid checker is a symbol defined as syntax checker with
`flycheck-define-checker'."
(and (symbolp checker)
(= (or (get checker 'flycheck-generic-checker-version) 0)
flycheck-generic-checker-version)))
(defun flycheck-checker-supports-major-mode-p (checker &optional mode)
"Whether CHECKER supports the given major MODE.
CHECKER is a syntax checker symbol and MODE a major mode symbol.
Look at the `modes' property of CHECKER to determine whether
CHECKER supports buffers in the given major MODE.
MODE defaults to the value of `major-mode' if omitted or nil.
Return non-nil if CHECKER supports MODE and nil otherwise."
(let ((mode (or mode major-mode)))
(memq mode (flycheck-checker-get checker 'modes))))
(defvar-local flycheck-enabled-checkers nil
"Syntax checkers included in automatic selection.
A list of Flycheck syntax checkers included in automatic
selection for current buffer.")
(defun flycheck-may-enable-checker (checker)
"Whether a generic CHECKER may be enabled for current buffer.
Return non-nil if CHECKER may be used for the current buffer, and
nil otherwise."
(let* ((enabled (flycheck-checker-get checker 'enabled))
(shall-enable (and (not (flycheck-disabled-checker-p checker))
(or (memq checker flycheck-enabled-checkers)
(null enabled)
(funcall enabled)))))
(if shall-enable
(cl-pushnew checker flycheck-enabled-checkers)
(cl-pushnew checker flycheck-disabled-checkers))
shall-enable))
(defun flycheck-may-use-checker (checker)
"Whether a generic CHECKER may be used.
Return non-nil if CHECKER may be used for the current buffer, and
nil otherwise."
(let ((predicate (flycheck-checker-get checker 'predicate)))
(and (flycheck-valid-checker-p checker)
(flycheck-checker-supports-major-mode-p checker)
(flycheck-may-enable-checker checker)
(or (null predicate) (funcall predicate)))))
(defun flycheck-may-use-next-checker (next-checker)
"Determine whether NEXT-CHECKER may be used."
(when (symbolp next-checker)
(push t next-checker))
(let ((level (car next-checker))
(next-checker (cdr next-checker)))
(and (or (eq level t)
(flycheck-has-max-current-errors-p level))
(flycheck-registered-checker-p next-checker)
(flycheck-may-use-checker next-checker))))
;;; Help for generic syntax checkers
(define-button-type 'help-flycheck-checker-def
:supertype 'help-xref
'help-function #'flycheck-goto-checker-definition
'help-echo "mouse-2, RET: find Flycheck checker definition")
(defconst flycheck-find-checker-regexp
(rx line-start (zero-or-more (syntax whitespace))
"(" symbol-start "flycheck-define-checker" symbol-end
(eval (list 'regexp find-function-space-re))
symbol-start
"%s"
symbol-end
(or (syntax whitespace) line-end))
"Regular expression to find a checker definition.")
(add-to-list 'find-function-regexp-alist
'(flycheck-checker . flycheck-find-checker-regexp))
(defun flycheck-goto-checker-definition (checker file)
"Go to to the definition of CHECKER in FILE."
(let ((location (find-function-search-for-symbol
checker 'flycheck-checker file)))
(pop-to-buffer (car location))
(if (cdr location)
(goto-char (cdr location))
(message "Unable to find checker location in file"))))
(defun flycheck-checker-at-point ()
"Return the Flycheck checker found at or before point.
Return nil if there is no checker."
(let ((symbol (variable-at-point 'any-symbol)))
(when (flycheck-valid-checker-p symbol)
symbol)))
(defun flycheck-describe-checker (checker)
"Display the documentation of CHECKER.
CHECKER is a checker symbol.
Pop up a help buffer with the documentation of CHECKER."
(interactive
(let* ((enable-recursive-minibuffers t)
(default (or (flycheck-checker-at-point)
(ignore-errors (flycheck-get-checker-for-buffer))))
(prompt (if default
(format "Describe syntax checker (default %s): " default)
"Describe syntax checker: ")))
(list (read-flycheck-checker prompt default))))
(unless (flycheck-valid-checker-p checker)
(user-error "You didn't specify a Flycheck syntax checker"))
(help-setup-xref (list #'flycheck-describe-checker checker)
(called-interactively-p 'interactive))
(save-excursion
(with-help-window (help-buffer)
(let ((filename (flycheck-checker-get checker 'file))
(modes (flycheck-checker-get checker 'modes))
(predicate (flycheck-checker-get checker 'predicate))
(print-doc (flycheck-checker-get checker 'print-doc))
(next-checkers (flycheck-checker-get checker 'next-checkers)))
(princ (format "%s is a Flycheck syntax checker" checker))
(when filename
(princ (format " in `%s'" (file-name-nondirectory filename)))
(with-current-buffer standard-output
(save-excursion
(re-search-backward "`\\([^`']+\\)'" nil t)
(help-xref-button 1 'help-flycheck-checker-def checker filename))))
(princ ".\n\n")
(let ((modes-start (with-current-buffer standard-output (point-max))))
;; Track the start of the modes documentation, to properly re-fill
;; it later
(princ " This syntax checker checks syntax in the major mode(s) ")
(princ (string-join
(seq-map (apply-partially #'format "`%s'") modes)
", "))
(when predicate
(princ ", and uses a custom predicate"))
(princ ".")
(when next-checkers
(princ " It runs the following checkers afterwards:"))
(with-current-buffer standard-output
(save-excursion
(fill-region-as-paragraph modes-start (point-max))))
(princ "\n")
;; Print the list of next checkers
(when next-checkers
(princ "\n")
(let ((beg-checker-list (with-current-buffer standard-output
(point))))
(dolist (next-checker next-checkers)
(if (symbolp next-checker)
(princ (format " * `%s'\n" next-checker))
(princ (format " * `%s' (maximum level `%s')\n"
(cdr next-checker) (car next-checker)))))
;;
(with-current-buffer standard-output
(save-excursion
(while (re-search-backward "`\\([^`']+\\)'"
beg-checker-list t)
(when (flycheck-valid-checker-p
(intern-soft (match-string 1)))
(help-xref-button 1 'help-flycheck-checker-def checker
filename))))))))
;; Call the custom print-doc function of the checker, if present
(when print-doc
(funcall print-doc checker))
;; Ultimately, print the docstring
(princ "\nDocumentation:\n")
(princ (flycheck-checker-get checker 'documentation))))))
;;; Syntax checker verification
(cl-defstruct (flycheck-verification-result
(:constructor flycheck-verification-result-new))
"Structure for storing a single verification result.
Slots:
`label'
A label for this result, as string
`message'
A message for this result, as string
`face'
The face to use for the `message'.
You can either use a face symbol, or a list of face symbols."
label message face)
(defun flycheck-verify-generic-checker (checker)
"Verify a generic CHECKER in the current buffer.
Return a list of `flycheck-verification-result' objects."
(let (results
(predicate (flycheck-checker-get checker 'predicate))
(enabled (flycheck-checker-get checker 'enabled))
(verify (flycheck-checker-get checker 'verify)))
(when enabled
(let ((result (funcall enabled)))
(push (flycheck-verification-result-new
:label "may enable"
:message (if result "yes" "Automatically disabled!")
:face (if result 'success '(bold warning)))
results)))
(when predicate
(let ((result (funcall predicate)))
(push (flycheck-verification-result-new
:label "predicate"
:message (prin1-to-string (not (null result)))
:face (if result 'success '(bold warning)))
results)))
(append (nreverse results)
(and verify (funcall verify checker)))))
(define-button-type 'help-flycheck-checker-doc
:supertype 'help-xref
'help-function #'flycheck-describe-checker
'help-echo "mouse-2, RET: describe Flycheck checker")
(defun flycheck--verify-princ-checker (checker buffer &optional with-mm)
"Print verification result of CHECKER for BUFFER.
When WITH-MM is given and non-nil, also include the major mode
into the verification results."
(princ " ")
(insert-button (symbol-name checker)
'type 'help-flycheck-checker-doc
'help-args (list checker))
(when (with-current-buffer buffer (flycheck-disabled-checker-p checker))
(insert (propertize " (disabled)" 'face '(bold error))))
(princ "\n")
(let ((results (with-current-buffer buffer
(flycheck-verify-generic-checker checker))))
(when with-mm
(with-current-buffer buffer
(let ((message-and-face
(if (flycheck-checker-supports-major-mode-p checker)
(cons (format "`%s' supported" major-mode) 'success)
(cons (format "`%s' not supported" major-mode) 'error))))
(push (flycheck-verification-result-new
:label "major mode"
:message (car message-and-face)
:face (cdr message-and-face))
results))))
(let* ((label-length
(seq-max (mapcar
(lambda (res)
(length (flycheck-verification-result-label res)))
results)))
(message-column (+ 8 label-length)))
(dolist (result results)
(princ " - ")
(princ (flycheck-verification-result-label result))
(princ ": ")
(princ (make-string (- message-column (current-column)) ?\ ))
(let ((message (flycheck-verification-result-message result))
(face (flycheck-verification-result-face result)))
(insert (propertize message 'face face)))
(princ "\n"))))
(princ "\n"))
(defun flycheck--verify-print-header (desc buffer)
"Print a title with DESC for BUFFER in the current buffer.
DESC is an arbitrary string containing a description, and BUFFER
is the buffer being verified. The name and the major mode mode
of BUFFER are printed.
DESC and information about BUFFER are printed in the current
buffer."
(princ desc)
(insert (propertize (buffer-name buffer) 'face 'bold))
(princ " in ")
(let ((mode (buffer-local-value 'major-mode buffer)))
(insert-button (symbol-name mode)
'type 'help-function
'help-args (list mode)))
(princ ":\n\n"))
(defun flycheck--verify-print-footer (buffer)
"Print a footer for BUFFER in the current buffer.
BUFFER is the buffer being verified."
(princ "Flycheck Mode is ")
(let ((enabled (buffer-local-value 'flycheck-mode buffer)))
(insert (propertize (if enabled "enabled" "disabled")
'face (if enabled 'success '(warning bold)))))
(princ
(with-current-buffer buffer
;; Use key binding state in the verified buffer to print the help.
(substitute-command-keys
". Use \\[universal-argument] \\[flycheck-disable-checker] to enable disabled checkers.")))
(save-excursion
(let ((end (point)))
(backward-paragraph)
(fill-region-as-paragraph (point) end)))
(princ "\n\n--------------------\n\n")
(princ (format "Flycheck version: %s\n" (flycheck-version)))
(princ (format "Emacs version: %s\n" emacs-version))
(princ (format "System: %s\n" system-configuration))
(princ (format "Window system: %S\n" window-system)))
(defun flycheck-verify-checker (checker)
"Check whether a CHECKER can be used in this buffer.
Show a buffer listing possible problems that prevent CHECKER from
being used for the current buffer.
Note: Do not use this function to check whether a syntax checker
is applicable from Emacs Lisp code. Use
`flycheck-may-use-checker' instead."
(interactive (list (read-flycheck-checker "Checker to verify: ")))
(unless (flycheck-valid-checker-p checker)
(user-error "%s is not a syntax checker" checker))
;; Save the buffer to make sure that all predicates are good
(when (and (buffer-file-name) (buffer-modified-p))
(save-buffer))
(let ((buffer (current-buffer)))
(with-help-window (get-buffer-create " *Flycheck checker*")
(with-current-buffer standard-output
(flycheck--verify-print-header "Syntax checker in buffer " buffer)
(flycheck--verify-princ-checker checker buffer 'with-mm)
(if (with-current-buffer buffer (flycheck-may-use-checker checker))
(insert (propertize "Flycheck can use this syntax checker for this buffer.\n"
'face 'success))
(insert (propertize "Flycheck cannot use this syntax checker for this buffer.\n"
'face 'error)))
(insert "\n")
(flycheck--verify-print-footer buffer)))))
(defun flycheck-verify-setup ()
"Check whether Flycheck can be used in this buffer.
Display a new buffer listing all syntax checkers that could be
applicable in the current buffer. For each syntax checkers,
possible problems are shown."
(interactive)
(when (and (buffer-file-name) (buffer-modified-p))
;; Save the buffer
(save-buffer))
(let ((buffer (current-buffer))
;; Get all checkers that support the current major mode
(checkers (seq-filter #'flycheck-checker-supports-major-mode-p
flycheck-checkers))
(help-buffer (get-buffer-create " *Flycheck checkers*")))
;; Now print all applicable checkers
(with-help-window help-buffer
(with-current-buffer standard-output
(flycheck--verify-print-header "Syntax checkers for buffer " buffer)
(unless checkers
(insert (propertize "There are no syntax checkers for this buffer!\n\n"
'face '(bold error))))
(dolist (checker checkers)
(flycheck--verify-princ-checker checker buffer))
(-when-let (selected-checker (buffer-local-value 'flycheck-checker buffer))
(insert (propertize "The following checker is explicitly selected for this buffer:\n\n"
'face 'bold))
(flycheck--verify-princ-checker selected-checker buffer 'with-mm))
(let ((unregistered-checkers (seq-difference (flycheck-defined-checkers)
flycheck-checkers)))
(when unregistered-checkers
(insert (propertize "\nThe following syntax checkers are not registered:\n\n"
'face '(bold warning)))
(dolist (checker unregistered-checkers)
(princ " - ")
(princ checker)
(princ "\n"))
(princ "\nTry adding these syntax checkers to `flycheck-checkers'.\n")))
(flycheck--verify-print-footer buffer)))
(with-current-buffer help-buffer
(setq-local revert-buffer-function
(lambda (_ignore-auto _noconfirm)
(with-current-buffer buffer (flycheck-verify-setup)))))))
;;; Predicates for generic syntax checkers
(defun flycheck-buffer-saved-p (&optional buffer)
"Determine whether BUFFER is saved to a file.
BUFFER is the buffer to check. If omitted or nil, use the
current buffer as BUFFER.
Return non-nil if the BUFFER is backed by a file, and not
modified, or nil otherwise."
(let ((file-name (buffer-file-name buffer)))
(and file-name (file-exists-p file-name) (not (buffer-modified-p buffer)))))
;;; Extending generic checkers
(defun flycheck-add-next-checker (checker next &optional append)
"After CHECKER add a NEXT checker.
CHECKER is a syntax checker symbol, to which to add NEXT checker.
NEXT is a cons cell `(LEVEL . NEXT-CHECKER)'. NEXT-CHECKER is a
symbol denoting the syntax checker to run after CHECKER. LEVEL
is an error level. NEXT-CHECKER will only be used if there is no
current error whose level is more severe than LEVEL. LEVEL may
also be t, in which case NEXT-CHECKER is used regardless of the
current errors.
NEXT can also be a syntax checker symbol only, which is
equivalent to `(t . NEXT)'.
NEXT-CHECKER is prepended before other next checkers, unless
APPEND is non-nil."
(unless (flycheck-valid-checker-p checker)
(error "%s is not a valid syntax checker" checker))
(flycheck-validate-next-checker next 'strict)
(if append
(setf (flycheck-checker-get checker 'next-checkers)
(append (flycheck-checker-get checker 'next-checkers) (list next)))
(push next (flycheck-checker-get checker 'next-checkers))))
(defun flycheck-add-mode (checker mode)
"To CHECKER add a new major MODE.
CHECKER and MODE are symbols denoting a syntax checker and a
major mode respectively.
Add MODE to the `:modes' property of CHECKER, so that CHECKER
will be used in buffers with MODE."
(unless (flycheck-valid-checker-p checker)
(error "%s is not a valid syntax checker" checker))
(unless (symbolp mode)
(error "%s is not a symbol" mode))
(push mode (flycheck-checker-get checker 'modes)))
;;; Generic syntax checks
(cl-defstruct (flycheck-syntax-check
(:constructor flycheck-syntax-check-new))
"Structure for storing syntax check state.
Slots:
`buffer'
The buffer being checked.
`checker'
The syntax checker being used.
`context'
The context object.
`working-directory'
Working directory for the syntax checker. Serve as a value for
`default-directory' for a checker."
buffer checker context working-directory)
(defun flycheck-syntax-check-start (syntax-check callback)
"Start a SYNTAX-CHECK with CALLBACK."
(let ((checker (flycheck-syntax-check-checker syntax-check))
(default-directory (flycheck-syntax-check-working-directory syntax-check)))
(setf (flycheck-syntax-check-context syntax-check)
(funcall (flycheck-checker-get checker 'start) checker callback))))
(defun flycheck-syntax-check-interrupt (syntax-check)
"Interrupt a SYNTAX-CHECK."
(let* ((checker (flycheck-syntax-check-checker syntax-check))
(interrupt-fn (flycheck-checker-get checker 'interrupt))
(context (flycheck-syntax-check-context syntax-check)))
(when interrupt-fn
(funcall interrupt-fn checker context))))
;;; Syntax checking mode
(defvar flycheck-mode-map
(let ((map (make-sparse-keymap)))
(define-key map flycheck-keymap-prefix flycheck-command-map)
;; We place the menu under a custom menu key. Since this menu key is not
;; present in the menu of the global map, no top-level menu entry is added
;; to the global menu bar. However, it still appears on the mode line
;; lighter.
(define-key map [menu-bar flycheck] flycheck-mode-menu-map)
map)
"Keymap of command `flycheck-mode'.")
(defvar-local flycheck-old-next-error-function nil
"Remember the old `next-error-function'.")
(defconst flycheck-hooks-alist
'(
;; Handle events that may start automatic syntax checks
(after-save-hook . flycheck-handle-save)
(after-change-functions . flycheck-handle-change)
;; Handle events that may triggered pending deferred checks
(window-configuration-change-hook . flycheck-perform-deferred-syntax-check)
(post-command-hook . flycheck-perform-deferred-syntax-check)
;; Teardown Flycheck whenever the buffer state is about to get lost, to
;; clean up temporary files and directories.
(kill-buffer-hook . flycheck-teardown)
(change-major-mode-hook . flycheck-teardown)
(before-revert-hook . flycheck-teardown)
;; Update the error list if necessary
(post-command-hook . flycheck-error-list-update-source)
(post-command-hook . flycheck-error-list-highlight-errors)
;; Display errors. Show errors at point after commands (like movements) and
;; when Emacs gets focus. Cancel the display timer when Emacs looses focus
;; (as there's no need to display errors if the user can't see them), and
;; hide the error buffer (for large error messages) if necessary. Note that
;; the focus hooks only work on Emacs 24.4 and upwards, but since undefined
;; hooks are perfectly ok we don't need a version guard here. They'll just
;; not work silently.
(post-command-hook . flycheck-display-error-at-point-soon)
(focus-in-hook . flycheck-display-error-at-point-soon)
(focus-out-hook . flycheck-cancel-error-display-error-at-point-timer)
(post-command-hook . flycheck-hide-error-buffer)
;; Immediately show error popups when navigating to an error
(next-error-hook . flycheck-display-error-at-point))
"Hooks which Flycheck needs to hook in.
The `car' of each pair is a hook variable, the `cdr' a function
to be added or removed from the hook variable if Flycheck mode is
enabled and disabled respectively.")
;;;###autoload
(define-minor-mode flycheck-mode
"Minor mode for on-the-fly syntax checking.
When called interactively, toggle `flycheck-mode'. With prefix
ARG, enable `flycheck-mode' if ARG is positive, otherwise disable
it.
When called from Lisp, enable `flycheck-mode' if ARG is omitted,
nil or positive. If ARG is `toggle', toggle `flycheck-mode'.
Otherwise behave as if called interactively.
In `flycheck-mode' the buffer is automatically syntax-checked
using the first suitable syntax checker from `flycheck-checkers'.
Use `flycheck-select-checker' to select a checker for the current
buffer manually.
\\{flycheck-mode-map}"
:init-value nil
:keymap flycheck-mode-map
:lighter flycheck-mode-line
:after-hook (flycheck-buffer-automatically 'mode-enabled 'force-deferred)
(cond
(flycheck-mode
(flycheck-clear)
(pcase-dolist (`(,hook . ,fn) flycheck-hooks-alist)
(add-hook hook fn nil 'local))
(setq flycheck-old-next-error-function (if flycheck-standard-error-navigation
next-error-function
:unset))
(when flycheck-standard-error-navigation
(setq next-error-function #'flycheck-next-error-function)))
(t
(unless (eq flycheck-old-next-error-function :unset)
(setq next-error-function flycheck-old-next-error-function))
(pcase-dolist (`(,hook . ,fn) flycheck-hooks-alist)
(remove-hook hook fn 'local))
(flycheck-teardown))))
;;; Syntax checker selection for the current buffer
(defun flycheck-get-checker-for-buffer ()
"Find the checker for the current buffer.
Use the selected checker for the current buffer, if any,
otherwise search for the best checker from `flycheck-checkers'.
Return checker if there is a checker for the current buffer, or
nil otherwise."
(if flycheck-checker
(if (flycheck-may-use-checker flycheck-checker)
flycheck-checker
(error "Flycheck cannot use %s in this buffer, type M-x flycheck-verify-setup for more details"
flycheck-checker))
(seq-find #'flycheck-may-use-checker flycheck-checkers)))
(defun flycheck-get-next-checker-for-buffer (checker)
"Get the checker to run after CHECKER for the current buffer."
(let ((next (seq-find #'flycheck-may-use-next-checker
(flycheck-checker-get checker 'next-checkers))))
(when next
(if (symbolp next) next (cdr next)))))
(defun flycheck-select-checker (checker)
"Select CHECKER for the current buffer.
CHECKER is a syntax checker symbol (see `flycheck-checkers') or
nil. In the former case, use CHECKER for the current buffer,
otherwise deselect the current syntax checker (if any) and use
automatic checker selection via `flycheck-checkers'.
If called interactively prompt for CHECKER. With prefix arg
deselect the current syntax checker and enable automatic
selection again.
Set `flycheck-checker' to CHECKER and automatically start a new
syntax check if the syntax checker changed.
CHECKER will be used, even if it is not contained in
`flycheck-checkers', or if it is disabled via
`flycheck-disabled-checkers'."
(interactive
(if current-prefix-arg
(list nil)
(list (read-flycheck-checker "Select checker: "
(flycheck-get-checker-for-buffer)))))
(when (not (eq checker flycheck-checker))
(unless (or (not checker) (flycheck-may-use-checker checker))
(flycheck-verify-checker checker)
(user-error "Can't use syntax checker %S in this buffer" checker))
(setq flycheck-checker checker)
(when flycheck-mode
(flycheck-buffer))))
(defun flycheck-disable-checker (checker &optional enable)
"Interactively disable CHECKER for the current buffer.
Interactively, prompt for a syntax checker to disable, and add
the syntax checker to the buffer-local value of
`flycheck-disabled-checkers'.
With non-nil ENABLE or with prefix arg, prompt for a disabled
syntax checker and re-enable it by removing it from the
buffer-local value of `flycheck-disabled-checkers'."
(declare (interactive-only "Directly set `flycheck-disabled-checkers' instead"))
(interactive
(let* ((enable current-prefix-arg)
(candidates (if enable flycheck-disabled-checkers flycheck-checkers))
(prompt (if enable "Enable syntax checker: "
"Disable syntax checker: ")))
(when (and enable (not candidates))
(user-error "No syntax checkers disabled in this buffer"))
(list (read-flycheck-checker prompt nil nil candidates) enable)))
(unless checker
(user-error "No syntax checker given"))
(if enable
;; We must use `remq' instead of `delq', because we must _not_ modify the
;; list. Otherwise we could potentially modify the global default value,
;; in case the list is the global default.
(when (memq checker flycheck-disabled-checkers)
(setq flycheck-disabled-checkers
(remq checker flycheck-disabled-checkers))
(flycheck-buffer))
(unless (memq checker flycheck-disabled-checkers)
(push checker flycheck-disabled-checkers)
(flycheck-buffer))))
;;; Syntax checks for the current buffer
(defvar-local flycheck-current-syntax-check nil
"The current syntax check in the this buffer.")
(put 'flycheck-current-syntax-check 'permanent-local t)
(defun flycheck-start-current-syntax-check (checker)
"Start a syntax check in the current buffer with CHECKER.
Set `flycheck-current-syntax-check' accordingly."
;; Allocate the current syntax check *before* starting it. This allows for
;; synchronous checks, which call the status callback immediately in their
;; start function.
(let* ((check (flycheck-syntax-check-new
:buffer (current-buffer)
:checker checker
:context nil
:working-directory (flycheck-compute-working-directory checker)))
(callback (flycheck-buffer-status-callback check)))
(setq flycheck-current-syntax-check check)
(flycheck-report-status 'running)
(flycheck-syntax-check-start check callback)))
(defun flycheck-running-p ()
"Determine whether a syntax check is running in the current buffer."
(not (null flycheck-current-syntax-check)))
(defun flycheck-stop ()
"Stop any ongoing syntax check in the current buffer."
(when (flycheck-running-p)
(flycheck-syntax-check-interrupt flycheck-current-syntax-check)
;; Remove the current syntax check, to reset Flycheck into a non-running
;; state, and to make `flycheck-report-buffer-checker-status' ignore any
;; status reports from the current syntax check.
(setq flycheck-current-syntax-check nil)
(flycheck-report-status 'interrupted)))
(defun flycheck-buffer-status-callback (syntax-check)
"Create a status callback for SYNTAX-CHECK in the current buffer."
(lambda (&rest args)
(apply #'flycheck-report-buffer-checker-status
syntax-check args)))
(defun flycheck-buffer ()
"Start checking syntax in the current buffer.
Get a syntax checker for the current buffer with
`flycheck-get-checker-for-buffer', and start it."
(interactive)
(flycheck-clean-deferred-check)
(if flycheck-mode
(unless (flycheck-running-p)
;; Clear error list and mark all overlays for deletion. We do not
;; delete all overlays immediately to avoid excessive re-displays and
;; flickering, if the same errors gets highlighted again after the check
;; completed.
(run-hooks 'flycheck-before-syntax-check-hook)
(flycheck-clear-errors)
(flycheck-mark-all-overlays-for-deletion)
(condition-case err
(let* ((checker (flycheck-get-checker-for-buffer)))
(if checker
(flycheck-start-current-syntax-check checker)
(flycheck-clear)
(flycheck-report-status 'no-checker)))
(error
(flycheck-report-failed-syntax-check)
(signal (car err) (cdr err)))))
(user-error "Flycheck mode disabled")))
(defun flycheck-report-buffer-checker-status
(syntax-check status &optional data)
"In BUFFER, report a SYNTAX-CHECK STATUS with DATA.
SYNTAX-CHECK is the `flycheck-syntax-check' which reported
STATUS. STATUS denotes the status of CHECKER, with an optional
DATA. STATUS may be one of the following symbols:
`errored'
The syntax checker has errored. DATA is an optional error
message.
This report finishes the current syntax check.
`interrupted'
The syntax checker was interrupted. DATA is ignored.
This report finishes the current syntax check.
`finished'
The syntax checker has finished with a proper error report
for the current buffer. DATA is the (potentially empty)
list of `flycheck-error' objects reported by the syntax
check.
This report finishes the current syntax check.
`suspicious'
The syntax checker encountered a suspicious state, which the
user needs to be informed about. DATA is an optional
message.
A syntax checker _must_ report a status at least once with any
symbol that finishes the current syntax checker. Otherwise
Flycheck gets stuck with the current syntax check.
If CHECKER is not the currently used syntax checker in
`flycheck-current-syntax-check', the status report is largely
ignored. Notably, any errors reported by the checker are
discarded."
(let ((buffer (flycheck-syntax-check-buffer syntax-check)))
;; Ignore the status report if the buffer is gone, or if this syntax check
;; isn't the current one in buffer (which can happen if this is an old
;; report of an interrupted syntax check, and a new syntax check was started
;; since this check was interrupted)
(when (and (buffer-live-p buffer)
(eq syntax-check
(buffer-local-value 'flycheck-current-syntax-check buffer)))
(with-current-buffer buffer
(let ((checker (flycheck-syntax-check-checker syntax-check)))
(pcase status
((or `errored `interrupted)
(flycheck-report-failed-syntax-check status)
(when (eq status 'errored)
;; In case of error, show the error message
(message "Error from syntax checker %s: %s"
checker (or data "UNKNOWN!"))))
(`suspicious
(when flycheck-mode
(message "Suspicious state from syntax checker %s: %s"
checker (or data "UNKNOWN!")))
(flycheck-report-status 'suspicious))
(`finished
(when flycheck-mode
;; Only report errors from the checker if Flycheck Mode is
;; still enabled.
(flycheck-finish-current-syntax-check
data
(flycheck-syntax-check-working-directory syntax-check))))
(_
(error "Unknown status %s from syntax checker %s"
status checker))))))))
(defun flycheck-finish-current-syntax-check (errors working-dir)
"Finish the current syntax-check in the current buffer with ERRORS.
ERRORS is a list of `flycheck-error' objects reported by the
current syntax check in `flycheck-current-syntax-check'.
Report all ERRORS and potentially start any next syntax checkers.
If the current syntax checker reported excessive errors, it is
disabled via `flycheck-disable-excessive-checker' for subsequent
syntax checks.
Relative file names in ERRORS will be expanded relative to
WORKING-DIR."
(let* ((syntax-check flycheck-current-syntax-check)
(checker (flycheck-syntax-check-checker syntax-check))
(errors (flycheck-relevant-errors
(flycheck-fill-and-expand-error-file-names
(flycheck-filter-errors
(flycheck-assert-error-list-p errors) checker)
working-dir))))
(unless (flycheck-disable-excessive-checker checker errors)
(flycheck-report-current-errors errors))
(let ((next-checker (flycheck-get-next-checker-for-buffer checker)))
(if next-checker
(flycheck-start-current-syntax-check next-checker)
(setq flycheck-current-syntax-check nil)
(flycheck-report-status 'finished)
;; Delete overlays only after the very last checker has run, to avoid
;; flickering on intermediate re-displays
(flycheck-delete-marked-overlays)
(flycheck-error-list-refresh)
(run-hooks 'flycheck-after-syntax-check-hook)
(when (eq (current-buffer) (window-buffer))
(flycheck-display-error-at-point))
;; Immediately try to run any pending deferred syntax check, which
;; were triggered by intermediate automatic check event, to make sure
;; that we quickly refine outdated error information
(flycheck-perform-deferred-syntax-check)))))
(defun flycheck-disable-excessive-checker (checker errors)
"Disable CHECKER if it reported excessive ERRORS.
If ERRORS has more items than `flycheck-checker-error-threshold',
add CHECKER to `flycheck-disabled-checkers', and show a warning.
Return t when CHECKER was disabled, or nil otherwise."
(when (and flycheck-checker-error-threshold
(> (length errors) flycheck-checker-error-threshold))
;; Disable CHECKER for this buffer (`flycheck-disabled-checkers' is a local
;; variable).
(lwarn '(flycheck syntax-checker) :warning
"Syntax checker %s reported too many errors (%s) and is disabled."
checker (length errors))
(push checker flycheck-disabled-checkers)
t))
(defun flycheck-clear (&optional shall-interrupt)
"Clear all errors in the current buffer.
With prefix arg or SHALL-INTERRUPT non-nil, also interrupt the
current syntax check."
(interactive "P")
(when shall-interrupt
(flycheck-stop))
(flycheck-delete-all-overlays)
(flycheck-clear-errors)
(flycheck-error-list-refresh)
(flycheck-hide-error-buffer))
(defun flycheck-teardown ()
"Teardown Flycheck in the current buffer..
Completely clear the whole Flycheck state. Remove overlays, kill
running checks, and empty all variables used by Flycheck."
(flycheck-safe-delete-temporaries)
(flycheck-stop)
(flycheck-clean-deferred-check)
(flycheck-clear)
(flycheck-cancel-error-display-error-at-point-timer))
;;; Automatic syntax checking in a buffer
(defun flycheck-may-check-automatically (&optional condition)
"Determine whether the buffer may be checked under CONDITION.
Read-only buffers may never be checked automatically.
If CONDITION is non-nil, determine whether syntax may checked
automatically according to
`flycheck-check-syntax-automatically'."
(and (not (or buffer-read-only (flycheck-ephemeral-buffer-p)))
(file-exists-p default-directory)
(or (not condition)
(memq condition flycheck-check-syntax-automatically))))
(defun flycheck-buffer-automatically (&optional condition force-deferred)
"Automatically check syntax at CONDITION.
Syntax is not checked if `flycheck-may-check-automatically'
returns nil for CONDITION.
The syntax check is deferred if FORCE-DEFERRED is non-nil, or if
`flycheck-must-defer-check' returns t."
(when (and flycheck-mode (flycheck-may-check-automatically condition))
(if (or force-deferred (flycheck-must-defer-check))
(flycheck-buffer-deferred)
(with-demoted-errors "Error while checking syntax automatically: %S"
(flycheck-buffer)))))
(defvar-local flycheck-idle-change-timer nil
"Timer to mark the idle time since the last change.")
(defun flycheck-clear-idle-change-timer ()
"Clear the idle change timer."
(when flycheck-idle-change-timer
(cancel-timer flycheck-idle-change-timer)
(setq flycheck-idle-change-timer nil)))
(defun flycheck-handle-change (beg end _len)
"Handle a buffer change between BEG and END.
BEG and END mark the beginning and end of the change text. _LEN
is ignored.
Start a syntax check if a new line has been inserted into the
buffer."
;; Save and restore the match data, as recommended in (elisp)Change Hooks
(save-match-data
(when flycheck-mode
;; The buffer was changed, thus clear the idle timer
(flycheck-clear-idle-change-timer)
(if (string-match-p (rx "\n") (buffer-substring beg end))
(flycheck-buffer-automatically 'new-line 'force-deferred)
(setq flycheck-idle-change-timer
(run-at-time flycheck-idle-change-delay nil
#'flycheck-handle-idle-change))))))
(defun flycheck-handle-idle-change ()
"Handle an expired idle time since the last change."
(flycheck-clear-idle-change-timer)
(flycheck-buffer-automatically 'idle-change))
(defun flycheck-handle-save ()
"Handle a save of the buffer."
(flycheck-buffer-automatically 'save))
;;; Deferred syntax checking
(defvar-local flycheck-deferred-syntax-check nil
"If non-nil, a deferred syntax check is pending.")
(defun flycheck-must-defer-check ()
"Determine whether the syntax check has to be deferred.
A check has to be deferred if the buffer is not visible, or if the buffer is
currently being reverted.
Return t if the check is to be deferred, or nil otherwise."
(or (not (get-buffer-window))
;; We defer the syntax check if Flycheck is already running, to
;; immediately start a new syntax check after the current one finished,
;; because the result of the current check will most likely be outdated by
;; the time it is finished.
(flycheck-running-p)
;; We must defer checks while a buffer is being reverted, to avoid race
;; conditions while the buffer contents are being restored.
revert-buffer-in-progress-p))
(defun flycheck-deferred-check-p ()
"Determine whether the current buffer has a deferred check.
Return t if so, or nil otherwise."
flycheck-deferred-syntax-check)
(defun flycheck-buffer-deferred ()
"Defer syntax check for the current buffer."
(setq flycheck-deferred-syntax-check t))
(defun flycheck-clean-deferred-check ()
"Clean an deferred syntax checking state."
(setq flycheck-deferred-syntax-check nil))
(defun flycheck-perform-deferred-syntax-check ()
"Perform the deferred syntax check."
(when (flycheck-deferred-check-p)
(flycheck-clean-deferred-check)
(flycheck-buffer-automatically)))
;;; Syntax checking in all buffers
(defun flycheck-may-enable-mode ()
"Determine whether Flycheck mode may be enabled.
Flycheck mode is not enabled for
- the minibuffer,
- `fundamental-mode'
- major modes whose `mode-class' property is `special',
- ephemeral buffers (see `flycheck-ephemeral-buffer-p'),
- encrypted buffers (see `flycheck-encrypted-buffer-p'),
- remote files (see `file-remote-p'),
- and major modes excluded by `flycheck-global-modes'.
Return non-nil if Flycheck mode may be enabled, and nil
otherwise."
(and (pcase flycheck-global-modes
;; Whether `major-mode' is disallowed by `flycheck-global-modes'
(`t t)
(`(not . ,modes) (not (memq major-mode modes)))
(modes (memq major-mode modes)))
(not (or (minibufferp)
(eq major-mode 'fundamental-mode)
(eq (get major-mode 'mode-class) 'special)
(flycheck-ephemeral-buffer-p)
(flycheck-encrypted-buffer-p)
(and (buffer-file-name)
(file-remote-p (buffer-file-name) 'method))))))
(defun flycheck-mode-on-safe ()
"Enable command `flycheck-mode' if it is safe to do so.
Command `flycheck-mode' is only enabled if
`flycheck-may-enable-mode' returns a non-nil result."
(when (flycheck-may-enable-mode)
(flycheck-mode)))
;;;###autoload
(define-globalized-minor-mode global-flycheck-mode flycheck-mode
flycheck-mode-on-safe
:init-value nil
;; Do not expose Global Flycheck Mode on customize interface, because the
;; interaction between package.el and customize is currently broken. See
;; https://github.com/flycheck/flycheck/issues/595
;; :require 'flycheck :group
;; 'flycheck
)
(defun flycheck-global-teardown ()
"Teardown Flycheck in all buffers.
Completely clear the whole Flycheck state in all buffers, stop
all running checks, remove all temporary files, and empty all
variables of Flycheck."
(dolist (buffer (buffer-list))
(with-current-buffer buffer
(when flycheck-mode
(flycheck-teardown)))))
;; Clean up the entire state of Flycheck when Emacs is killed, to get rid of any
;; pending temporary files.
(add-hook 'kill-emacs-hook #'flycheck-global-teardown)
;;; Errors from syntax checks
(cl-defstruct (flycheck-error
(:constructor flycheck-error-new)
(:constructor flycheck-error-new-at (line column
&optional level message
&key checker id group
(filename (buffer-file-name))
(buffer (current-buffer)))))
"Structure representing an error reported by a syntax checker.
Slots:
`buffer'
The buffer that the error was reported for, as buffer object.
`checker'
The syntax checker which reported this error, as symbol.
`filename'
The file name the error refers to, as string.
`line'
The line number the error refers to, as number.
`column' (optional)
The column number the error refers to, as number.
For compatibility with external tools and unlike Emacs
itself (e.g. in Compile Mode) Flycheck uses _1-based_
columns: The first character on a line is column 1.
Occasionally some tools try to proactively adapt to Emacs
and emit 0-based columns automatically. In these cases, the
columns must be adjusted for Flycheck, see
`flycheck-increment-error-columns'.
`message' (optional)
The error message as a string, if any.
`level'
The error level, as either `info', `warning' or `error'.
`id' (optional)
An ID identifying the kind of error.
`group` (optional)
A symbol identifying the group the error belongs to.
Some tools will emit multiple errors that relate to the same
issue (e.g., lifetime errors in Rust). All related errors
collected by a checker should have the same `group` value,
in order to be able to present them to the user.
See `flycheck-errors-from-group`."
buffer checker filename line column message level id group)
(defmacro flycheck-error-with-buffer (err &rest forms)
"Switch to the buffer of ERR and evaluate FORMS.
If the buffer of ERR is not live, FORMS are not evaluated."
(declare (indent 1) (debug t))
`(when (buffer-live-p (flycheck-error-buffer ,err))
(with-current-buffer (flycheck-error-buffer ,err)
,@forms)))
(defun flycheck-error-line-region (err)
"Get the line region of ERR.
ERR is a Flycheck error whose region to get.
Return a cons cell `(BEG . END)' where BEG is the first
non-whitespace character on the line ERR refers to, and END the
end of the line."
(flycheck-error-with-buffer err
(save-restriction
(save-excursion
(widen)
(goto-char (point-min))
(forward-line (- (flycheck-error-line err) 1))
;; We are at the beginning of the line now, so move to the beginning of
;; its indentation, similar to `back-to-indentation'
(let ((end (line-end-position)))
(skip-syntax-forward " " end)
(backward-prefix-chars)
;; If the current line is empty, include the previous line break
;; character(s) to have any region at all. When called with 0,
;; `line-end-position' gives us the end of the previous line
(cons (if (eolp) (line-end-position 0) (point)) end))))))
(defun flycheck-error-column-region (err)
"Get the error column region of ERR.
ERR is a Flycheck error whose region to get.
Return a cons cell `(BEG . END)' where BEG is the character
before the error column, and END the actual error column, or nil
if ERR has no column."
(flycheck-error-with-buffer err
(save-restriction
(save-excursion
(-when-let (column (flycheck-error-column err))
(widen)
(goto-char (point-min))
(forward-line (- (flycheck-error-line err) 1))
(cond
((eobp) ; Line beyond EOF
;; If we are at the end of the file (i.e. the line was beyond the
;; end of the file), use the very last column in the file.
(cons (- (point-max) 1) (point-max)))
((eolp) ; Empty line
;; If the target line is empty, there's no column to highlight on
;; this line, so return the last column of the previous line.
(cons (line-end-position 0) (point)))
(t
;; The end is either the column offset of the line, or the end of
;; the line, if the column offset points beyond the end of the
;; line.
(let ((end (min (+ (point) column)
(+ (line-end-position) 1))))
(cons (- end 1) end)))))))))
(defun flycheck-error-thing-region (thing err)
"Get the region of THING at the column of ERR.
ERR is a Flycheck error whose region to get. THING is a
understood by `thing-at-point'.
Return a cons cell `(BEG . END)' where BEG is the beginning of
the THING at the error column, and END the end of the symbol. If
ERR has no error column, or if there is no THING at this column,
return nil."
(-when-let (column (car (flycheck-error-column-region err)))
(flycheck-error-with-buffer err
(save-excursion
(save-restriction
(widen)
(goto-char column)
(bounds-of-thing-at-point thing))))))
(defun flycheck-error-region-for-mode (err mode)
"Get the region of ERR for the highlighting MODE.
ERR is a Flycheck error. MODE may be one of the following symbols:
`columns'
Get the column region of ERR, or the line region if ERR
has no column.
`symbols'
Get the symbol region of ERR, or the result of `columns', if
there is no sexp at the error column.
`sexps'
Get the sexp region of ERR, or the result of `columns', if
there is no sexp at the error column.
`lines'
Return the line region.
Otherwise signal an error."
;; Ignoring fields speeds up calls to `line-end-position' in
;; `flycheck-error-column-region' and `flycheck-error-line-region'.
(let ((inhibit-field-text-motion t))
(pcase mode
(`columns (or (flycheck-error-column-region err)
(flycheck-error-line-region err)))
(`symbols (or (flycheck-error-thing-region 'symbol err)
(flycheck-error-region-for-mode err 'columns)))
(`sexps (or (flycheck-error-thing-region 'sexp err)
(flycheck-error-region-for-mode err 'columns)))
(`lines (flycheck-error-line-region err))
(_ (error "Invalid mode %S" mode)))))
(defun flycheck-error-pos (err)
"Get the buffer position of ERR.
ERR is a Flycheck error whose position to get.
The error position is the error column, or the first
non-whitespace character of the error line, if ERR has no error column."
(car (or (flycheck-error-column-region err)
(flycheck-error-line-region err))))
(defun flycheck-error-format-message-and-id (err)
"Format the message and id of ERR as human-readable string."
(let ((id (flycheck-error-id err))
(message (flycheck-error-message err)))
(if id (format "%s [%s]" message id) message)))
(defun flycheck-error-format (err &optional with-file-name)
"Format ERR as human-readable string, optionally WITH-FILE-NAME.
Return a string that represents the given ERR. If WITH-FILE-NAME
is given and non-nil, include the file-name as well, otherwise
omit it."
(let* ((line (flycheck-error-line err))
(column (flycheck-error-column err))
(level (symbol-name (flycheck-error-level err)))
(checker (symbol-name (flycheck-error-checker err)))
(format `(,@(when with-file-name
(list (flycheck-error-filename err) ":"))
,(number-to-string line) ":"
,@(when column (list (number-to-string column) ":"))
,level ": "
,(flycheck-error-format-message-and-id err)
" (" ,checker ")")))
(apply #'concat format)))
(defun flycheck-error-< (err1 err2)
"Determine whether ERR1 is less than ERR2 by location.
Compare by line numbers and then by column numbers."
(let ((line1 (flycheck-error-line err1))
(line2 (flycheck-error-line err2)))
(if (= line1 line2)
(let ((col1 (flycheck-error-column err1))
(col2 (flycheck-error-column err2)))
(and col2
;; Sort errors for the whole line first
(or (not col1) (< col1 col2))))
(< line1 line2))))
(defun flycheck-error-level-< (err1 err2)
"Determine whether ERR1 is less than ERR2 by error level.
Like `flycheck-error-<', but compares by error level severity
first. Levels of the same severity are compared by name."
(let* ((level1 (flycheck-error-level err1))
(level2 (flycheck-error-level err2))
(severity1 (flycheck-error-level-severity level1))
(severity2 (flycheck-error-level-severity level2)))
(cond
((= severity1 severity2)
(if (string= level1 level2)
(flycheck-error-< err1 err2)
(string< level1 level2)))
(t (< severity1 severity2)))))
(defun flycheck-assert-error-list-p (errors)
"Assert that all items in ERRORS are of `flycheck-error' type.
Signal an error if any item in ERRORS is not a `flycheck-error'
object, as by `flycheck-error-p'. Otherwise return ERRORS
again."
(unless (listp errors)
(signal 'wrong-type-argument (list 'listp errors)))
(dolist (err errors)
(unless (flycheck-error-p err)
(signal 'wrong-type-argument (list 'flycheck-error-p err))))
errors)
;;; Errors in the current buffer
(defvar-local flycheck-current-errors nil
"A list of all errors and warnings in the current buffer.")
(defun flycheck-report-current-errors (errors)
"Report ERRORS in the current buffer.
Add ERRORS to `flycheck-current-errors' and process each error
with `flycheck-process-error-functions'."
(setq flycheck-current-errors (sort (append errors flycheck-current-errors)
#'flycheck-error-<))
(overlay-recenter (point-max))
(seq-do (lambda (err)
(run-hook-with-args-until-success 'flycheck-process-error-functions
err))
errors))
(defun flycheck-clear-errors ()
"Remove all error information from the current buffer."
(setq flycheck-current-errors nil)
(flycheck-report-status 'not-checked))
(defun flycheck-fill-and-expand-error-file-names (errors directory)
"Fill and expand file names in ERRORS relative to DIRECTORY.
Expand all file names of ERRORS against DIRECTORY. If the file
name of an error is nil fill in the result of function
`buffer-file-name' in the current buffer.
Return ERRORS, modified in-place."
(seq-do (lambda (err)
(setf (flycheck-error-filename err)
(-if-let (filename (flycheck-error-filename err))
(expand-file-name filename directory)
(buffer-file-name))))
errors)
errors)
(defun flycheck-relevant-error-p (err)
"Determine whether ERR is relevant for the current buffer.
Return t if ERR may be shown for the current buffer, or nil
otherwise."
(flycheck-error-with-buffer err
(let ((file-name (flycheck-error-filename err))
(message (flycheck-error-message err)))
(and
;; The error is relevant for the current buffer if it's got no file-name
;; and the current buffer has no file name, too, or if it refers to the
;; same file as the current buffer.
(or (and (not file-name) (not buffer-file-name))
(and buffer-file-name file-name
(flycheck-same-files-p file-name buffer-file-name)))
message
(not (string-empty-p message))
(flycheck-error-line err)))))
(defun flycheck-relevant-errors (errors)
"Filter the relevant errors from ERRORS.
Return a list of all errors that are relevant for their
corresponding buffer."
(seq-filter #'flycheck-relevant-error-p errors))
(defun flycheck-related-errors (err)
"Get all the errors that are in the same group as ERR.
Return a list of all errors (in `flycheck-current-errors') that
have the same `flycheck-error-group' as ERR, including ERR
itself."
(-if-let (group (flycheck-error-group err))
(seq-filter (lambda (e)
(eq (flycheck-error-group e) group))
flycheck-current-errors)
(list err)))
;;; Status reporting for the current buffer
(defvar-local flycheck-last-status-change 'not-checked
"The last status change in the current buffer.")
(defun flycheck-report-failed-syntax-check (&optional status)
"Report a failed Flycheck syntax check with STATUS.
STATUS is a status symbol for `flycheck-report-status',
defaulting to `errored'.
Clear Flycheck state, run `flycheck-syntax-check-failed-hook' and
report an error STATUS."
(flycheck-clear)
(setq flycheck-current-syntax-check nil)
(run-hooks 'flycheck-syntax-check-failed-hook)
(flycheck-report-status (or status 'errored)))
(defun flycheck-report-status (status)
"Report Flycheck STATUS.
STATUS is one of the following symbols:
`not-checked'
The current buffer was not checked.
`no-checker'
Automatic syntax checker selection did not find a suitable
syntax checker.
`running'
A syntax check is now running in the current buffer.
`errored'
The current syntax check has errored.
`finished'
The current syntax check was finished normally.
`interrupted'
The current syntax check was interrupted.
`suspicious'
The last syntax check had a suspicious result.
Set `flycheck-last-status-change' and call
`flycheck-status-changed-functions' with STATUS. Afterwards
refresh the mode line."
(setq flycheck-last-status-change status)
(run-hook-with-args 'flycheck-status-changed-functions status)
(force-mode-line-update))
(defun flycheck-mode-line-status-text (&optional status)
"Get a text describing STATUS for use in the mode line.
STATUS defaults to `flycheck-last-status-change' if omitted or
nil."
(let ((text (pcase (or status flycheck-last-status-change)
(`not-checked "")
(`no-checker "-")
(`running "*")
(`errored "!")
(`finished
(let-alist (flycheck-count-errors flycheck-current-errors)
(if (or .error .warning)
(format ":%s/%s" (or .error 0) (or .warning 0))
"")))
(`interrupted "-")
(`suspicious "?"))))
(concat " " flycheck-mode-line-prefix text)))
;;; Error levels
;;;###autoload
(defun flycheck-define-error-level (level &rest properties)
"Define a new error LEVEL with PROPERTIES.
The following PROPERTIES constitute an error level:
`:severity SEVERITY'
A number denoting the severity of this level. The higher
the number, the more severe is this level compared to other
levels. Defaults to 0.
The severity is used by `flycheck-error-level-<' to
determine the ordering of errors according to their levels.
`:compilation-level LEVEL'
A number indicating the broad class of messages that errors
at this level belong to: one of 0 (info), 1 (warning), or
2 or nil (error). Defaults to nil.
This is used by `flycheck-checker-pattern-to-error-regexp'
to map error levels into `compilation-mode''s hierarchy and
to get proper highlighting of errors in `compilation-mode'.
`:overlay-category CATEGORY'
A symbol denoting the overlay category to use for error
highlight overlays for this level. See Info
node `(elisp)Overlay Properties' for more information about
overlay categories.
A category for an error level overlay should at least define
the `face' property, for error highlighting. Another useful
property for error level categories is `priority', to
influence the stacking of multiple error level overlays.
`:fringe-bitmap BITMAP'
A fringe bitmap symbol denoting the bitmap to use for fringe
indicators for this level. See Info node `(elisp)Fringe
Bitmaps' for more information about fringe bitmaps,
including a list of built-in fringe bitmaps.
`:fringe-face FACE'
A face symbol denoting the face to use for fringe indicators
for this level.
`:error-list-face FACE'
A face symbol denoting the face to use for messages of this
level in the error list. See `flycheck-list-errors'."
(declare (indent 1))
(setf (get level 'flycheck-error-level) t)
(setf (get level 'flycheck-error-severity)
(or (plist-get properties :severity) 0))
(setf (get level 'flycheck-compilation-level)
(plist-get properties :compilation-level))
(setf (get level 'flycheck-overlay-category)
(plist-get properties :overlay-category))
(setf (get level 'flycheck-fringe-bitmap-double-arrow)
(plist-get properties :fringe-bitmap))
(setf (get level 'flycheck-fringe-face)
(plist-get properties :fringe-face))
(setf (get level 'flycheck-error-list-face)
(plist-get properties :error-list-face)))
(defun flycheck-error-level-p (level)
"Determine whether LEVEL is a Flycheck error level."
(get level 'flycheck-error-level))
(defun flycheck-error-level-severity (level)
"Get the numeric severity of LEVEL."
(or (get level 'flycheck-error-severity) 0))
(defun flycheck-error-level-compilation-level (level)
"Get the compilation level for LEVEL."
(get level 'flycheck-compilation-level))
(defun flycheck-error-level-overlay-category (level)
"Get the overlay category for LEVEL."
(get level 'flycheck-overlay-category))
(defun flycheck-error-level-fringe-bitmap (level)
"Get the fringe bitmap for LEVEL."
(get level 'flycheck-fringe-bitmap-double-arrow))
(defun flycheck-error-level-fringe-face (level)
"Get the fringe face for LEVEL."
(get level 'flycheck-fringe-face))
(defun flycheck-error-level-error-list-face (level)
"Get the error list face for LEVEL."
(get level 'flycheck-error-list-face))
(defun flycheck-error-level-make-fringe-icon (level side)
"Create the fringe icon for LEVEL at SIDE.
Return a propertized string that shows a fringe bitmap according
to LEVEL and the given fringe SIDE.
LEVEL is a Flycheck error level defined with
`flycheck-define-error-level', and SIDE is either `left-fringe'
or `right-fringe'.
Return a propertized string representing the fringe icon,
intended for use as `before-string' of an overlay to actually
show the icon."
(unless (memq side '(left-fringe right-fringe))
(error "Invalid fringe side: %S" side))
(propertize "!" 'display
(list side
(flycheck-error-level-fringe-bitmap level)
(flycheck-error-level-fringe-face level))))
;;; Built-in error levels
(when (fboundp 'define-fringe-bitmap)
(define-fringe-bitmap 'flycheck-fringe-bitmap-double-arrow
(vector #b00000000
#b00000000
#b00000000
#b00000000
#b00000000
#b10011000
#b01101100
#b00110110
#b00011011
#b00110110
#b01101100
#b10011000
#b00000000
#b00000000
#b00000000
#b00000000
#b00000000)))
(setf (get 'flycheck-error-overlay 'face) 'flycheck-error)
(setf (get 'flycheck-error-overlay 'priority) 110)
(flycheck-define-error-level 'error
:severity 100
:compilation-level 2
:overlay-category 'flycheck-error-overlay
:fringe-bitmap 'flycheck-fringe-bitmap-double-arrow
:fringe-face 'flycheck-fringe-error
:error-list-face 'flycheck-error-list-error)
(setf (get 'flycheck-warning-overlay 'face) 'flycheck-warning)
(setf (get 'flycheck-warning-overlay 'priority) 100)
(flycheck-define-error-level 'warning
:severity 10
:compilation-level 1
:overlay-category 'flycheck-warning-overlay
:fringe-bitmap 'flycheck-fringe-bitmap-double-arrow
:fringe-face 'flycheck-fringe-warning
:error-list-face 'flycheck-error-list-warning)
(setf (get 'flycheck-info-overlay 'face) 'flycheck-info)
(setf (get 'flycheck-info-overlay 'priority) 90)
(flycheck-define-error-level 'info
:severity -10
:compilation-level 0
:overlay-category 'flycheck-info-overlay
:fringe-bitmap 'flycheck-fringe-bitmap-double-arrow
:fringe-face 'flycheck-fringe-info
:error-list-face 'flycheck-error-list-info)
;;; Error filtering
(defun flycheck-filter-errors (errors checker)
"Filter ERRORS from CHECKER.
Apply the error filter of CHECKER to ERRORs and return the
result. If CHECKER has no error filter, fall back to
`flycheck-sanitize-errors'."
(let ((filter (or (flycheck-checker-get checker 'error-filter)
#'flycheck-sanitize-errors)))
(funcall filter errors)))
(defun flycheck-sanitize-errors (errors)
"Sanitize ERRORS.
Sanitize ERRORS by trimming leading and trailing whitespace in
all error messages, and by replacing 0 columns and empty error
messages with nil.
Returns sanitized ERRORS."
(dolist (err errors)
(flycheck-error-with-buffer err
(let ((message (flycheck-error-message err))
(column (flycheck-error-column err))
(id (flycheck-error-id err)))
(when message
(setq message (string-trim message))
(setf (flycheck-error-message err)
(if (string-empty-p message) nil message)))
(when (and id (string-empty-p id))
(setf (flycheck-error-id err) nil))
(when (eq column 0)
(setf (flycheck-error-column err) nil)))))
errors)
(defun flycheck-remove-error-file-names (file-name errors)
"Remove matching FILE-NAME from ERRORS.
Use as `:error-filter' for syntax checkers that output faulty
filenames. Flycheck will later fill in the buffer file name.
Return ERRORS."
(seq-do (lambda (err)
(when (and (flycheck-error-filename err)
(string= (flycheck-error-filename err) file-name))
(setf (flycheck-error-filename err) nil)))
errors)
errors)
(defun flycheck-increment-error-columns (errors &optional offset)
"Increment all columns of ERRORS by OFFSET.
Use this as `:error-filter' if a syntax checker outputs 0-based
columns."
(seq-do (lambda (err)
(let ((column (flycheck-error-column err)))
(when column
(setf (flycheck-error-column err)
(+ column (or offset 1))))))
errors)
errors)
(defun flycheck-collapse-error-message-whitespace (errors)
"Collapse whitespace in all messages of ERRORS.
Return ERRORS."
(dolist (err errors)
(-when-let (message (flycheck-error-message err))
(setf (flycheck-error-message err)
(replace-regexp-in-string (rx (one-or-more (any space "\n" "\r")))
" " message 'fixed-case 'literal))))
errors)
(defun flycheck-dedent-error-messages (errors)
"Dedent all messages of ERRORS.
For each error in ERRORS, determine the indentation offset from
the leading whitespace of the first line, and dedent all further
lines accordingly.
Return ERRORS, with in-place modifications."
(dolist (err errors)
(-when-let (message (flycheck-error-message err))
(with-temp-buffer
(insert message)
;; Determine the indentation offset
(goto-char (point-min))
(back-to-indentation)
(let* ((indent-offset (- (point) (point-min))))
;; Now iterate over all lines and dedent each according to
;; `indent-offset'
(while (not (eobp))
(back-to-indentation)
;; If the current line starts with sufficient whitespace, delete the
;; indendation offset. Otherwise keep the line intact, as we might
;; loose valuable information
(when (>= (- (point) (line-beginning-position)) indent-offset)
(delete-char (- indent-offset)))
(forward-line 1)))
(delete-trailing-whitespace (point-min) (point-max))
(setf (flycheck-error-message err)
(buffer-substring-no-properties (point-min) (point-max))))))
errors)
(defun flycheck-fold-include-levels (errors sentinel-message)
"Fold levels of ERRORS from included files.
ERRORS is a list of `flycheck-error' objects. SENTINEL-MESSAGE
is a regular expression matched against the error message to
determine whether the errror denotes errors from an included
file. Alternatively, it is a function that is given an error and
shall return non-nil, if the error denotes errors from an
included file."
(unless (or (stringp sentinel-message) (functionp sentinel-message))
(error "Sentinel must be string or function: %S" sentinel-message))
(let ((sentinel (if (functionp sentinel-message)
sentinel-message
(lambda (err)
(string-match-p sentinel-message
(flycheck-error-message err)))))
(remaining-errors errors))
(while remaining-errors
(let* ((current-error (pop remaining-errors)))
(when (funcall sentinel current-error)
;; We found an error denoting errors in the included file:
;; 1. process all subsequent errors until faulty include file is found
;; 2. process again all subsequent errors until an error has the
;; current file name again
;; 3. find the most severe error level
(let ((current-filename (flycheck-error-filename current-error))
(current-level nil)
(faulty-include-filename nil)
(filename nil)
(done (null remaining-errors)))
(while (not done)
(setq filename (flycheck-error-filename (car remaining-errors)))
(unless faulty-include-filename
(unless (string= filename current-filename)
(setq faulty-include-filename filename)))
(let* ((error-in-include (pop remaining-errors))
(in-include-level (flycheck-error-level error-in-include)))
(unless (funcall sentinel error-in-include)
;; Ignore nested "included file" errors, we are only
;; interested in real errors because these define our level
(when (or (not current-level)
(> (flycheck-error-level-severity in-include-level)
(flycheck-error-level-severity current-level)))
(setq current-level in-include-level))))
(setq done (or (null remaining-errors)
(and faulty-include-filename
(string= filename current-filename)))))
(setf (flycheck-error-level current-error) current-level
(flycheck-error-message current-error)
(format "In include %s" faulty-include-filename))))))
errors))
(defun flycheck-dequalify-error-ids (errors)
"De-qualify error ids in ERRORS.
Remove all qualifications from error ids in ERRORS, by stripping
all leading dotted components from error IDs. For instance, if
the error ID is com.foo.E100, replace it with E100.
This error filter is mainly useful to simplify error IDs obtained
from parsing Checkstyle XML, which frequently has very verbose
IDs, that include the name of the tool."
(seq-do (lambda (err)
(let ((id (flycheck-error-id err)))
(when id
(setf (flycheck-error-id err)
(replace-regexp-in-string
(rx string-start
(group
(optional (zero-or-more not-newline) "."))
(one-or-more (not (any ".")))
string-end)
"" id 'fixedcase 'literal 1)))))
errors)
errors)
(defun flycheck-remove-error-ids (errors)
"Remove all error ids from ERRORS."
(seq-do (lambda (err) (setf (flycheck-error-id err) nil)) errors)
errors)
;;; Error analysis
(defun flycheck-count-errors (errors)
"Count the number of ERRORS, grouped by level..
Return an alist, where each ITEM is a cons cell whose `car' is an
error level, and whose `cdr' is the number of errors of that
level."
(let (counts-by-level)
(dolist (err errors)
(let* ((level (flycheck-error-level err))
(item (assq level counts-by-level)))
(if item
(cl-incf (cdr item))
(push (cons level 1) counts-by-level))))
counts-by-level))
(defun flycheck-has-max-errors-p (errors level)
"Check if there is no error in ERRORS more severe than LEVEL."
(let ((se