]> code.delx.au - gnu-emacs/blobdiff - lisp/emacs-lisp/check-declare.el
Merge from mainline.
[gnu-emacs] / lisp / emacs-lisp / check-declare.el
index 76719f1b876f16b8a5d1d1cfb2e69ee415b2825f..f6ff67a90c30fb25872bcdda2fc62ac4bd47e699 100644 (file)
@@ -1,16 +1,16 @@
 ;;; check-declare.el --- Check declare-function statements
 
-;; Copyright (C) 2007 Free Software Foundation, Inc.
+;; Copyright (C) 2007-2011  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
+;; 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, or (at your option)
-;; any later version.
+;; 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
@@ -18,9 +18,7 @@
 ;; GNU General Public License for more details.
 
 ;; You should have received a copy of the GNU General Public License
-;; along with GNU Emacs; see the file COPYING.  If not, write to the
-;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
-;; Boston, MA 02110-1301, USA.
+;; along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.
 
 ;;; Commentary:
 
 ;; checks that all such statements in a file or directory are accurate.
 ;; The entry points are `check-declare-file' and `check-declare-directory'.
 
-;;; TODO:
+;; For more information, see Info node `elisp(Declaring Functions)'.
 
-;; 1. Handle defstructs (eg uniquify-item-base in desktop.el).
+;;; TODO:
 
-;; 2. Check C files (look in src/)?
+;; 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), where
-ARGLIST may be absent.  This claims that FNFILE defines FN, with ARGLIST."
+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 fnfile fn)
+        alist form len fn fnfile arglist fileonly)
     (message "%s" m)
     (with-temp-buffer
       (insert-file-contents file)
-      (while (re-search-forward
-              "^[ \t]*(declare-function[ \t]+\\(\\S-+\\)[ \t]+\
-\"\\(\\S-+\\)\"" nil t)
-        (setq fn (match-string 1)
-              fnfile (match-string 2))
-        (or (file-name-absolute-p fnfile)
-            (setq fnfile (expand-file-name fnfile (file-name-directory file))))
-        (setq alist (cons
-                     (list fnfile fn
-                           (progn
-                             (skip-chars-forward " \t\n")
-                             ;; Use `t' to distinguish no arglist
-                             ;; specified from an empty one.
-                             (if (looking-at "\\((\\|nil\\)")
-                                 (read (current-buffer))
-                               t)))
-                     alist))))
+      ;; 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)))
+                       len (length form))
+                 (> len 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.
+          (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")
 
 (defun check-declare-verify (fnfile fnlist)
   "Check that FNFILE contains function definitions matching FNLIST.
-Each element of FNLIST has the form (FILE FN ARGLIST), where
-ARGLIST is optional.  This means FILE claimed FN was defined in
-FNFILE with the specified ARGLIST.  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."
+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))
-        re fn sig siglist arglist type errlist)
+        (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 (string-equal (file-name-extension fnfile) "c")
-        (progn
-          (message "%sskipping C file" m)
-          nil)
-      (or (file-exists-p fnfile)
-          (setq fnfile (concat fnfile ".el")))
-      (if (file-exists-p fnfile)
-          (with-temp-buffer
-            (insert-file-contents fnfile)
-            ;; defsubst's don't _have_ to be known at compile time.
-            (setq re (format "^[ \t]*(\\(def\\(?:un\\|subst\\|\
-ine-derived-mode\\|ine-minor-mode\\|alias[ \t]+'\\)\\)\
-\[ \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)
-                    sig (cond ((string-equal (match-string 1)
-                                             "define-derived-mode")
-                               '(0 . 0))
-                              ((string-equal (match-string 1)
-                                             "define-minor-mode")
-                               '(0 . 1))
-                              ;; Can't easily check alias arguments.
-                              ((string-equal (match-string 1)
-                                             "defalias")
-                               t)
-                              (t
-                               (if (looking-at "\\((\\|nil\\)")
-                                   (byte-compile-arglist-signature
-                                    (read (current-buffer))))))
-                    ;; alist of functions and arglist signatures.
-                    siglist (cons (cons fn sig) siglist)))))
-      (dolist (e fnlist)
-        (setq arglist (nth 2 e)
-              type
-              (if re                   ; re non-nil means found a file
-                  (if (setq sig (assoc (cadr e) siglist))
-                      ;; Recall we use t to mean no arglist specified,
-                      ;; to distinguish from an empty arglist.
-                      (unless (or (eq arglist t)
-                                  (eq sig t))
-                        (unless (equal (byte-compile-arglist-signature arglist)
-                                       (cdr sig))
-                          "arglist mismatch"))
+    (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]+'\\|\
+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\\|\
+method\\|class\\)\\|fset\\)\\>" 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")
-                "file not found"))
-        (when type
-          (setq errlist (cons (list (car e) (cadr e) type) errlist))))
-      (message "%s%s" m (if errlist "problems found" "OK"))
-      errlist)))
+                (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 (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 ...).
@@ -190,15 +287,13 @@ See `check-declare-directory' for more information."
         errlist)
     (message "%s" m)
     (setq errlist (check-declare-files file))
-    (message "%s%s" m (if errlist "problems found" "OK"))
+    (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.  For this to
-work correctly, the statements must adhere to the format
-described in the documentation of `declare-function'."
+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))
@@ -207,16 +302,16 @@ described in the documentation of `declare-function'."
         errlist files)
     (message "%s" m)
     (message "%s" m2)
-    (setq files (process-lines "find" root "-name" "*.el"
-                                 "-exec" "grep" "-l"
-                                 "^[   ]*(declare-function" "{}" ";"))
+    (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 (if errlist "problems found" "OK"))
+      (message "%s%s" m (check-declare-errmsg errlist t))
       errlist)))
 
 (provide 'check-declare)
 
-;; arch-tag: a4d6cdc4-deb7-4502-b327-0e4ef3d82d96
 ;;; check-declare.el ends here.