(check-declare-verify): Fix previous change.
[bpt/emacs.git] / lisp / emacs-lisp / check-declare.el
CommitLineData
87b8db2b
GM
1;;; check-declare.el --- Check declare-function statements
2
3;; Copyright (C) 2007 Free Software Foundation, Inc.
4
5;; Author: Glenn Morris <rgm@gnu.org>
6;; Keywords: lisp, tools, maint
7
8;; This file is part of GNU Emacs.
9
10;; GNU Emacs is free software; you can redistribute it and/or modify
11;; it under the terms of the GNU General Public License as published by
12;; the Free Software Foundation; either version 3, or (at your option)
13;; any later version.
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
21;; along with GNU Emacs; see the file COPYING. If not, write to the
22;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
23;; Boston, MA 02110-1301, USA.
24
25;;; Commentary:
26
27;; The byte-compiler often warns about undefined functions that you
28;; know will actually be defined when it matters. The `declare-function'
29;; statement allows you to suppress these warnings. This package
30;; checks that all such statements in a file or directory are accurate.
31;; The entry points are `check-declare-file' and `check-declare-directory'.
32
f3a4724d
GM
33;;; TODO:
34
35;; 1. Handle defstructs (eg uniquify-item-base in desktop.el).
36
87b8db2b
GM
37;;; Code:
38
39(defconst check-declare-warning-buffer "*Check Declarations Warnings*"
40 "Name of buffer used to display any `check-declare' warnings.")
41
42(defun check-declare-scan (file)
43 "Scan FILE for `declare-function' calls.
44Return a list with elements of the form (FNFILE FN ARGLIST), where
45ARGLIST may be absent. This claims that FNFILE defines FN, with ARGLIST."
46 (let ((m (format "Scanning %s..." file))
47 alist fnfile fn)
48 (message "%s" m)
49 (with-temp-buffer
50 (insert-file-contents file)
51 (while (re-search-forward
52 "^[ \t]*(declare-function[ \t]+\\(\\S-+\\)[ \t]+\
53\"\\(\\S-+\\)\"" nil t)
54 (setq fn (match-string 1)
55 fnfile (match-string 2))
56 (or (file-name-absolute-p fnfile)
9769d49f
GM
57 (setq fnfile
58 (expand-file-name fnfile
59 ;; .c files are assumed to be
60 ;; relative to the Emacs src/ directory.
61 (if (string-equal
62 "c" (file-name-extension fnfile))
63 (expand-file-name "src"
64 source-directory)
65 (file-name-directory file)))))
87b8db2b
GM
66 (setq alist (cons
67 (list fnfile fn
68 (progn
69 (skip-chars-forward " \t\n")
70 ;; Use `t' to distinguish no arglist
71 ;; specified from an empty one.
72 (if (looking-at "\\((\\|nil\\)")
73 (read (current-buffer))
74 t)))
75 alist))))
76 (message "%sdone" m)
77 alist))
78
79(autoload 'byte-compile-arglist-signature "bytecomp")
80
81(defun check-declare-verify (fnfile fnlist)
82 "Check that FNFILE contains function definitions matching FNLIST.
83Each element of FNLIST has the form (FILE FN ARGLIST), where
84ARGLIST is optional. This means FILE claimed FN was defined in
85FNFILE with the specified ARGLIST. Returns nil if all claims are
86found to be true, otherwise a list of errors with elements of the form
87\(FILE FN TYPE), where TYPE is a string giving details of the error."
88 (let ((m (format "Checking %s..." fnfile))
9769d49f 89 (cflag (string-equal "c" (file-name-extension fnfile)))
ad95f32a 90 re fn sig siglist arglist type errlist minargs maxargs)
87b8db2b 91 (message "%s" m)
9769d49f
GM
92 (or cflag
93 (file-exists-p fnfile)
94 (setq fnfile (concat fnfile ".el")))
95 (if (file-exists-p fnfile)
96 (with-temp-buffer
97 (insert-file-contents fnfile)
98 ;; defsubst's don't _have_ to be known at compile time.
99 (setq re (format (if cflag
100 "^[ \t]*\\(DEFUN\\)[ \t]*([ \t]*\"%s\""
101 "^[ \t]*(\\(def\\(?:un\\|subst\\|\
2ae3bb85 102ine-derived-mode\\|ine-minor-mode\\|alias[ \t]+'\\)\\)\
9769d49f
GM
103\[ \t]*%s\\([ \t;]+\\|$\\)")
104 (regexp-opt (mapcar 'cadr fnlist) t)))
105 (while (re-search-forward re nil t)
106 (skip-chars-forward " \t\n")
107 (setq fn (match-string 2)
108 ;; (min . max) for a fixed number of arguments, or
109 ;; arglists with optional elements.
110 ;; (min) for arglists with &rest.
ad95f32a
GM
111 sig (cond (cflag
112 (re-search-forward "," nil t 3)
113 (skip-chars-forward " \t\n")
114 ;; Assuming minargs and maxargs on same line.
115 (when (looking-at "\\([0-9]+\\)[ \t]*,[ \t]*\
116\\([0-9]+\\|MANY\\|UNEVALLED\\)")
117 (setq minargs (string-to-number (match-string 1))
118 maxargs (match-string 2))
119 (cons minargs (unless (string-match "[^0-9]"
120 maxargs)
121 (string-to-number maxargs)))))
122 ((string-equal (match-string 1)
9769d49f
GM
123 "define-derived-mode")
124 '(0 . 0))
125 ((string-equal (match-string 1)
126 "define-minor-mode")
127 '(0 . 1))
128 ;; Can't easily check alias arguments.
129 ((string-equal (match-string 1)
130 "defalias")
131 t)
132 (t
133 (if (looking-at "\\((\\|nil\\)")
134 (byte-compile-arglist-signature
135 (read (current-buffer))))))
136 ;; alist of functions and arglist signatures.
137 siglist (cons (cons fn sig) siglist)))))
138 (dolist (e fnlist)
139 (setq arglist (nth 2 e)
140 type
141 (if re ; re non-nil means found a file
142 (if (setq sig (assoc (cadr e) siglist))
143 ;; Recall we use t to mean no arglist specified,
144 ;; to distinguish from an empty arglist.
ad95f32a 145 (unless (or (eq arglist t)
9769d49f
GM
146 (eq sig t))
147 (unless (equal (byte-compile-arglist-signature arglist)
148 (cdr sig))
149 "arglist mismatch"))
150 "function not found")
151 "file not found"))
152 (when type
153 (setq errlist (cons (list (car e) (cadr e) type) errlist))))
154 (message "%s%s" m (if errlist "problems found" "OK"))
155 errlist))
87b8db2b
GM
156
157(defun check-declare-sort (alist)
158 "Sort a list with elements FILE (FNFILE ...).
159Returned list has elements FNFILE (FILE ...)."
160 (let (file fnfile rest sort a)
161 (dolist (e alist)
162 (setq file (car e))
163 (dolist (f (cdr e))
164 (setq fnfile (car f)
165 rest (cdr f))
166 (if (setq a (assoc fnfile sort))
167 (setcdr a (append (cdr a) (list (cons file rest))))
168 (setq sort (cons (list fnfile (cons file rest)) sort)))))
169 sort))
170
171(defun check-declare-warn (file fn fnfile type)
172 "Warn that FILE made a false claim about FN in FNFILE.
173TYPE is a string giving the nature of the error. Warning is displayed in
174`check-declare-warning-buffer'."
175 (display-warning 'check-declare
176 (format "%s said `%s' was defined in %s: %s"
177 (file-name-nondirectory file) fn
178 (file-name-nondirectory fnfile)
179 type)
180 nil check-declare-warning-buffer))
181
182(defun check-declare-files (&rest files)
183 "Check veracity of all `declare-function' statements in FILES.
184Return a list of any errors found."
185 (let (alist err errlist)
186 (dolist (file files)
187 (setq alist (cons (cons file (check-declare-scan file)) alist)))
188 ;; Sort so that things are ordered by the files supposed to
189 ;; contain the defuns.
190 (dolist (e (check-declare-sort alist))
191 (if (setq err (check-declare-verify (car e) (cdr e)))
192 (setq errlist (cons (cons (car e) err) errlist))))
193 (if (get-buffer check-declare-warning-buffer)
194 (kill-buffer check-declare-warning-buffer))
195 ;; Sort back again so that errors are ordered by the files
196 ;; containing the declare-function statements.
197 (dolist (e (check-declare-sort errlist))
198 (dolist (f (cdr e))
199 (check-declare-warn (car e) (cadr f) (car f) (nth 2 f))))
200 errlist))
201
202;;;###autoload
203(defun check-declare-file (file)
204 "Check veracity of all `declare-function' statements in FILE.
205See `check-declare-directory' for more information."
206 (interactive "fFile to check: ")
207 (or (file-exists-p file)
208 (error "File `%s' not found" file))
209 (let ((m (format "Checking %s..." file))
210 errlist)
211 (message "%s" m)
212 (setq errlist (check-declare-files file))
213 (message "%s%s" m (if errlist "problems found" "OK"))
214 errlist))
215
216;;;###autoload
217(defun check-declare-directory (root)
218 "Check veracity of all `declare-function' statements under directory ROOT.
219Returns non-nil if any false statements are found. For this to
220work correctly, the statements must adhere to the format
221described in the documentation of `declare-function'."
222 (interactive "DDirectory to check: ")
223 (or (file-directory-p (setq root (expand-file-name root)))
224 (error "Directory `%s' not found" root))
225 (let ((m "Checking `declare-function' statements...")
226 (m2 "Finding files with declarations...")
227 errlist files)
228 (message "%s" m)
229 (message "%s" m2)
230 (setq files (process-lines "find" root "-name" "*.el"
231 "-exec" "grep" "-l"
232 "^[ ]*(declare-function" "{}" ";"))
233 (message "%s%d found" m2 (length files))
234 (when files
235 (setq errlist (apply 'check-declare-files files))
236 (message "%s%s" m (if errlist "problems found" "OK"))
237 errlist)))
238
239(provide 'check-declare)
240
241;; arch-tag: a4d6cdc4-deb7-4502-b327-0e4ef3d82d96
242;;; check-declare.el ends here.