| 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 | |
| 33 | ;;; TODO: |
| 34 | |
| 35 | ;; 1. Handle defstructs (eg uniquify-item-base in desktop.el). |
| 36 | |
| 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. |
| 44 | Return a list with elements of the form (FNFILE FN ARGLIST), where |
| 45 | ARGLIST 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) |
| 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))))) |
| 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. |
| 83 | Each element of FNLIST has the form (FILE FN ARGLIST), where |
| 84 | ARGLIST is optional. This means FILE claimed FN was defined in |
| 85 | FNFILE with the specified ARGLIST. Returns nil if all claims are |
| 86 | found 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)) |
| 89 | (cflag (string-equal "c" (file-name-extension fnfile))) |
| 90 | re fn sig siglist arglist type errlist minargs maxargs) |
| 91 | (message "%s" m) |
| 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\\|\ |
| 102 | ine-derived-mode\\|ine-minor-mode\\|alias[ \t]+'\\)\\)\ |
| 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. |
| 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) |
| 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. |
| 145 | (unless (or (eq arglist t) |
| 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)) |
| 156 | |
| 157 | (defun check-declare-sort (alist) |
| 158 | "Sort a list with elements FILE (FNFILE ...). |
| 159 | Returned 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. |
| 173 | TYPE 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. |
| 184 | Return 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. |
| 205 | See `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. |
| 219 | Returns non-nil if any false statements are found. For this to |
| 220 | work correctly, the statements must adhere to the format |
| 221 | described 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. |