
Instead of restyling curved quotes for every call to ‘format’, create a new function ‘format-message’ that does the restyling, and using the new function instead of ‘format’ only in contexts where this seems appropriate. Problem reported by Dmitry Gutov and Andreas Schwab in: http://lists.gnu.org/archive/html/emacs-devel/2015-08/msg00826.html http://lists.gnu.org/archive/html/emacs-devel/2015-08/msg00827.html * doc/lispref/commands.texi (Using Interactive): * doc/lispref/control.texi (Signaling Errors, Signaling Errors): * doc/lispref/display.texi (Displaying Messages, Progress): * doc/lispref/elisp.texi: * doc/lispref/help.texi (Keys in Documentation): * doc/lispref/minibuf.texi (Minibuffer Misc): * doc/lispref/strings.texi (Formatting Strings): * etc/NEWS: Document the changes. * lisp/abbrev.el (expand-region-abbrevs): * lisp/apropos.el (apropos-library): * lisp/calc/calc-ext.el (calc-record-message) (calc-user-function-list): * lisp/calc/calc-help.el (calc-describe-key, calc-full-help): * lisp/calc/calc-lang.el (math-read-big-balance): * lisp/calc/calc-store.el (calc-edit-variable): * lisp/calc/calc-units.el (math-build-units-table-buffer): * lisp/calc/calc-yank.el (calc-edit-mode): * lisp/calendar/icalendar.el (icalendar-export-region) (icalendar--add-diary-entry): * lisp/cedet/mode-local.el (mode-local-print-binding) (mode-local-describe-bindings-2): * lisp/cedet/semantic/complete.el (semantic-completion-message): * lisp/cedet/semantic/edit.el (semantic-parse-changes-failed): * lisp/cedet/semantic/wisent/comp.el (wisent-log): * lisp/cedet/srecode/insert.el (srecode-insert-show-error-report): * lisp/descr-text.el (describe-text-properties-1, describe-char): * lisp/dframe.el (dframe-message): * lisp/dired-aux.el (dired-query): * lisp/emacs-lisp/byte-opt.el (byte-compile-log-lap-1): * lisp/emacs-lisp/bytecomp.el (byte-compile-log) (byte-compile-log-file, byte-compile-warn, byte-compile-form): * lisp/emacs-lisp/cconv.el (cconv-convert, cconv--analyze-use) (cconv-analyze-form): * lisp/emacs-lisp/check-declare.el (check-declare-warn): * lisp/emacs-lisp/checkdoc.el (checkdoc-this-string-valid-engine): * lisp/emacs-lisp/cl-macs.el (cl-symbol-macrolet): * lisp/emacs-lisp/edebug.el (edebug-format): * lisp/emacs-lisp/eieio-core.el (eieio-oref): * lisp/emacs-lisp/eldoc.el (eldoc-minibuffer-message) (eldoc-message): * lisp/emacs-lisp/elint.el (elint-file, elint-log): * lisp/emacs-lisp/find-func.el (find-function-library): * lisp/emacs-lisp/macroexp.el (macroexp--obsolete-warning): * lisp/emacs-lisp/map-ynp.el (map-y-or-n-p): * lisp/emacs-lisp/nadvice.el (advice--make-docstring): * lisp/emacs-lisp/package.el (package-compute-transaction) (package-install-button-action, package-delete-button-action) (package-menu--list-to-prompt): * lisp/emacs-lisp/timer.el (timer-event-handler): * lisp/emacs-lisp/warnings.el (lwarn, warn): * lisp/emulation/viper-cmd.el: (viper-toggle-parse-sexp-ignore-comments) (viper-kill-buffer, viper-brac-function): * lisp/emulation/viper-macs.el (viper-record-kbd-macro): * lisp/facemenu.el (facemenu-add-new-face): * lisp/faces.el (face-documentation, read-face-name) (face-read-string, read-face-font, describe-face): * lisp/files.el (find-alternate-file, hack-local-variables) (hack-one-local-variable--obsolete, write-file) (basic-save-buffer, delete-directory): * lisp/format.el (format-write-file, format-find-file) (format-insert-file): * lisp/help-fns.el (help-fns--key-bindings) (help-fns--compiler-macro, help-fns--obsolete) (help-fns--interactive-only, describe-function-1) (describe-variable): * lisp/help.el (describe-mode): * lisp/info-xref.el (info-xref-output): * lisp/info.el (Info-virtual-index-find-node) (Info-virtual-index, info-apropos): * lisp/international/kkc.el (kkc-error): * lisp/international/mule-cmds.el: (select-safe-coding-system-interactively) (select-safe-coding-system, describe-input-method): * lisp/international/mule-conf.el (code-offset): * lisp/international/mule-diag.el (describe-character-set) (list-input-methods-1): * lisp/international/quail.el (quail-error): * lisp/minibuffer.el (minibuffer-message): * lisp/mpc.el (mpc--debug): * lisp/msb.el (msb--choose-menu): * lisp/net/ange-ftp.el (ange-ftp-message): * lisp/net/gnutls.el (gnutls-message-maybe): * lisp/net/newst-backend.el (newsticker--sentinel-work): * lisp/net/newst-treeview.el (newsticker--treeview-load): * lisp/net/nsm.el (nsm-query-user): * lisp/net/rlogin.el (rlogin): * lisp/net/soap-client.el (soap-warning): * lisp/net/tramp.el (tramp-debug-message): * lisp/nxml/nxml-outln.el (nxml-report-outline-error): * lisp/nxml/nxml-parse.el (nxml-parse-error): * lisp/nxml/rng-cmpct.el (rng-c-error): * lisp/nxml/rng-match.el (rng-compile-error): * lisp/nxml/rng-uri.el (rng-uri-error): * lisp/obsolete/iswitchb.el (iswitchb-possible-new-buffer): * lisp/org/org-ctags.el: (org-ctags-ask-rebuild-tags-file-then-find-tag): * lisp/proced.el (proced-log): * lisp/progmodes/ebnf2ps.el (ebnf-log): * lisp/progmodes/flymake.el (flymake-log): * lisp/progmodes/vhdl-mode.el (vhdl-warning-when-idle): * lisp/replace.el (occur-1): * lisp/simple.el (execute-extended-command) (undo-outer-limit-truncate, define-alternatives): * lisp/startup.el (command-line): * lisp/subr.el (error, user-error, add-to-list): * lisp/tutorial.el (tutorial--describe-nonstandard-key) (tutorial--find-changed-keys): * src/callint.c (Fcall_interactively): * src/editfns.c (Fmessage, Fmessage_box): Restyle the quotes of format strings intended for use as a diagnostic, when restyling seems appropriate. * lisp/subr.el (format-message): New function. * src/doc.c (Finternal__text_restyle): New function. (syms_of_doc): Define it.
353 lines
15 KiB
EmacsLisp
353 lines
15 KiB
EmacsLisp
;;; check-declare.el --- Check declare-function statements
|
||
|
||
;; Copyright (C) 2007-2015 Free Software Foundation, Inc.
|
||
|
||
;; Author: Glenn Morris <rgm@gnu.org>
|
||
;; Keywords: lisp, tools, maint
|
||
|
||
;; This file is part of GNU Emacs.
|
||
|
||
;; GNU Emacs 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.
|
||
|
||
;; GNU Emacs 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 GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
|
||
|
||
;;; Commentary:
|
||
|
||
;; The byte-compiler often warns about undefined functions that you
|
||
;; know will actually be defined when it matters. The `declare-function'
|
||
;; statement allows you to suppress these warnings. This package
|
||
;; checks that all such statements in a file or directory are accurate.
|
||
;; The entry points are `check-declare-file' and `check-declare-directory'.
|
||
|
||
;; For more information, see Info node `(elisp)Declaring Functions'.
|
||
|
||
;;; TODO:
|
||
|
||
;; 1. Warn about functions marked as obsolete, eg
|
||
;; password-read-and-add in smime.el.
|
||
;; 2. defmethod, defclass argument checking.
|
||
;; 3. defclass also defines -p and -child-p.
|
||
|
||
;;; Code:
|
||
|
||
(defconst check-declare-warning-buffer "*Check Declarations Warnings*"
|
||
"Name of buffer used to display any `check-declare' warnings.")
|
||
|
||
(defun check-declare-locate (file basefile)
|
||
"Return the full path of FILE.
|
||
Expands files with a \".c\" or \".m\" extension relative to the Emacs
|
||
\"src/\" directory. Otherwise, `locate-library' searches for FILE.
|
||
If that fails, expands FILE relative to BASEFILE's directory part.
|
||
The returned file might not exist. If FILE has an \"ext:\" prefix, so does
|
||
the result."
|
||
(let ((ext (string-match "^ext:" file))
|
||
tfile)
|
||
(if ext
|
||
(setq file (substring file 4)))
|
||
(setq file
|
||
(if (member (file-name-extension file) '("c" "m"))
|
||
(expand-file-name file (expand-file-name "src" source-directory))
|
||
(if (setq tfile (locate-library file))
|
||
(progn
|
||
(setq tfile
|
||
(replace-regexp-in-string "\\.elc\\'" ".el" tfile))
|
||
(if (and (not (file-exists-p tfile))
|
||
(file-exists-p (concat tfile ".gz")))
|
||
(concat tfile ".gz")
|
||
tfile))
|
||
(setq tfile (expand-file-name file
|
||
(file-name-directory basefile)))
|
||
(if (or (file-exists-p tfile)
|
||
(string-match "\\.el\\'" tfile))
|
||
tfile
|
||
(concat tfile ".el")))))
|
||
(if ext (concat "ext:" file)
|
||
file)))
|
||
|
||
(defun check-declare-scan (file)
|
||
"Scan FILE for `declare-function' calls.
|
||
Return a list with elements of the form (FNFILE FN ARGLIST FILEONLY),
|
||
where only the first two elements need be present. This claims that FNFILE
|
||
defines FN, with ARGLIST. FILEONLY non-nil means only check that FNFILE
|
||
exists, not that it defines FN. This is for function definitions that we
|
||
don't know how to recognize (e.g. some macros)."
|
||
(let ((m (format "Scanning %s..." file))
|
||
alist form len fn fnfile arglist fileonly)
|
||
(message "%s" m)
|
||
(with-temp-buffer
|
||
(insert-file-contents file)
|
||
;; FIXME we could theoretically be inside a string.
|
||
(while (re-search-forward "^[ \t]*\\((declare-function\\)[ \t\n]" nil t)
|
||
(goto-char (match-beginning 1))
|
||
(if (and (setq form (ignore-errors (read (current-buffer))))
|
||
;; Exclude element of byte-compile-initial-macro-environment.
|
||
(or (listp (cdr form)) (setq form nil))
|
||
(> (setq len (length form)) 2)
|
||
(< len 6)
|
||
(symbolp (setq fn (cadr form)))
|
||
(setq fn (symbol-name fn)) ; later we use as a search string
|
||
(stringp (setq fnfile (nth 2 form)))
|
||
(setq fnfile (check-declare-locate fnfile
|
||
(expand-file-name file)))
|
||
;; Use t to distinguish unspecified arglist from empty one.
|
||
(or (eq t (setq arglist (if (> len 3)
|
||
(nth 3 form)
|
||
t)))
|
||
(listp arglist))
|
||
(symbolp (setq fileonly (nth 4 form))))
|
||
(setq alist (cons (list fnfile fn arglist fileonly) alist))
|
||
;; FIXME make this more noticeable.
|
||
(if form (message "Malformed declaration for ‘%s’" (cadr form))))))
|
||
(message "%sdone" m)
|
||
alist))
|
||
|
||
(defun check-declare-errmsg (errlist &optional full)
|
||
"Return a string with the number of errors in ERRLIST, if any.
|
||
Normally just counts the number of elements in ERRLIST.
|
||
With optional argument FULL, sums the number of elements in each element."
|
||
(if errlist
|
||
(let ((l (length errlist)))
|
||
(when full
|
||
(setq l 0)
|
||
(dolist (e errlist)
|
||
(setq l (+ l (1- (length e))))))
|
||
(format "%d problem%s found" l (if (= l 1) "" "s")))
|
||
"OK"))
|
||
|
||
(autoload 'byte-compile-arglist-signature "bytecomp")
|
||
|
||
(defgroup check-declare nil
|
||
"Check declare-function statements."
|
||
:group 'tools)
|
||
|
||
(defcustom check-declare-ext-errors nil
|
||
"When non-nil, warn about functions not found in :ext."
|
||
:type 'boolean)
|
||
|
||
(defun check-declare-verify (fnfile fnlist)
|
||
"Check that FNFILE contains function definitions matching FNLIST.
|
||
Each element of FNLIST has the form (FILE FN ARGLIST FILEONLY), where
|
||
only the first two elements need be present. This means FILE claimed FN
|
||
was defined in FNFILE with the specified ARGLIST. FILEONLY non-nil means
|
||
to only check that FNFILE exists, not that it actually defines FN.
|
||
|
||
Returns nil if all claims are found to be true, otherwise a list
|
||
of errors with elements of the form \(FILE FN TYPE), where TYPE
|
||
is a string giving details of the error."
|
||
(let ((m (format "Checking %s..." fnfile))
|
||
(cflag (member (file-name-extension fnfile) '("c" "m")))
|
||
(ext (string-match "^ext:" fnfile))
|
||
re fn sig siglist arglist type errlist minargs maxargs)
|
||
(message "%s" m)
|
||
(if ext
|
||
(setq fnfile (substring fnfile 4)))
|
||
(if (file-regular-p fnfile)
|
||
(with-temp-buffer
|
||
(insert-file-contents fnfile)
|
||
;; defsubst's don't _have_ to be known at compile time.
|
||
(setq re (format (if cflag
|
||
"^[ \t]*\\(DEFUN\\)[ \t]*([ \t]*\"%s\""
|
||
"^[ \t]*(\\(fset[ \t]+'\\|\
|
||
cl-def\\(?:generic\\|method\\)\\|\
|
||
def\\(?:un\\|subst\\|foo\\|method\\|class\\|\
|
||
ine-\\(?:derived\\|generic\\|\\(?:global\\(?:ized\\)?-\\)?minor\\)-mode\\|\
|
||
\\(?:ine-obsolete-function-\\)?alias[ \t]+'\\|\
|
||
ine-overloadable-function\\)\\)\
|
||
\[ \t]*%s\\([ \t;]+\\|$\\)")
|
||
(regexp-opt (mapcar 'cadr fnlist) t)))
|
||
(while (re-search-forward re nil t)
|
||
(skip-chars-forward " \t\n")
|
||
(setq fn (match-string 2)
|
||
type (match-string 1)
|
||
;; (min . max) for a fixed number of arguments, or
|
||
;; arglists with optional elements.
|
||
;; (min) for arglists with &rest.
|
||
;; sig = 'err means we could not find an arglist.
|
||
sig (cond (cflag
|
||
(or
|
||
(when (search-forward "," nil t 3)
|
||
(skip-chars-forward " \t\n")
|
||
;; Assuming minargs and maxargs on same line.
|
||
(when (looking-at "\\([0-9]+\\)[ \t]*,[ \t]*\
|
||
\\([0-9]+\\|MANY\\|UNEVALLED\\)")
|
||
(setq minargs (string-to-number
|
||
(match-string 1))
|
||
maxargs (match-string 2))
|
||
(cons minargs (unless (string-match "[^0-9]"
|
||
maxargs)
|
||
(string-to-number
|
||
maxargs)))))
|
||
'err))
|
||
((string-match
|
||
"\\`define-\\(derived\\|generic\\)-mode\\'"
|
||
type)
|
||
'(0 . 0))
|
||
((string-match
|
||
"\\`define\\(-global\\(ized\\)?\\)?-minor-mode\\'"
|
||
type)
|
||
'(0 . 1))
|
||
;; Prompt to update.
|
||
((string-match
|
||
"\\`define-obsolete-function-alias\\>"
|
||
type)
|
||
'obsolete)
|
||
;; Can't easily check arguments in these cases.
|
||
((string-match "\\`\\(def\\(alias\\|class\\)\\|\
|
||
fset\\|\\(?:cl-\\)?defmethod\\)\\>" type)
|
||
t)
|
||
((looking-at "\\((\\|nil\\)")
|
||
(byte-compile-arglist-signature
|
||
(read (current-buffer))))
|
||
(t
|
||
'err))
|
||
;; alist of functions and arglist signatures.
|
||
siglist (cons (cons fn sig) siglist)))))
|
||
(dolist (e fnlist)
|
||
(setq arglist (nth 2 e)
|
||
type
|
||
(if (not re)
|
||
"file not found"
|
||
(if (not (setq sig (assoc (cadr e) siglist)))
|
||
(unless (nth 3 e) ; fileonly
|
||
"function not found")
|
||
(setq sig (cdr sig))
|
||
(cond ((eq sig 'obsolete) ; check even when no arglist specified
|
||
"obsolete alias")
|
||
;; arglist t means no arglist specified, as
|
||
;; opposed to an empty arglist.
|
||
((eq arglist t) nil)
|
||
((eq sig t) nil) ; eg defalias - can't check arguments
|
||
((eq sig 'err)
|
||
"arglist not found") ; internal error
|
||
((not (equal (byte-compile-arglist-signature
|
||
arglist)
|
||
sig))
|
||
"arglist mismatch")))))
|
||
(when type
|
||
(setq errlist (cons (list (car e) (cadr e) type) errlist))))
|
||
(message "%s%s" m
|
||
(if (or re (or check-declare-ext-errors
|
||
(not ext)))
|
||
(check-declare-errmsg errlist)
|
||
(progn
|
||
(setq errlist nil)
|
||
"skipping external file")))
|
||
errlist))
|
||
|
||
(defun check-declare-sort (alist)
|
||
"Sort a list with elements FILE (FNFILE ...).
|
||
Returned list has elements FNFILE (FILE ...)."
|
||
(let (file fnfile rest sort a)
|
||
(dolist (e alist)
|
||
(setq file (car e))
|
||
(dolist (f (cdr e))
|
||
(setq fnfile (car f)
|
||
rest (cdr f))
|
||
(if (setq a (assoc fnfile sort))
|
||
(setcdr a (append (cdr a) (list (cons file rest))))
|
||
(setq sort (cons (list fnfile (cons file rest)) sort)))))
|
||
sort))
|
||
|
||
(defun check-declare-warn (file fn fnfile type)
|
||
"Warn that FILE made a false claim about FN in FNFILE.
|
||
TYPE is a string giving the nature of the error. Warning is displayed in
|
||
`check-declare-warning-buffer'."
|
||
(let ((warning-prefix-function
|
||
(lambda (level entry)
|
||
(let ((line 0)
|
||
(col 0))
|
||
(insert
|
||
(with-current-buffer (find-file-noselect file)
|
||
(goto-char (point-min))
|
||
(when (re-search-forward
|
||
(format "(declare-function[ \t\n]+%s" fn) nil t)
|
||
(goto-char (match-beginning 0))
|
||
(setq line (line-number-at-pos))
|
||
(setq col (1+ (current-column))))
|
||
(format "%s:%d:%d:"
|
||
(file-name-nondirectory file)
|
||
line col))))
|
||
entry))
|
||
(warning-fill-prefix " "))
|
||
(display-warning 'check-declare
|
||
(format-message "said ‘%s’ was defined in %s: %s"
|
||
fn (file-name-nondirectory fnfile) type)
|
||
nil check-declare-warning-buffer)))
|
||
|
||
(declare-function compilation-forget-errors "compile" ())
|
||
|
||
(defun check-declare-files (&rest files)
|
||
"Check veracity of all `declare-function' statements in FILES.
|
||
Return a list of any errors found."
|
||
(let (alist err errlist)
|
||
(dolist (file files)
|
||
(setq alist (cons (cons file (check-declare-scan file)) alist)))
|
||
;; Sort so that things are ordered by the files supposed to
|
||
;; contain the defuns.
|
||
(dolist (e (check-declare-sort alist))
|
||
(if (setq err (check-declare-verify (car e) (cdr e)))
|
||
(setq errlist (cons (cons (car e) err) errlist))))
|
||
(setq errlist (nreverse errlist))
|
||
(if (get-buffer check-declare-warning-buffer)
|
||
(kill-buffer check-declare-warning-buffer))
|
||
(with-current-buffer (get-buffer-create check-declare-warning-buffer)
|
||
(unless (derived-mode-p 'compilation-mode)
|
||
(compilation-mode))
|
||
(let ((inhibit-read-only t))
|
||
(insert "\f\n"))
|
||
(compilation-forget-errors))
|
||
;; Sort back again so that errors are ordered by the files
|
||
;; containing the declare-function statements.
|
||
(dolist (e (check-declare-sort errlist))
|
||
(dolist (f (cdr e))
|
||
(check-declare-warn (car e) (cadr f) (car f) (nth 2 f))))
|
||
errlist))
|
||
|
||
;;;###autoload
|
||
(defun check-declare-file (file)
|
||
"Check veracity of all `declare-function' statements in FILE.
|
||
See `check-declare-directory' for more information."
|
||
(interactive "fFile to check: ")
|
||
(or (file-exists-p file)
|
||
(error "File ‘%s’ not found" file))
|
||
(let ((m (format "Checking %s..." file))
|
||
errlist)
|
||
(message "%s" m)
|
||
(setq errlist (check-declare-files file))
|
||
(message "%s%s" m (check-declare-errmsg errlist))
|
||
errlist))
|
||
|
||
;;;###autoload
|
||
(defun check-declare-directory (root)
|
||
"Check veracity of all `declare-function' statements under directory ROOT.
|
||
Returns non-nil if any false statements are found."
|
||
(interactive "DDirectory to check: ")
|
||
(or (file-directory-p (setq root (expand-file-name root)))
|
||
(error "Directory ‘%s’ not found" root))
|
||
(let ((m "Checking ‘declare-function’ statements...")
|
||
(m2 "Finding files with declarations...")
|
||
errlist files)
|
||
(message "%s" m)
|
||
(message "%s" m2)
|
||
(setq files (process-lines find-program root
|
||
"-name" "*.el"
|
||
"-exec" grep-program
|
||
"-l" "^[ \t]*(declare-function" "{}" ";"))
|
||
(message "%s%d found" m2 (length files))
|
||
(when files
|
||
(setq errlist (apply 'check-declare-files files))
|
||
(message "%s%s" m (check-declare-errmsg errlist t))
|
||
errlist)))
|
||
|
||
(provide 'check-declare)
|
||
|
||
;;; check-declare.el ends here.
|