changeset 104974:646ba543ede9

(elint-file, elint-directory): New autoloaded commands. (elint-current-buffer): Set mode-line-process. (elint-init-env): Handle define-derived-mode. Fix declare-function with unspecified arglist. Guard against odd defalias statements (eg iso-insert's 8859-1-map). (elint-add-required-env): Use a temp buffer. (elint-form): Just print the function/macro name, not the whole form. Return env unchanged if we fail to parse a macro. (elint-forms): Guard against parse errors. (elint-output): New function, to handle batch mode. (elint-log-message): Add optional argument. Use elint-output. (elint-set-mode-line): New function.
author Glenn Morris <rgm@gnu.org>
date Sat, 12 Sep 2009 02:41:39 +0000
parents cc423a84dd77
children b866a533c9f3
files lisp/emacs-lisp/elint.el
diffstat 1 files changed, 111 insertions(+), 31 deletions(-) [+]
line wrap: on
line diff
--- a/lisp/emacs-lisp/elint.el	Sat Sep 12 01:40:11 2009 +0000
+++ b/lisp/emacs-lisp/elint.el	Sat Sep 12 02:41:39 2009 +0000
@@ -40,6 +40,9 @@
 
 ;; * List of variables and functions defined in dumped lisp files.
 ;; * Adding type checking. (Stop that sniggering!)
+;; * Handle eval-when-compile (eg for requires, being sensitive to the
+;;   difference between funcs and macros).
+;; * Requires within function bodies.
 
 ;;; Code:
 
@@ -152,6 +155,50 @@
 ;;;
 
 ;;;###autoload
+(defun elint-file (file)
+  "Lint the file FILE."
+  (interactive "fElint file: ")
+  (setq file (expand-file-name file))
+  (or elint-builtin-variables
+      (elint-initialize))
+  (let ((dir (file-name-directory file)))
+    (let ((default-directory dir))
+      (elint-display-log))
+    (elint-set-mode-line t)
+    (with-current-buffer elint-log-buffer
+      (unless (string-equal default-directory dir)
+	(elint-log-message (format "\nLeaving directory `%s'"
+				   default-directory) t)
+	(elint-log-message (format "Entering directory `%s'" dir) t)
+	(setq default-directory dir))))
+  (let ((str (format "Linting file %s" file)))
+    (message "%s..." str)
+    (or noninteractive
+	(elint-log-message (format "\n%s at %s" str (current-time-string)) t))
+    ;; elint-current-buffer clears log.
+    (with-temp-buffer
+      (insert-file-contents file)
+      (let ((buffer-file-name file))
+	(with-syntax-table emacs-lisp-mode-syntax-table
+	  (mapc 'elint-top-form (elint-update-env)))))
+    (elint-set-mode-line)
+    (message "%s...done" str)))
+
+;; cf byte-recompile-directory.
+;;;###autoload
+(defun elint-directory (directory)
+  "Lint all the .el files in DIRECTORY."
+  (interactive "DElint directory: ")
+  (let ((elint-running t))
+    (dolist (file (directory-files directory t))
+      ;; Bytecomp has emacs-lisp-file-regexp.
+      (when (and (string-match "\\.el\\'" file)
+		 (file-readable-p file)
+		 (not (auto-save-file-name-p file)))
+	(elint-file file))))
+  (elint-set-mode-line))
+
+;;;###autoload
 (defun elint-current-buffer ()
   "Lint the current buffer.
 If necessary, this first calls `elint-initalize'."
@@ -161,12 +208,14 @@
   (elint-clear-log (format "Linting %s" (or (buffer-file-name)
 					    (buffer-name))))
   (elint-display-log)
+  (elint-set-mode-line t)
   (mapc 'elint-top-form (elint-update-env))
   ;; Tell the user we're finished.  This is terribly klugy: we set
-  ;; elint-top-form-logged so elint-log-message doesn't print the
-  ;; ** top form ** header...
-  (let ((elint-top-form-logged t))
-    (elint-log-message "\nLinting finished.\n")))
+    ;; elint-top-form-logged so elint-log-message doesn't print the
+    ;; ** top form ** header...
+  (elint-set-mode-line)
+  (elint-log-message "\nLinting finished.\n" t))
+
 
 ;;;###autoload
 (defun elint-defun ()
@@ -254,15 +303,19 @@
        ;; Add function
        ((memq (car form) '(defun defsubst))
 	(setq env (elint-env-add-func env (cadr form) (nth 2 form))))
+       ((eq (car form) 'define-derived-mode)
+	(setq env (elint-env-add-func env (cadr form) ())
+	      env (elint-env-add-var env (cadr form))))
        ;; FIXME it would be nice to check the autoloads are correct.
        ((eq (car form) 'autoload)
 	(setq env (elint-env-add-func env (cadr (cadr form)) 'unknown)))
        ((eq (car form) 'declare-function)
 	(setq env (elint-env-add-func env (cadr form)
-				      (if (> (length form) 3)
-					  (nth 3 form)
-					'unknown))))
-       ((eq (car form) 'defalias)
+				      (if (or (< (length form) 4)
+					      (eq (nth 3 form) t))
+					'unknown
+					(nth 3 form)))))
+       ((and (eq (car form) 'defalias) (listp (nth 2 form)))
 	;; If the alias points to something already in the environment,
 	;; add the alias to the environment with the same arguments.
 	(let ((def (elint-env-find-func env (cadr (nth 2 form)))))
@@ -295,10 +348,16 @@
 	(message nil)
 	(if lib
 	    (save-excursion
-	      (set-buffer (find-file-noselect lib))
-	      (elint-update-env)
-	      (setq env (elint-env-add-env env elint-buffer-env)))
-	  (error "Dummy error")))
+ 	      ;;; (set-buffer (find-file-noselect lib))
+ 	      ;;; (elint-update-env)
+ 	      ;;; (setq env (elint-env-add-env env elint-buffer-env)))
+	      (with-temp-buffer
+		(insert-file-contents lib)
+		(with-syntax-table emacs-lisp-mode-syntax-table
+		  (elint-update-env))
+		(setq env (elint-env-add-env env elint-buffer-env))))
+	      ;;(message "Elint processed (require '%s)" name))
+	  (error "Unable to find require'd library %s" name)))
     (error
      (ding)
      (message "Can't get variables from require'd library %s" name)))
@@ -356,7 +415,7 @@
 	  (cond
 	   ((eq args 'undefined)
 	    (setq argsok nil)
-	    (elint-error "Call to undefined function: %s" form))
+	    (elint-error "Call to undefined function: %s" func))
 
 	   ((eq args 'unknown) nil)
 
@@ -371,7 +430,8 @@
 		      (elint-form
 		       (macroexpand form (elint-env-macro-env env)) env)
 		    (error
-		     (elint-error "Elint failed to expand macro: %s" form)))
+		     (elint-error "Elint failed to expand macro: %s" func)
+		     env))
 		env)
 
 	    (let ((fcode (if (symbolp func)
@@ -397,8 +457,11 @@
 (defun elint-forms (forms env)
   "Lint the FORMS, accumulating an environment, starting with ENV."
   ;; grumblegrumbletailrecursiongrumblegrumble
-  (dolist (f forms env)
-    (setq env (elint-form f env))))
+  (if (listp forms)
+      (dolist (f forms env)
+	(setq env (elint-form f env)))
+    ;; Loop macro?
+    (elint-error "Elint failed to parse form: %s" forms)))
 
 (defun elint-unbound-variable (var env)
   "T if VAR is unbound in ENV."
@@ -639,27 +702,33 @@
 See `elint-error'."
   (elint-log "Warning" string args))
 
-(defun elint-log-message (errstr)
-  "Insert ERRSTR last in the lint log buffer."
+(defun elint-output (string)
+  "Print or insert STRING, depending on value of `noninteractive'."
+  (if noninteractive
+      (message "%s" string)
+    (insert string "\n")))
+
+(defun elint-log-message (errstr &optional top)
+  "Insert ERRSTR last in the lint log buffer.
+Optional argument TOP non-nil means pretend `elint-top-form-logged' is non-nil."
   (with-current-buffer (elint-get-log-buffer)
     (goto-char (point-max))
     (let ((inhibit-read-only t))
       (or (bolp) (newline))
       ;; Do we have to say where we are?
-      (unless elint-top-form-logged
-	(insert
-	 (let* ((form (elint-top-form-form elint-top-form))
-		(top (car form)))
-	   (cond
-	    ((memq top '(defun defsubst))
-	     (format "\nIn function %s:\n" (cadr form)))
-	    ((eq top 'defmacro)
-	     (format "\nIn macro %s:\n" (cadr form)))
-	    ((memq top '(defvar defconst))
-	     (format "\nIn variable %s:\n" (cadr form)))
-	    (t "\nIn top level expression:\n"))))
+      (unless (or elint-top-form-logged top)
+	(let* ((form (elint-top-form-form elint-top-form))
+	       (top (car form)))
+	  (elint-output (cond
+			 ((memq top '(defun defsubst))
+			  (format "\nIn function %s:" (cadr form)))
+			 ((eq top 'defmacro)
+			  (format "\nIn macro %s:" (cadr form)))
+			 ((memq top '(defvar defconst))
+			  (format "\nIn variable %s:" (cadr form)))
+			 (t "\nIn top level expression:"))))
 	(setq elint-top-form-logged t))
-      (insert errstr "\n"))))
+      (elint-output errstr))))
 
 (defun elint-clear-log (&optional header)
   "Clear the lint log buffer.
@@ -677,6 +746,17 @@
     (display-buffer (elint-get-log-buffer))
     (sit-for 0)))
 
+(defvar elint-running)
+
+(defun elint-set-mode-line (&optional on)
+  "Set the mode-line-process of the Elint log buffer."
+  (with-current-buffer (elint-get-log-buffer)
+    (and (eq major-mode 'compilation-mode)
+	 (setq mode-line-process
+	       (list (if (or on (bound-and-true-p elint-running))
+			 (propertize ":run" 'face 'compilation-warning)
+		       (propertize ":finished" 'face 'compilation-info)))))))
+
 (defun elint-get-log-buffer ()
   "Return a log buffer for elint."
   (or (get-buffer elint-log-buffer)