aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDamien Cassou2019-12-27 15:35:52 +0100
committerEli Zaretskii2020-01-16 20:41:48 +0200
commit25adbc4a5ecc3e16625c0171607e3153bbdf7ab1 (patch)
treeafcf3388aaa4a4e33fa3da39ff7dead458bfd7ee
parent1b1aaf37dc745570ec5202c8cc596591f4afa38d (diff)
downloademacs-25adbc4a5ecc3e16625c0171607e3153bbdf7ab1.tar.gz
emacs-25adbc4a5ecc3e16625c0171607e3153bbdf7ab1.zip
Add unattended spell-checking to checkdoc
This commit makes checkdoc capable of spell-checking even when the user isn't using it interactively. When TAKE-NOTES is non-nil, checkdoc will run spell-checking (with ispell) and report spelling mistakes. Fixes: (bug#38583). * lisp/textmodes/ispell.el (ispell-word): Extract part of it to `ispell--run-on-word`. (ispell--run-on-word): New function, extracted from `ispell-word`. (ispell-error-checking-word): New function. (ispell-correct-p): New function. Use `ispell--run-on-word` and `ispell-error-checking-word`. * lisp/emacs-lisp/checkdoc.el (checkdoc-current-buffer): Pass TAKE-NOTES to `checkdoc-start`. (checkdoc-continue): Pass TAKE-NOTES to `checkdoc-this-string-valid`. (checkdoc-this-string-valid): Add optional argument TAKE-NOTES and pass it to `checkdoc-this-string-valid-engine`. (checkdoc-this-string-valid-engine): Add optional argument TAKE-NOTES and pass it to `checkdoc-ispell-docstring-engine`. (checkdoc-ispell-init): Call `ispell-set-spellchecker-params` and `ispell-accept-buffer-local-defs`. These calls are required to properly use ispell. The problem went unnoticed until now because checkdoc was only using ispell through the high-level command `ispell-word` which takes care of all the initialization for the user. (checkdoc-ispell-docstring-engine): Add optional argument TAKE-NOTES to force reporting of spell-checking errors. Throw error when (checkdoc-ispell-init) fails configuring ispell. Replace a few (if cond nil body) with (unless cond body). Replace (let ((var nil))) with (let (var)). Replace (if (not (eq checkdoc-autofix-flag 'never)) body) with just body because `checkdoc-autofix-flag` is checked at the beginning of the function.
-rw-r--r--lisp/emacs-lisp/checkdoc.el118
-rw-r--r--lisp/textmodes/ispell.el50
2 files changed, 106 insertions, 62 deletions
diff --git a/lisp/emacs-lisp/checkdoc.el b/lisp/emacs-lisp/checkdoc.el
index 93b9ffbe38b..cbad6f05541 100644
--- a/lisp/emacs-lisp/checkdoc.el
+++ b/lisp/emacs-lisp/checkdoc.el
@@ -849,7 +849,7 @@ otherwise stop after the first error."
849 ;; every test is responsible for returning the cursor. 849 ;; every test is responsible for returning the cursor.
850 (or (and buffer-file-name ;; only check comments in a file 850 (or (and buffer-file-name ;; only check comments in a file
851 (checkdoc-comments)) 851 (checkdoc-comments))
852 (checkdoc-start) 852 (checkdoc-start take-notes)
853 (checkdoc-message-text) 853 (checkdoc-message-text)
854 (checkdoc-rogue-spaces) 854 (checkdoc-rogue-spaces)
855 (when checkdoc-package-keywords-flag 855 (when checkdoc-package-keywords-flag
@@ -902,7 +902,7 @@ buffer and save warnings in a separate buffer."
902 ;; the user is navigating down through the buffer. 902 ;; the user is navigating down through the buffer.
903 (while (and (not wrong) (checkdoc-next-docstring)) 903 (while (and (not wrong) (checkdoc-next-docstring))
904 ;; OK, let's look at the doc string. 904 ;; OK, let's look at the doc string.
905 (setq msg (checkdoc-this-string-valid)) 905 (setq msg (checkdoc-this-string-valid take-notes))
906 (if msg (setq wrong (point))))) 906 (if msg (setq wrong (point)))))
907 (if wrong 907 (if wrong
908 (progn 908 (progn
@@ -1284,12 +1284,15 @@ checking of documentation strings.
1284 1284
1285;;; Checking engines 1285;;; Checking engines
1286;; 1286;;
1287(defun checkdoc-this-string-valid () 1287(defun checkdoc-this-string-valid (&optional take-notes)
1288 "Return a message string if the current doc string is invalid. 1288 "Return a message string if the current doc string is invalid.
1289Check for style only, such as the first line always being a complete 1289Check for style only, such as the first line always being a complete
1290sentence, whitespace restrictions, and making sure there are no 1290sentence, whitespace restrictions, and making sure there are no
1291hard-coded key-codes such as C-[char] or mouse-[number] in the comment. 1291hard-coded key-codes such as C-[char] or mouse-[number] in the comment.
1292See the style guide in the Emacs Lisp manual for more details." 1292See the style guide in the Emacs Lisp manual for more details.
1293
1294With a non-nil TAKE-NOTES, store all errors found in a warnings
1295buffer, otherwise stop after the first error."
1293 1296
1294 ;; Jump over comments between the last object and the doc string 1297 ;; Jump over comments between the last object and the doc string
1295 (while (looking-at "[ \t\n]*;") 1298 (while (looking-at "[ \t\n]*;")
@@ -1366,13 +1369,16 @@ documentation string")
1366 (point) (+ (point) 1) t))))) 1369 (point) (+ (point) 1) t)))))
1367 (if (and (not err) (= (following-char) ?\")) 1370 (if (and (not err) (= (following-char) ?\"))
1368 (with-syntax-table checkdoc-syntax-table 1371 (with-syntax-table checkdoc-syntax-table
1369 (checkdoc-this-string-valid-engine fp)) 1372 (checkdoc-this-string-valid-engine fp take-notes))
1370 err))) 1373 err)))
1371 1374
1372(defun checkdoc-this-string-valid-engine (fp) 1375(defun checkdoc-this-string-valid-engine (fp &optional take-notes)
1373 "Return an error list or string if the current doc string is invalid. 1376 "Return an error list or string if the current doc string is invalid.
1374Depends on `checkdoc-this-string-valid' to reset the syntax table so that 1377Depends on `checkdoc-this-string-valid' to reset the syntax table so that
1375regexp short cuts work. FP is the function defun information." 1378regexp short cuts work. FP is the function defun information.
1379
1380With a non-nil TAKE-NOTES, store all errors found in a warnings
1381buffer, otherwise stop after the first error."
1376 (let ((case-fold-search nil) 1382 (let ((case-fold-search nil)
1377 ;; Use a marker so if an early check modifies the text, 1383 ;; Use a marker so if an early check modifies the text,
1378 ;; we won't accidentally lose our place. This could cause 1384 ;; we won't accidentally lose our place. This could cause
@@ -1864,7 +1870,7 @@ Replace with \"%s\"? " original replace)
1864 ;; Make sure the doc string has correctly spelled English words 1870 ;; Make sure the doc string has correctly spelled English words
1865 ;; in it. This function is extracted due to its complexity, 1871 ;; in it. This function is extracted due to its complexity,
1866 ;; and reliance on the Ispell program. 1872 ;; and reliance on the Ispell program.
1867 (checkdoc-ispell-docstring-engine e) 1873 (checkdoc-ispell-docstring-engine e take-notes)
1868 ;; User supplied checks 1874 ;; User supplied checks
1869 (save-excursion (checkdoc-run-hooks 'checkdoc-style-functions fp e)) 1875 (save-excursion (checkdoc-run-hooks 'checkdoc-style-functions fp e))
1870 ;; Done! 1876 ;; Done!
@@ -2090,6 +2096,10 @@ If the offending word is in a piece of quoted text, then it is skipped."
2090;; 2096;;
2091(defvar ispell-process) 2097(defvar ispell-process)
2092(declare-function ispell-buffer-local-words "ispell" ()) 2098(declare-function ispell-buffer-local-words "ispell" ())
2099(declare-function ispell-correct-p "ispell" ())
2100(declare-function ispell-set-spellchecker-params "ispell" ())
2101(declare-function ispell-accept-buffer-local-defs "ispell" ())
2102(declare-function ispell-error-checking-word "ispell" (word))
2093 2103
2094(defun checkdoc-ispell-init () 2104(defun checkdoc-ispell-init ()
2095 "Initialize Ispell process (default version) with Lisp words. 2105 "Initialize Ispell process (default version) with Lisp words.
@@ -2100,58 +2110,66 @@ nil."
2100 (unless ispell-process 2110 (unless ispell-process
2101 (condition-case nil 2111 (condition-case nil
2102 (progn 2112 (progn
2103 (ispell-buffer-local-words) 2113 (ispell-set-spellchecker-params) ; Initialize variables and dicts alists
2114 (ispell-accept-buffer-local-defs) ; use the correct dictionary
2104 ;; This code copied in part from ispell.el Emacs 19.34 2115 ;; This code copied in part from ispell.el Emacs 19.34
2105 (dolist (w checkdoc-ispell-lisp-words) 2116 (dolist (w checkdoc-ispell-lisp-words)
2106 (process-send-string ispell-process (concat "@" w "\n")))) 2117 (process-send-string ispell-process (concat "@" w "\n"))))
2107 (error (setq checkdoc-spellcheck-documentation-flag nil))))) 2118 (error (setq checkdoc-spellcheck-documentation-flag nil)))))
2108 2119
2109(defun checkdoc-ispell-docstring-engine (end) 2120(defun checkdoc-ispell-docstring-engine (end &optional take-notes)
2110 "Run the Ispell tools on the doc string between point and END. 2121 "Run the Ispell tools on the doc string between point and END.
2111Since Ispell isn't Lisp-smart, we must pre-process the doc string 2122Since Ispell isn't Lisp-smart, we must pre-process the doc string
2112before using the Ispell engine on it." 2123before using the Ispell engine on it.
2113 (if (or (not checkdoc-spellcheck-documentation-flag) 2124
2114 ;; If the user wants no questions or fixing, then we must 2125With a non-nil TAKE-NOTES, store all errors found in a warnings
2115 ;; disable spell checking as not useful. 2126buffer, otherwise stop after the first error."
2116 (not checkdoc-autofix-flag) 2127 (when (and checkdoc-spellcheck-documentation-flag
2117 (eq checkdoc-autofix-flag 'never)) 2128 ;; If the user wants no questions or fixing, then we must
2118 nil 2129 ;; disable spell checking as not useful.
2130 (or take-notes
2131 (and checkdoc-autofix-flag
2132 (not (eq checkdoc-autofix-flag 'never)))))
2119 (checkdoc-ispell-init) 2133 (checkdoc-ispell-init)
2134 (unless checkdoc-spellcheck-documentation-flag
2135 ;; this happens when (checkdoc-ispell-init) can't start `ispell-program-name'
2136 (user-error "No spellchecker installed: check the variable `ispell-program-name'."))
2120 (save-excursion 2137 (save-excursion
2121 (skip-chars-forward "^a-zA-Z") 2138 (skip-chars-forward "^a-zA-Z")
2122 (let ((word nil) (sym nil) (case-fold-search nil) (err nil)) 2139 (let (word sym case-fold-search err word-beginning word-end)
2123 (while (and (not err) (< (point) end)) 2140 (while (and (not err) (< (point) end))
2124 (if (save-excursion (forward-char -1) (looking-at "[('`]")) 2141 (if (save-excursion (forward-char -1) (looking-at "[('`]"))
2125 ;; Skip lists describing meta-syntax, or bound variables 2142 ;; Skip lists describing meta-syntax, or bound variables
2126 (forward-sexp 1) 2143 (forward-sexp 1)
2127 (setq word (buffer-substring-no-properties 2144 (setq word-beginning (point)
2128 (point) (progn 2145 word-end (progn
2129 (skip-chars-forward "a-zA-Z-") 2146 (skip-chars-forward "a-zA-Z-")
2130 (point))) 2147 (point))
2131 sym (intern-soft word)) 2148 word (buffer-substring-no-properties word-beginning word-end)
2132 (if (and sym (or (boundp sym) (fboundp sym))) 2149 sym (intern-soft word))
2133 ;; This is probably repetitive in most cases, but not always. 2150 (unless (and sym (or (boundp sym) (fboundp sym)))
2134 nil 2151 ;; Find out how we spell-check this word.
2135 ;; Find out how we spell-check this word. 2152 (unless (or
2136 (if (or 2153 ;; All caps w/ option th, or s tacked on the end
2137 ;; All caps w/ option th, or s tacked on the end 2154 ;; for pluralization or number.
2138 ;; for pluralization or number. 2155 (string-match "^[A-Z][A-Z]+\\(s\\|th\\)?$" word)
2139 (string-match "^[A-Z][A-Z]+\\(s\\|th\\)?$" word) 2156 (looking-at "}") ; a keymap expression
2140 (looking-at "}") ; a keymap expression 2157 )
2141 ) 2158 (save-excursion
2142 nil 2159 (let ((lk last-input-event))
2143 (save-excursion 2160 (if take-notes
2144 (if (not (eq checkdoc-autofix-flag 'never)) 2161 (progn
2145 (let ((lk last-input-event)) 2162 (unless (ispell-correct-p)
2146 (ispell-word nil t) 2163 (checkdoc-create-error
2147 (if (not (equal last-input-event lk)) 2164 (ispell-error-checking-word word)
2148 (progn 2165 word-beginning word-end)))
2149 (sit-for 0) 2166 (ispell-word nil t))
2150 (message "Continuing...")))) 2167 (if (not (equal last-input-event lk))
2151 ;; Nothing here. 2168 (progn
2152 ))))) 2169 (sit-for 0)
2153 (skip-chars-forward "^a-zA-Z")) 2170 (message "Continuing..."))))))))
2154 err)))) 2171 (skip-chars-forward "^a-zA-Z"))
2172 err))))
2155 2173
2156;;; Rogue space checking engine 2174;;; Rogue space checking engine
2157;; 2175;;
diff --git a/lisp/textmodes/ispell.el b/lisp/textmodes/ispell.el
index 53a45433085..c06f3915faa 100644
--- a/lisp/textmodes/ispell.el
+++ b/lisp/textmodes/ispell.el
@@ -1951,18 +1951,7 @@ quit spell session exited."
1951 (or quietly 1951 (or quietly
1952 (message "Checking spelling of %s..." 1952 (message "Checking spelling of %s..."
1953 (funcall ispell-format-word-function word))) 1953 (funcall ispell-format-word-function word)))
1954 (ispell-send-string "%\n") ; put in verbose mode 1954 (setq poss (ispell--run-on-word word))
1955 (ispell-send-string (concat "^" word "\n"))
1956 ;; wait until ispell has processed word
1957 (while (progn
1958 (ispell-accept-output)
1959 (not (string= "" (car ispell-filter)))))
1960 ;;(ispell-send-string "!\n") ;back to terse mode.
1961 (setq ispell-filter (cdr ispell-filter)) ; remove extra \n
1962 (if (and ispell-filter (listp ispell-filter))
1963 (if (> (length ispell-filter) 1)
1964 (error "Ispell and its process have different character maps")
1965 (setq poss (ispell-parse-output (car ispell-filter)))))
1966 (cond ((eq poss t) 1955 (cond ((eq poss t)
1967 (or quietly 1956 (or quietly
1968 (message "%s is correct" 1957 (message "%s is correct"
@@ -2024,6 +2013,43 @@ quit spell session exited."
2024 (goto-char cursor-location) ; return to original location 2013 (goto-char cursor-location) ; return to original location
2025 replace)))) 2014 replace))))
2026 2015
2016(defun ispell--run-on-word (word)
2017 "Run ispell on WORD."
2018 (ispell-send-string "%\n") ; put in verbose mode
2019 (ispell-send-string (concat "^" word "\n"))
2020 ;; wait until ispell has processed word
2021 (while (progn
2022 (ispell-accept-output)
2023 (not (string= "" (car ispell-filter)))))
2024 (setq ispell-filter (cdr ispell-filter))
2025 (when (and ispell-filter (listp ispell-filter))
2026 (if (> (length ispell-filter) 1)
2027 (error "Ispell and its processs have different character maps: %s" ispell-filter)
2028 (ispell-parse-output (car ispell-filter)))))
2029
2030(defun ispell-error-checking-word (word)
2031 "Return a string describing that checking for WORD failed."
2032 (format "Error checking word %s using %s with %s dictionary"
2033 (funcall ispell-format-word-function word)
2034 (file-name-nondirectory ispell-program-name)
2035 (or ispell-current-dictionary "default")))
2036
2037(defun ispell-correct-p (&optional following)
2038 "Return t if the word at point is correct. Nil otherwise.
2039
2040If optional argument FOLLOWING is non-nil then the following
2041word (rather than preceding) is checked when the cursor is not
2042over a word."
2043 (save-excursion
2044 ;; reset ispell-filter so it only contains the result of
2045 ;; spell-checking the current-word:
2046 (setq ispell-filter nil)
2047 (let* ((word-and-boundaries (ispell-get-word following))
2048 (word (car word-and-boundaries))
2049 (poss (ispell--run-on-word word)))
2050 (unless poss (error (ispell-error-checking-word word)))
2051 (or (eq poss t)
2052 (stringp poss)))))
2027 2053
2028(defun ispell-get-word (following &optional extra-otherchars) 2054(defun ispell-get-word (following &optional extra-otherchars)
2029 "Return the word for spell-checking according to ispell syntax. 2055 "Return the word for spell-checking according to ispell syntax.