Add 2011 to FSF/AIST copyright years.
[bpt/emacs.git] / lisp / emacs-lisp / check-declare.el
CommitLineData
87b8db2b
GM
1;;; check-declare.el --- Check declare-function statements
2
5df4f04c 3;; Copyright (C) 2007, 2008, 2009, 2010, 2011 Free Software Foundation, Inc.
87b8db2b
GM
4
5;; Author: Glenn Morris <rgm@gnu.org>
6;; Keywords: lisp, tools, maint
7
8;; This file is part of GNU Emacs.
9
d6cba7ae 10;; GNU Emacs is free software: you can redistribute it and/or modify
87b8db2b 11;; it under the terms of the GNU General Public License as published by
d6cba7ae
GM
12;; the Free Software Foundation, either version 3 of the License, or
13;; (at your option) any later version.
87b8db2b
GM
14
15;; GNU Emacs is distributed in the hope that it will be useful,
16;; but WITHOUT ANY WARRANTY; without even the implied warranty of
17;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18;; GNU General Public License for more details.
19
20;; You should have received a copy of the GNU General Public License
d6cba7ae 21;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
87b8db2b
GM
22
23;;; Commentary:
24
25;; The byte-compiler often warns about undefined functions that you
26;; know will actually be defined when it matters. The `declare-function'
27;; statement allows you to suppress these warnings. This package
28;; checks that all such statements in a file or directory are accurate.
29;; The entry points are `check-declare-file' and `check-declare-directory'.
30
faf7b396
GM
31;; For more information, see Info node `elisp(Declaring Functions)'.
32
f3a4724d
GM
33;;; TODO:
34
e49337ea
GM
35;; 1. Warn about functions marked as obsolete, eg
36;; password-read-and-add in smime.el.
ae715515
GM
37;; 2. defmethod, defclass argument checking.
38;; 3. defclass also defines -p and -child-p.
e49337ea 39
87b8db2b
GM
40;;; Code:
41
42(defconst check-declare-warning-buffer "*Check Declarations Warnings*"
43 "Name of buffer used to display any `check-declare' warnings.")
44
7d4184ba
GM
45(defun check-declare-locate (file basefile)
46 "Return the full path of FILE.
75d37c3d 47Expands files with a \".c\" or \".m\" extension relative to the Emacs
4ab4de9c
GM
48\"src/\" directory. Otherwise, `locate-library' searches for FILE.
49If that fails, expands FILE relative to BASEFILE's directory part.
122bcd7e
GM
50The returned file might not exist. If FILE has an \"ext:\" prefix, so does
51the result."
52 (let ((ext (string-match "^ext:" file))
53 tfile)
54 (if ext
55 (setq file (substring file 4)))
56 (setq file
75d37c3d 57 (if (member (file-name-extension file) '("c" "m"))
122bcd7e 58 (expand-file-name file (expand-file-name "src" source-directory))
8360fce0 59 (if (setq tfile (locate-library file))
122bcd7e
GM
60 (progn
61 (setq tfile
62 (replace-regexp-in-string "\\.elc\\'" ".el" tfile))
63 (if (and (not (file-exists-p tfile))
64 (file-exists-p (concat tfile ".gz")))
65 (concat tfile ".gz")
66 tfile))
67 (setq tfile (expand-file-name file
68 (file-name-directory basefile)))
69 (if (or (file-exists-p tfile)
70 (string-match "\\.el\\'" tfile))
71 tfile
72 (concat tfile ".el")))))
73 (if ext (concat "ext:" file)
74 file)))
7d4184ba 75
87b8db2b
GM
76(defun check-declare-scan (file)
77 "Scan FILE for `declare-function' calls.
630456e6
GM
78Return a list with elements of the form (FNFILE FN ARGLIST FILEONLY),
79where only the first two elements need be present. This claims that FNFILE
80defines FN, with ARGLIST. FILEONLY non-nil means only check that FNFILE
81exists, not that it defines FN. This is for function definitions that we
82don't know how to recognize (e.g. some macros)."
87b8db2b 83 (let ((m (format "Scanning %s..." file))
787cc821 84 alist form len fn fnfile arglist fileonly)
87b8db2b
GM
85 (message "%s" m)
86 (with-temp-buffer
87 (insert-file-contents file)
787cc821
GM
88 ;; FIXME we could theoretically be inside a string.
89 (while (re-search-forward "^[ \t]*\\((declare-function\\)[ \t\n]" nil t)
90 (goto-char (match-beginning 1))
91 (if (and (setq form (ignore-errors (read (current-buffer)))
92 len (length form))
93 (> len 2) (< len 6)
94 (symbolp (setq fn (cadr form)))
95 (setq fn (symbol-name fn)) ; later we use as a search string
96 (stringp (setq fnfile (nth 2 form)))
97 (setq fnfile (check-declare-locate fnfile
98 (expand-file-name file)))
99 ;; Use `t' to distinguish unspecified arglist from empty one.
100 (or (eq t (setq arglist (if (> len 3)
101 (nth 3 form)
102 t)))
103 (listp arglist))
104 (symbolp (setq fileonly (nth 4 form))))
105 (setq alist (cons (list fnfile fn arglist fileonly) alist))
106 ;; FIXME make this more noticeable.
107 (message "Malformed declaration for `%s'" (cadr form)))))
87b8db2b
GM
108 (message "%sdone" m)
109 alist))
110
122bcd7e
GM
111(defun check-declare-errmsg (errlist &optional full)
112 "Return a string with the number of errors in ERRLIST, if any.
113Normally just counts the number of elements in ERRLIST.
114With optional argument FULL, sums the number of elements in each element."
115 (if errlist
116 (let ((l (length errlist)))
117 (when full
118 (setq l 0)
119 (dolist (e errlist)
51d16e07 120 (setq l (+ l (1- (length e))))))
122bcd7e
GM
121 (format "%d problem%s found" l (if (= l 1) "" "s")))
122 "OK"))
123
87b8db2b
GM
124(autoload 'byte-compile-arglist-signature "bytecomp")
125
126(defun check-declare-verify (fnfile fnlist)
127 "Check that FNFILE contains function definitions matching FNLIST.
630456e6
GM
128Each element of FNLIST has the form (FILE FN ARGLIST FILEONLY), where
129only the first two elements need be present. This means FILE claimed FN
130was defined in FNFILE with the specified ARGLIST. FILEONLY non-nil means
131to only check that FNFILE exists, not that it actually defines FN.
132
133Returns nil if all claims are found to be true, otherwise a list
134of errors with elements of the form \(FILE FN TYPE), where TYPE
135is a string giving details of the error."
87b8db2b 136 (let ((m (format "Checking %s..." fnfile))
75d37c3d 137 (cflag (member (file-name-extension fnfile) '("c" "m")))
122bcd7e 138 (ext (string-match "^ext:" fnfile))
ad95f32a 139 re fn sig siglist arglist type errlist minargs maxargs)
87b8db2b 140 (message "%s" m)
122bcd7e
GM
141 (if ext
142 (setq fnfile (substring fnfile 4)))
787cc821 143 (if (file-regular-p fnfile)
9769d49f
GM
144 (with-temp-buffer
145 (insert-file-contents fnfile)
146 ;; defsubst's don't _have_ to be known at compile time.
147 (setq re (format (if cflag
148 "^[ \t]*\\(DEFUN\\)[ \t]*([ \t]*\"%s\""
e49337ea 149 "^[ \t]*(\\(fset[ \t]+'\\|\
ae715515
GM
150def\\(?:un\\|subst\\|foo\\|method\\|class\\|\
151ine-\\(?:derived\\|generic\\|\\(?:global\\(?:ized\\)?-\\)?minor\\)-mode\\|\
152\\(?:ine-obsolete-function-\\)?alias[ \t]+'\\|\
153ine-overloadable-function\\)\\)\
9769d49f
GM
154\[ \t]*%s\\([ \t;]+\\|$\\)")
155 (regexp-opt (mapcar 'cadr fnlist) t)))
156 (while (re-search-forward re nil t)
157 (skip-chars-forward " \t\n")
158 (setq fn (match-string 2)
a6e02a86 159 type (match-string 1)
9769d49f
GM
160 ;; (min . max) for a fixed number of arguments, or
161 ;; arglists with optional elements.
162 ;; (min) for arglists with &rest.
64cea555 163 ;; sig = 'err means we could not find an arglist.
ad95f32a 164 sig (cond (cflag
64cea555 165 (or
8360fce0 166 (when (search-forward "," nil t 3)
64cea555
GM
167 (skip-chars-forward " \t\n")
168 ;; Assuming minargs and maxargs on same line.
169 (when (looking-at "\\([0-9]+\\)[ \t]*,[ \t]*\
ad95f32a 170\\([0-9]+\\|MANY\\|UNEVALLED\\)")
64cea555
GM
171 (setq minargs (string-to-number
172 (match-string 1))
173 maxargs (match-string 2))
174 (cons minargs (unless (string-match "[^0-9]"
175 maxargs)
176 (string-to-number
177 maxargs)))))
178 'err))
a6e02a86
GM
179 ((string-match
180 "\\`define-\\(derived\\|generic\\)-mode\\'"
181 type)
9769d49f 182 '(0 . 0))
a6e02a86
GM
183 ((string-match
184 "\\`define\\(-global\\(ized\\)?\\)?-minor-mode\\'"
185 type)
9769d49f 186 '(0 . 1))
a6e02a86
GM
187 ;; Prompt to update.
188 ((string-match
189 "\\`define-obsolete-function-alias\\>"
190 type)
191 'obsolete)
4ab4de9c 192 ;; Can't easily check arguments in these cases.
ae715515
GM
193 ((string-match "\\`\\(def\\(alias\\|\
194method\\|class\\)\\|fset\\)\\>" type)
9769d49f 195 t)
64cea555
GM
196 ((looking-at "\\((\\|nil\\)")
197 (byte-compile-arglist-signature
198 (read (current-buffer))))
9769d49f 199 (t
64cea555 200 'err))
9769d49f
GM
201 ;; alist of functions and arglist signatures.
202 siglist (cons (cons fn sig) siglist)))))
203 (dolist (e fnlist)
204 (setq arglist (nth 2 e)
205 type
a6e02a86
GM
206 (if (not re)
207 "file not found"
208 (if (not (setq sig (assoc (cadr e) siglist)))
630456e6
GM
209 (unless (nth 3 e) ; fileonly
210 "function not found")
a6e02a86
GM
211 (setq sig (cdr sig))
212 (cond ((eq sig 'obsolete) ; check even when no arglist specified
213 "obsolete alias")
214 ;; arglist t means no arglist specified, as
215 ;; opposed to an empty arglist.
216 ((eq arglist t) nil)
4ab4de9c 217 ((eq sig t) nil) ; eg defalias - can't check arguments
a6e02a86
GM
218 ((eq sig 'err)
219 "arglist not found") ; internal error
220 ((not (equal (byte-compile-arglist-signature
221 arglist)
222 sig))
223 "arglist mismatch")))))
9769d49f
GM
224 (when type
225 (setq errlist (cons (list (car e) (cadr e) type) errlist))))
122bcd7e
GM
226 (message "%s%s" m
227 (if (or re (not ext))
228 (check-declare-errmsg errlist)
630456e6
GM
229 (progn
230 (setq errlist nil)
231 "skipping external file")))
9769d49f 232 errlist))
87b8db2b
GM
233
234(defun check-declare-sort (alist)
235 "Sort a list with elements FILE (FNFILE ...).
236Returned list has elements FNFILE (FILE ...)."
237 (let (file fnfile rest sort a)
238 (dolist (e alist)
239 (setq file (car e))
240 (dolist (f (cdr e))
241 (setq fnfile (car f)
242 rest (cdr f))
243 (if (setq a (assoc fnfile sort))
244 (setcdr a (append (cdr a) (list (cons file rest))))
245 (setq sort (cons (list fnfile (cons file rest)) sort)))))
246 sort))
247
248(defun check-declare-warn (file fn fnfile type)
249 "Warn that FILE made a false claim about FN in FNFILE.
250TYPE is a string giving the nature of the error. Warning is displayed in
251`check-declare-warning-buffer'."
252 (display-warning 'check-declare
253 (format "%s said `%s' was defined in %s: %s"
254 (file-name-nondirectory file) fn
255 (file-name-nondirectory fnfile)
256 type)
257 nil check-declare-warning-buffer))
258
259(defun check-declare-files (&rest files)
260 "Check veracity of all `declare-function' statements in FILES.
261Return a list of any errors found."
262 (let (alist err errlist)
263 (dolist (file files)
264 (setq alist (cons (cons file (check-declare-scan file)) alist)))
265 ;; Sort so that things are ordered by the files supposed to
266 ;; contain the defuns.
267 (dolist (e (check-declare-sort alist))
268 (if (setq err (check-declare-verify (car e) (cdr e)))
269 (setq errlist (cons (cons (car e) err) errlist))))
270 (if (get-buffer check-declare-warning-buffer)
271 (kill-buffer check-declare-warning-buffer))
272 ;; Sort back again so that errors are ordered by the files
273 ;; containing the declare-function statements.
274 (dolist (e (check-declare-sort errlist))
275 (dolist (f (cdr e))
276 (check-declare-warn (car e) (cadr f) (car f) (nth 2 f))))
277 errlist))
278
279;;;###autoload
280(defun check-declare-file (file)
281 "Check veracity of all `declare-function' statements in FILE.
282See `check-declare-directory' for more information."
283 (interactive "fFile to check: ")
284 (or (file-exists-p file)
285 (error "File `%s' not found" file))
286 (let ((m (format "Checking %s..." file))
287 errlist)
288 (message "%s" m)
289 (setq errlist (check-declare-files file))
122bcd7e 290 (message "%s%s" m (check-declare-errmsg errlist))
87b8db2b
GM
291 errlist))
292
293;;;###autoload
294(defun check-declare-directory (root)
295 "Check veracity of all `declare-function' statements under directory ROOT.
787cc821 296Returns non-nil if any false statements are found."
87b8db2b
GM
297 (interactive "DDirectory to check: ")
298 (or (file-directory-p (setq root (expand-file-name root)))
299 (error "Directory `%s' not found" root))
300 (let ((m "Checking `declare-function' statements...")
301 (m2 "Finding files with declarations...")
302 errlist files)
303 (message "%s" m)
304 (message "%s" m2)
2f18aa21
JB
305 (setq files (process-lines find-program root
306 "-name" "*.el"
307 "-exec" grep-program
308 "-l" "^[ \t]*(declare-function" "{}" ";"))
87b8db2b
GM
309 (message "%s%d found" m2 (length files))
310 (when files
311 (setq errlist (apply 'check-declare-files files))
122bcd7e 312 (message "%s%s" m (check-declare-errmsg errlist t))
87b8db2b
GM
313 errlist)))
314
315(provide 'check-declare)
316
317;; arch-tag: a4d6cdc4-deb7-4502-b327-0e4ef3d82d96
318;;; check-declare.el ends here.