aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGlenn Morris2009-09-22 02:28:28 +0000
committerGlenn Morris2009-09-22 02:28:28 +0000
commit76f2c576919a3794e91ca9f6150d3b4cbbffb384 (patch)
tree930603a2072b7d56966d0a1daf63654b3cde27ca
parentb578c9cce418bf6e9a46c3e0118b4e30a769ffa3 (diff)
downloademacs-76f2c576919a3794e91ca9f6150d3b4cbbffb384.tar.gz
emacs-76f2c576919a3794e91ca9f6150d3b4cbbffb384.zip
(elint): New custom group.
(elint-log-buffer): Make it a defcustom. (elint-scan-preloaded, elint-ignored-warnings) (elint-directory-skip-re): New options. (elint-builtin-variables): Doc fix. (elint-preloaded-env): New variable. (elint-unknown-builtin-args): Add an entry for encode-time. (elint-extra-errors): Make it a variable rather than a constant. (elint-preloaded-skip-re): New constant. (elint-directory): Skip files matching elint-directory-skip-re. (elint-features): New variable, local to linted buffers. (elint-update-env): Initialize elint-features. Possibly add elint-preloaded-env to the buffer's environment. (elint-get-top-forms): Bind elint-current-pos, for log messages. Skip quoted forms. (elint-init-form): New function, extracted from elint-init-env. Make non-list forms a warning rather than an error. Add the mode-map for define-derived-mode. Handle define-minor-mode, easy-menu-define, put that adds an error-condition, and provide. When requiring cl, also require cl-macs. Really require cl, to handle some cl macros. Store required libraries in the list elint-features, so as not to re-load them. Treat cc-require like require. (elint-init-env): Call elint-init-form to do the work. Handle eval-and-compile and such like. (elint-add-required-env): Do not clear messages. (elint-special-forms): Add handlers for function, defalias, if, when, unless, and, or. (elint-form): Add optional argument to ignore elint-special-forms, useful to prevent recursive calls from handlers. Doc fix. Respect elint-ignored-warnings. (elint-form): Respect elint-ignored-warnings. (elint-bound-variable, elint-bound-function): New variables. (elint-unbound-variable): Respect elint-bound-variable. (elint-get-args): Respect elint-bound-function. (elint-check-cond-form): Add some simple handling for (f)boundp and featurep tests. (elint-check-defalias-form): New handler. (elint-check-let-form): Make an empty let a warning rather than an error. (elint-check-setq-form): Make an empty setq a warning rather than an error. Respect elint-ignored-warnings. (elint-check-defvar-form): Accept null doc-strings. (elint-check-conditional-form): New handler. Does some simple-minded checking of featurep and (f)boundp tests. (elint-put-function-args): New function. (elint-initialize): Use elint-scan-doc-file rather than elint-find-builtin-variables. Use elint-put-function-args. Possibly scan preloaded-file-list. (elint-scan-doc-file): Rename from elint-find-builtin-variables and extend to handle functions as well.
-rw-r--r--lisp/emacs-lisp/elint.el501
1 files changed, 397 insertions, 104 deletions
diff --git a/lisp/emacs-lisp/elint.el b/lisp/emacs-lisp/elint.el
index e90b72f65ff..27e7a7f293a 100644
--- a/lisp/emacs-lisp/elint.el
+++ b/lisp/emacs-lisp/elint.el
@@ -38,40 +38,118 @@
38 38
39;;; To do: 39;;; To do:
40 40
41;; * List of variables and functions defined in dumped lisp files.
42;; * Adding type checking. (Stop that sniggering!) 41;; * Adding type checking. (Stop that sniggering!)
43;; * Handle eval-when-compile (eg for requires, being sensitive to the 42;; * Make eval-when-compile be sensitive to the difference between
44;; difference between funcs and macros). 43;; funcs and macros.
45;; * Requires within function bodies. 44;; * Requires within function bodies.
45;; * Handle defstruct.
46;; * Prevent recursive requires.
46 47
47;;; Code: 48;;; Code:
48 49
49(defvar elint-log-buffer "*Elint*" 50(defgroup elint nil
50 "*The buffer to insert lint messages in.") 51 "Linting for Emacs Lisp."
52 :prefix "elint-"
53 :group 'maint)
54
55(defcustom elint-log-buffer "*Elint*"
56 "The buffer in which to log lint messages."
57 :type 'string
58 :safe 'stringp
59 :group 'elint)
60
61(defcustom elint-scan-preloaded t
62 "Non-nil means to scan `preloaded-file-list' when initializing.
63Otherwise, just scan the DOC file for functions and variables.
64This is faster, but less accurate, since it misses undocumented features.
65This may result in spurious warnings about unknown functions, etc."
66 :type 'boolean
67 :safe 'booleanp
68 :group 'elint
69 :version "23.2")
70
71(defcustom elint-ignored-warnings nil
72 "If non-nil, a list of issue types that Elint should ignore.
73This is useful if Elint has trouble understanding your code and
74you need to suppress lots of spurious warnings. The valid list elements
75are as follows, and suppress messages about the indicated features:
76 undefined-functions - calls to unknown functions
77 unbound-reference - reference to unknown variables
78 unbound-assignment - assignment to unknown variables
79 macro-expansions - failure to expand macros
80 empty-let - let-bindings with empty variable lists"
81 :type '(choice (const :tag "Don't suppress any warnings" nil)
82 (repeat :tag "List of issues to ignore"
83 (choice (const undefined-functions
84 :tag "Calls to unknown functions")
85 (const unbound-reference
86 :tag "Reference to unknown variables")
87 (const unbound-assignment
88 :tag "Assignment to unknown variables")
89 (const macro-expansion
90 :tag "Failure to expand macros")
91 (const empty-let
92 :tag "Let-binding with empty varlist"))))
93 :safe (lambda (value) (or (null value)
94 (and (listp value)
95 (equal value
96 (mapcar
97 (lambda (e)
98 (if (memq e
99 '(undefined-functions
100 unbound-reference
101 unbound-assignment
102 macro-expansion
103 empty-let))
104 e))
105 value)))))
106 :version "23.2"
107 :group 'elint)
108
109(defcustom elint-directory-skip-re "\\(ldefs-boot\\|loaddefs\\)\\.el\\'"
110 "If nil, a regexp matching files to skip when linting a directory."
111 :type '(choice (const :tag "Lint all files" nil)
112 (regexp :tag "Regexp to skip"))
113 :safe 'string-or-null-p
114 :group 'elint
115 :version "23.2")
51 116
52;;; 117;;;
53;;; Data 118;;; Data
54;;; 119;;;
55 120
56
57;; FIXME does this serve any useful purpose now elint-builtin-variables exists? 121;; FIXME does this serve any useful purpose now elint-builtin-variables exists?
58(defconst elint-standard-variables '(local-write-file-hooks vc-mode) 122(defconst elint-standard-variables '(local-write-file-hooks vc-mode)
59 "Standard buffer local variables, excluding `elint-builtin-variables'.") 123 "Standard buffer local variables, excluding `elint-builtin-variables'.")
60 124
61(defvar elint-builtin-variables nil 125(defvar elint-builtin-variables nil
62 "List of built-in variables. Set by `elint-initialize'.") 126 "List of built-in variables. Set by `elint-initialize'.
127This is actually all those documented in the DOC file, which includes
128built-in variables and those from dumped Lisp files.")
63 129
64(defvar elint-autoloaded-variables nil 130(defvar elint-autoloaded-variables nil
65 "List of `loaddefs.el' variables. Set by `elint-initialize'.") 131 "List of `loaddefs.el' variables. Set by `elint-initialize'.")
66 132
67;; FIXME dumped variables and functions. 133(defvar elint-preloaded-env nil
134 "Environment defined by the preloaded (dumped) Lisp files.
135Set by `elint-initialize', if `elint-scan-preloaded' is non-nil.")
68 136
69(defconst elint-unknown-builtin-args nil 137(defconst elint-unknown-builtin-args
138 ;; encode-time allows extra arguments for use with decode-time.
139 ;; For some reason, some people seem to like to use them in other cases.
140 '((encode-time second minute hour day month year &rest zone))
70 "Those built-ins for which we can't find arguments, if any.") 141 "Those built-ins for which we can't find arguments, if any.")
71 142
72(defconst elint-extra-errors '(file-locked file-supersession ftp-error) 143(defvar elint-extra-errors '(file-locked file-supersession ftp-error)
73 "Errors without error-message or error-confitions properties.") 144 "Errors without error-message or error-confitions properties.")
74 145
146(defconst elint-preloaded-skip-re
147 (regexp-opt '("loaddefs.el" "loadup.el" "cus-start" "language/"
148 "eucjp-ms" "mule-conf" "/characters" "/charprop"
149 "cp51932"))
150 "Regexp matching elements of `preloaded-file-list' to ignore.
151We ignore them because they contain no definitions of use to Elint.")
152
75;;; 153;;;
76;;; ADT: top-form 154;;; ADT: top-form
77;;; 155;;;
@@ -188,7 +266,8 @@ This environment can be passed to `macroexpand'."
188;; cf byte-recompile-directory. 266;; cf byte-recompile-directory.
189;;;###autoload 267;;;###autoload
190(defun elint-directory (directory) 268(defun elint-directory (directory)
191 "Lint all the .el files in DIRECTORY." 269 "Lint all the .el files in DIRECTORY.
270A complicated directory may require a lot of memory."
192 (interactive "DElint directory: ") 271 (interactive "DElint directory: ")
193 (let ((elint-running t)) 272 (let ((elint-running t))
194 (dolist (file (directory-files directory t)) 273 (dolist (file (directory-files directory t))
@@ -196,7 +275,9 @@ This environment can be passed to `macroexpand'."
196 (when (and (string-match "\\.el\\'" file) 275 (when (and (string-match "\\.el\\'" file)
197 (file-readable-p file) 276 (file-readable-p file)
198 (not (auto-save-file-name-p file))) 277 (not (auto-save-file-name-p file)))
199 (elint-file file)))) 278 (if (string-match elint-directory-skip-re file)
279 (message "Skipping file %s" file)
280 (elint-file file)))))
200 (elint-set-mode-line)) 281 (elint-set-mode-line))
201 282
202;;;###autoload 283;;;###autoload
@@ -249,6 +330,12 @@ Will be local in linted buffers.")
249 "The last time the buffers env was updated. 330 "The last time the buffers env was updated.
250Is measured in buffer-modified-ticks and is local in linted buffers.") 331Is measured in buffer-modified-ticks and is local in linted buffers.")
251 332
333;; This is a minor optimization. It is local to every buffer, and so
334;; does not prevent recursive requirs. It does not list the requires
335;; of requires.
336(defvar elint-features nil
337 "List of all libraries this buffer has required, or that have been provided.")
338
252(defun elint-update-env () 339(defun elint-update-env ()
253 "Update the elint environment in the current buffer. 340 "Update the elint environment in the current buffer.
254Don't do anything if the buffer hasn't been changed since this 341Don't do anything if the buffer hasn't been changed since this
@@ -262,26 +349,37 @@ Returns the forms."
262 elint-buffer-forms 349 elint-buffer-forms
263 ;; Remake env 350 ;; Remake env
264 (set (make-local-variable 'elint-buffer-forms) (elint-get-top-forms)) 351 (set (make-local-variable 'elint-buffer-forms) (elint-get-top-forms))
352 (set (make-local-variable 'elint-features) nil)
265 (set (make-local-variable 'elint-buffer-env) 353 (set (make-local-variable 'elint-buffer-env)
266 (elint-init-env elint-buffer-forms)) 354 (elint-init-env elint-buffer-forms))
355 (if elint-preloaded-env
356 (elint-env-add-env elint-preloaded-env elint-buffer-env))
267 (set (make-local-variable 'elint-last-env-time) (buffer-modified-tick)) 357 (set (make-local-variable 'elint-last-env-time) (buffer-modified-tick))
268 elint-buffer-forms)) 358 elint-buffer-forms))
269 359
270(defun elint-get-top-forms () 360(defun elint-get-top-forms ()
271 "Collect all the top forms in the current buffer." 361 "Collect all the top forms in the current buffer."
272 (save-excursion 362 (save-excursion
273 (let ((tops nil)) 363 (let (tops)
274 (goto-char (point-min)) 364 (goto-char (point-min))
275 (while (elint-find-next-top-form) 365 (while (elint-find-next-top-form)
276 (let ((pos (point))) 366 (let ((elint-current-pos (point)))
277 (condition-case nil 367 ;; non-list check could be here too. errors may be out of seq.
278 (setq tops (cons 368 ;; quoted check cannot be elsewhere, since quotes skipped.
279 (elint-make-top-form (read (current-buffer)) pos) 369 (if (looking-back "'")
280 tops)) 370 ;; Eg cust-print.el uses ' as a comment syntax.
281 (end-of-file 371 (elint-warning "Skipping quoted form `'%.20s...'"
282 (goto-char pos) 372 (read (current-buffer)))
283 (error "Missing ')' in top form: %s" 373 (condition-case nil
284 (buffer-substring pos (line-end-position))))))) 374 (setq tops (cons
375 (elint-make-top-form (read (current-buffer))
376 elint-current-pos)
377 tops))
378 (end-of-file
379 (goto-char elint-current-pos)
380 (error "Missing ')' in top form: %s"
381 (buffer-substring elint-current-pos
382 (line-end-position))))))))
285 (nreverse tops)))) 383 (nreverse tops))))
286 384
287(defun elint-find-next-top-form () 385(defun elint-find-next-top-form ()
@@ -290,6 +388,81 @@ Return nil if there are no more forms, t otherwise."
290 (parse-partial-sexp (point) (point-max) nil t) 388 (parse-partial-sexp (point) (point-max) nil t)
291 (not (eobp))) 389 (not (eobp)))
292 390
391(defvar env) ; from elint-init-env
392
393(defun elint-init-form (form)
394 "Process FORM, adding to ENV if recognized."
395 (cond
396 ;; Eg nnmaildir seems to use [] as a form of comment syntax.
397 ((not (listp form))
398 (elint-warning "Skipping non-list form `%s'" form))
399 ;; Add defined variable
400 ((memq (car form) '(defvar defconst defcustom))
401 (setq env (elint-env-add-var env (cadr form))))
402 ;; Add function
403 ((memq (car form) '(defun defsubst))
404 (setq env (elint-env-add-func env (cadr form) (nth 2 form))))
405 ;; FIXME needs a handler to say second arg is not a variable when we come
406 ;; to scan the form.
407 ((eq (car form) 'define-derived-mode)
408 (setq env (elint-env-add-func env (cadr form) ())
409 env (elint-env-add-var env (cadr form))
410 env (elint-env-add-var env (intern (format "%s-map" (cadr form))))))
411 ((eq (car form) 'define-minor-mode)
412 (setq env (elint-env-add-func env (cadr form) '(&optional arg))
413 ;; FIXME mode map?
414 env (elint-env-add-var env (cadr form))))
415 ((and (eq (car form) 'easy-menu-define)
416 (cadr form))
417 (setq env (elint-env-add-func env (cadr form) '(event))
418 env (elint-env-add-var env (cadr form))))
419 ;; FIXME it would be nice to check the autoloads are correct.
420 ((eq (car form) 'autoload)
421 (setq env (elint-env-add-func env (cadr (cadr form)) 'unknown)))
422 ((eq (car form) 'declare-function)
423 (setq env (elint-env-add-func env (cadr form)
424 (if (or (< (length form) 4)
425 (eq (nth 3 form) t))
426 'unknown
427 (nth 3 form)))))
428 ((and (eq (car form) 'defalias) (listp (nth 2 form)))
429 ;; If the alias points to something already in the environment,
430 ;; add the alias to the environment with the same arguments.
431 ;; FIXME symbol-function, eg backquote.el?
432 (let ((def (elint-env-find-func env (cadr (nth 2 form)))))
433 (setq env (elint-env-add-func env (cadr (cadr form))
434 (if def (cadr def) 'unknown)))))
435 ;; Add macro, both as a macro and as a function
436 ((eq (car form) 'defmacro)
437 (setq env (elint-env-add-macro env (cadr form)
438 (cons 'lambda (cddr form)))
439 env (elint-env-add-func env (cadr form) (nth 2 form))))
440 ((and (eq (car form) 'put)
441 (= 4 (length form))
442 (eq (car-safe (cadr form)) 'quote)
443 (equal (nth 2 form) '(quote error-conditions)))
444 (set (make-local-variable 'elint-extra-errors)
445 (cons (cadr (cadr form)) elint-extra-errors)))
446 ((eq (car form) 'provide)
447 (add-to-list 'elint-features (eval (cadr form))))
448 ;; Import variable definitions
449 ((memq (car form) '(require cc-require cc-require-when-compile))
450 (let ((name (eval (cadr form)))
451 (file (eval (nth 2 form)))
452 (elint-doing-cl (bound-and-true-p elint-doing-cl)))
453 (unless (memq name elint-features)
454 (add-to-list 'elint-features name)
455 ;; cl loads cl-macs in an opaque manner.
456 ;; Since cl-macs requires cl, we can just process cl-macs.
457 (and (eq name 'cl) (not elint-doing-cl)
458 ;; We need cl if elint-form is to be able to expand cl macros.
459 (require 'cl)
460 (setq name 'cl-macs
461 file nil
462 elint-doing-cl t)) ; blech
463 (setq env (elint-add-required-env env name file))))))
464 env)
465
293(defun elint-init-env (forms) 466(defun elint-init-env (forms)
294 "Initialize the environment from FORMS." 467 "Initialize the environment from FORMS."
295 (let ((env (elint-make-env)) 468 (let ((env (elint-make-env))
@@ -297,45 +470,14 @@ Return nil if there are no more forms, t otherwise."
297 (while forms 470 (while forms
298 (setq form (elint-top-form-form (car forms)) 471 (setq form (elint-top-form-form (car forms))
299 forms (cdr forms)) 472 forms (cdr forms))
300 (cond 473 ;; FIXME eval-when-compile should be treated differently (macros).
301 ;; Eg nnmaildir seems to use [] as a form of comment syntax. 474 ;; Could bind something that makes elint-init-form only check
302 ((not (listp form)) 475 ;; defmacros.
303 (elint-error "Skipping non-list form `%s'" form)) 476 (if (memq (car-safe form)
304 ;; Add defined variable 477 '(eval-and-compile eval-when-compile progn prog1 prog2
305 ((memq (car form) '(defvar defconst defcustom)) 478 with-no-warnings))
306 (setq env (elint-env-add-var env (cadr form)))) 479 (mapc 'elint-init-form (cdr form))
307 ;; Add function 480 (elint-init-form form)))
308 ((memq (car form) '(defun defsubst))
309 (setq env (elint-env-add-func env (cadr form) (nth 2 form))))
310 ((eq (car form) 'define-derived-mode)
311 (setq env (elint-env-add-func env (cadr form) ())
312 env (elint-env-add-var env (cadr form))))
313 ;; FIXME it would be nice to check the autoloads are correct.
314 ((eq (car form) 'autoload)
315 (setq env (elint-env-add-func env (cadr (cadr form)) 'unknown)))
316 ((eq (car form) 'declare-function)
317 (setq env (elint-env-add-func env (cadr form)
318 (if (or (< (length form) 4)
319 (eq (nth 3 form) t))
320 'unknown
321 (nth 3 form)))))
322 ((and (eq (car form) 'defalias) (listp (nth 2 form)))
323 ;; If the alias points to something already in the environment,
324 ;; add the alias to the environment with the same arguments.
325 (let ((def (elint-env-find-func env (cadr (nth 2 form)))))
326 ;; FIXME warn if the alias target is unknown.
327 (setq env (elint-env-add-func env (cadr (cadr form))
328 (if def (cadr def) 'unknown)))))
329 ;; Add macro, both as a macro and as a function
330 ((eq (car form) 'defmacro)
331 (setq env (elint-env-add-macro env (cadr form)
332 (cons 'lambda (cddr form)))
333 env (elint-env-add-func env (cadr form) (nth 2 form))))
334 ;; Import variable definitions
335 ((eq (car form) 'require)
336 (let ((name (eval (cadr form)))
337 (file (eval (nth 2 form))))
338 (setq env (elint-add-required-env env name file))))))
339 env)) 481 env))
340 482
341(defun elint-add-required-env (env name file) 483(defun elint-add-required-env (env name file)
@@ -349,15 +491,16 @@ Return nil if there are no more forms, t otherwise."
349 (lib1 (locate-library (concat libname ".el") t)) 491 (lib1 (locate-library (concat libname ".el") t))
350 (lib (or lib1 (locate-library libname t)))) 492 (lib (or lib1 (locate-library libname t))))
351 ;; Clear the messages :-/ 493 ;; Clear the messages :-/
352 (message nil) 494 ;; (Messes up the "Initializing elint..." message.)
495;;; (message nil)
353 (if lib 496 (if lib
354 (save-excursion 497 (save-excursion
355 ;; FIXME this doesn't use a temp buffer, because it 498 ;; FIXME this doesn't use a temp buffer, because it
356 ;; stores the result in buffer-local variables so that 499 ;; stores the result in buffer-local variables so that
357 ;; it can be reused. 500 ;; it can be reused.
358 (set-buffer (find-file-noselect lib)) 501 (set-buffer (find-file-noselect lib))
359 (elint-update-env) 502 (elint-update-env)
360 (setq env (elint-env-add-env env elint-buffer-env))) 503 (setq env (elint-env-add-env env elint-buffer-env)))
361 ;;; (with-temp-buffer 504 ;;; (with-temp-buffer
362 ;;; (insert-file-contents lib) 505 ;;; (insert-file-contents lib)
363 ;;; (with-syntax-table emacs-lisp-mode-syntax-table 506 ;;; (with-syntax-table emacs-lisp-mode-syntax-table
@@ -391,10 +534,12 @@ Return nil if there are no more forms, t otherwise."
391 (let* . elint-check-let-form) 534 (let* . elint-check-let-form)
392 (setq . elint-check-setq-form) 535 (setq . elint-check-setq-form)
393 (quote . elint-check-quote-form) 536 (quote . elint-check-quote-form)
537 (function . elint-check-quote-form)
394 (cond . elint-check-cond-form) 538 (cond . elint-check-cond-form)
395 (lambda . elint-check-defun-form) 539 (lambda . elint-check-defun-form)
396 (function . elint-check-function-form) 540 (function . elint-check-function-form)
397 (setq-default . elint-check-setq-form) 541 (setq-default . elint-check-setq-form)
542 (defalias . elint-check-defalias-form)
398 (defun . elint-check-defun-form) 543 (defun . elint-check-defun-form)
399 (defsubst . elint-check-defun-form) 544 (defsubst . elint-check-defun-form)
400 (defmacro . elint-check-defun-form) 545 (defmacro . elint-check-defun-form)
@@ -402,16 +547,22 @@ Return nil if there are no more forms, t otherwise."
402 (defconst . elint-check-defvar-form) 547 (defconst . elint-check-defvar-form)
403 (defcustom . elint-check-defcustom-form) 548 (defcustom . elint-check-defcustom-form)
404 (macro . elint-check-macro-form) 549 (macro . elint-check-macro-form)
405 (condition-case . elint-check-condition-case-form)) 550 (condition-case . elint-check-condition-case-form)
551 (if . elint-check-conditional-form)
552 (when . elint-check-conditional-form)
553 (unless . elint-check-conditional-form)
554 (and . elint-check-conditional-form)
555 (or . elint-check-conditional-form))
406 "Functions to call when some special form should be linted.") 556 "Functions to call when some special form should be linted.")
407 557
408(defun elint-form (form env) 558(defun elint-form (form env &optional nohandler)
409 "Lint FORM in the environment ENV. 559 "Lint FORM in the environment ENV.
410The environment created by the form is returned." 560Optional argument NOHANDLER non-nil means ignore `elint-special-forms'.
561Returns the environment created by the form."
411 (cond 562 (cond
412 ((consp form) 563 ((consp form)
413 (let ((func (cdr (assq (car form) elint-special-forms)))) 564 (let ((func (cdr (assq (car form) elint-special-forms))))
414 (if func 565 (if (and func (not nohandler))
415 ;; Special form 566 ;; Special form
416 (funcall func form env) 567 (funcall func form env)
417 568
@@ -421,7 +572,8 @@ The environment created by the form is returned."
421 (cond 572 (cond
422 ((eq args 'undefined) 573 ((eq args 'undefined)
423 (setq argsok nil) 574 (setq argsok nil)
424 (elint-error "Call to undefined function: %s" func)) 575 (or (memq 'undefined-functions elint-ignored-warnings)
576 (elint-error "Call to undefined function: %s" func)))
425 577
426 ((eq args 'unknown) nil) 578 ((eq args 'unknown) nil)
427 579
@@ -436,7 +588,8 @@ The environment created by the form is returned."
436 (elint-form 588 (elint-form
437 (macroexpand form (elint-env-macro-env env)) env) 589 (macroexpand form (elint-env-macro-env env)) env)
438 (error 590 (error
439 (elint-error "Elint failed to expand macro: %s" func) 591 (or (memq 'macro-expansion elint-ignored-warnings)
592 (elint-error "Elint failed to expand macro: %s" func))
440 env)) 593 env))
441 env) 594 env)
442 595
@@ -453,9 +606,10 @@ The environment created by the form is returned."
453 (elint-forms (cdr form) env)))))))) 606 (elint-forms (cdr form) env))))))))
454 ((symbolp form) 607 ((symbolp form)
455 ;; :foo variables are quoted 608 ;; :foo variables are quoted
456 (if (and (/= (aref (symbol-name form) 0) ?:) 609 (and (/= (aref (symbol-name form) 0) ?:)
457 (elint-unbound-variable form env)) 610 (not (memq 'unbound-reference elint-ignored-warnings))
458 (elint-warning "Reference to unbound symbol: %s" form)) 611 (elint-unbound-variable form env)
612 (elint-warning "Reference to unbound symbol: %s" form))
459 env) 613 env)
460 614
461 (t env))) 615 (t env)))
@@ -470,9 +624,13 @@ The environment created by the form is returned."
470 (elint-error "Elint failed to parse form: %s" forms) 624 (elint-error "Elint failed to parse form: %s" forms)
471 env)) 625 env))
472 626
627(defvar elint-bound-variable nil
628 "Name of a temporarily bound symbol.")
629
473(defun elint-unbound-variable (var env) 630(defun elint-unbound-variable (var env)
474 "T if VAR is unbound in ENV." 631 "T if VAR is unbound in ENV."
475 (not (or (memq var '(nil t)) 632 (not (or (memq var '(nil t))
633 (eq var elint-bound-variable)
476 (elint-env-find-var env var) 634 (elint-env-find-var env var)
477 (memq var elint-builtin-variables) 635 (memq var elint-builtin-variables)
478 (memq var elint-autoloaded-variables) 636 (memq var elint-autoloaded-variables)
@@ -509,6 +667,9 @@ The environment created by the form is returned."
509 t))) 667 t)))
510 ok)) 668 ok))
511 669
670(defvar elint-bound-function nil
671 "Name of a temporarily bound function symbol.")
672
512(defun elint-get-args (func env) 673(defun elint-get-args (func env)
513 "Find the args of FUNC in ENV. 674 "Find the args of FUNC in ENV.
514Returns `unknown' if we couldn't find arguments." 675Returns `unknown' if we couldn't find arguments."
@@ -516,13 +677,15 @@ Returns `unknown' if we couldn't find arguments."
516 (if f 677 (if f
517 (cadr f) 678 (cadr f)
518 (if (symbolp func) 679 (if (symbolp func)
519 (if (fboundp func) 680 (if (eq func elint-bound-function)
520 (let ((fcode (indirect-function func))) 681 'unknown
521 (if (subrp fcode) 682 (if (fboundp func)
522 ;; FIXME builtins with no args have args = nil. 683 (let ((fcode (indirect-function func)))
523 (or (get func 'elint-args) 'unknown) 684 (if (subrp fcode)
524 (elint-find-args-in-code fcode))) 685 ;; FIXME builtins with no args have args = nil.
525 'undefined) 686 (or (get func 'elint-args) 'unknown)
687 (elint-find-args-in-code fcode)))
688 'undefined))
526 (elint-find-args-in-code func))))) 689 (elint-find-args-in-code func)))))
527 690
528(defun elint-find-args-in-code (code) 691(defun elint-find-args-in-code (code)
@@ -543,10 +706,25 @@ CODE can be a lambda expression, a macro, or byte-compiled code."
543 706
544(defun elint-check-cond-form (form env) 707(defun elint-check-cond-form (form env)
545 "Lint a cond FORM in ENV." 708 "Lint a cond FORM in ENV."
546 (dolist (f (cdr form) env) 709 (dolist (f (cdr form))
547 (if (consp f) 710 (if (consp f)
548 (elint-forms f env) 711 (let ((test (car f)))
549 (elint-error "cond clause should be a list: %s" f)))) 712 (cond ((equal test '(featurep (quote xemacs))))
713 ((equal test '(not (featurep (quote emacs)))))
714 ;; FIXME (and (boundp 'foo)
715 ((and (eq (car-safe test) 'fboundp)
716 (= 2 (length test))
717 (eq (car-safe (cadr test)) 'quote))
718 (let ((elint-bound-function (cadr (cadr test))))
719 (elint-forms f env)))
720 ((and (eq (car-safe test) 'boundp)
721 (= 2 (length test))
722 (eq (car-safe (cadr test)) 'quote))
723 (let ((elint-bound-variable (cadr (cadr test))))
724 (elint-forms f env)))
725 (t (elint-forms f env))))
726 (elint-error "cond clause should be a list: %s" f)))
727 env)
550 728
551(defun elint-check-defun-form (form env) 729(defun elint-check-defun-form (form env)
552 "Lint a defun/defmacro/lambda FORM in ENV." 730 "Lint a defun/defmacro/lambda FORM in ENV."
@@ -557,12 +735,30 @@ CODE can be a lambda expression, a macro, or byte-compiled code."
557 (car form)) 735 (car form))
558 (elint-forms (cdr form) env)) 736 (elint-forms (cdr form) env))
559 737
738(defun elint-check-defalias-form (form env)
739 "Lint a defalias FORM in ENV."
740 (let ((alias (cadr form))
741 (target (nth 2 form)))
742 (and (eq (car-safe alias) 'quote)
743 (eq (car-safe target) 'quote)
744 (eq (elint-get-args (cadr target) env) 'undefined)
745 (elint-warning "Alias `%s' has unknown target `%s'"
746 (cadr alias) (cadr target))))
747 (elint-form form env t))
748
560(defun elint-check-let-form (form env) 749(defun elint-check-let-form (form env)
561 "Lint the let/let* FORM in ENV." 750 "Lint the let/let* FORM in ENV."
562 (let ((varlist (cadr form))) 751 (let ((varlist (cadr form)))
563 (if (not varlist) 752 (if (not varlist)
564 (progn 753 (if (> (length form) 2)
565 (elint-error "Missing varlist in let: %s" form) 754 ;; An empty varlist is not really an error. Eg some cl macros
755 ;; can expand to such a form.
756 (progn
757 (or (memq 'empty-let elint-ignored-warnings)
758 (elint-warning "Empty varlist in let: %s" form))
759 ;; Lint the body forms
760 (elint-forms (cddr form) env))
761 (elint-error "Malformed let: %s" form)
566 env) 762 env)
567 ;; Check for (let (a (car b)) ...) type of error 763 ;; Check for (let (a (car b)) ...) type of error
568 (if (and (= (length varlist) 2) 764 (if (and (= (length varlist) 2)
@@ -593,7 +789,8 @@ CODE can be a lambda expression, a macro, or byte-compiled code."
593(defun elint-check-setq-form (form env) 789(defun elint-check-setq-form (form env)
594 "Lint the setq FORM in ENV." 790 "Lint the setq FORM in ENV."
595 (or (= (mod (length form) 2) 1) 791 (or (= (mod (length form) 2) 1)
596 (elint-error "Missing value in setq: %s" form)) 792 ;; (setq foo) is valid and equivalent to (setq foo nil).
793 (elint-warning "Missing value in setq: %s" form))
597 (let ((newenv env) 794 (let ((newenv env)
598 sym val) 795 sym val)
599 (setq form (cdr form)) 796 (setq form (cdr form))
@@ -602,8 +799,9 @@ CODE can be a lambda expression, a macro, or byte-compiled code."
602 val (car (cdr form)) 799 val (car (cdr form))
603 form (cdr (cdr form))) 800 form (cdr (cdr form)))
604 (if (symbolp sym) 801 (if (symbolp sym)
605 (if (elint-unbound-variable sym newenv) 802 (and (not (memq 'unbound-assignment elint-ignored-warnings))
606 (elint-warning "Setting previously unbound symbol: %s" sym)) 803 (elint-unbound-variable sym newenv)
804 (elint-warning "Setting previously unbound symbol: %s" sym))
607 (elint-error "Setting non-symbol in setq: %s" sym)) 805 (elint-error "Setting non-symbol in setq: %s" sym))
608 (elint-form val newenv) 806 (elint-form val newenv)
609 (if (symbolp sym) 807 (if (symbolp sym)
@@ -614,7 +812,8 @@ CODE can be a lambda expression, a macro, or byte-compiled code."
614 "Lint the defvar/defconst FORM in ENV." 812 "Lint the defvar/defconst FORM in ENV."
615 (if (or (= (length form) 2) 813 (if (or (= (length form) 2)
616 (= (length form) 3) 814 (= (length form) 3)
617 (and (= (length form) 4) (stringp (nth 3 form)))) 815 ;; Eg the defcalcmodevar macro can expand with a nil doc-string.
816 (and (= (length form) 4) (string-or-null-p (nth 3 form))))
618 (elint-env-add-global-var (elint-form (nth 2 form) env) 817 (elint-env-add-global-var (elint-form (nth 2 form) env)
619 (car (cdr form))) 818 (car (cdr form)))
620 (elint-error "Malformed variable declaration: %s" form) 819 (elint-error "Malformed variable declaration: %s" form)
@@ -636,6 +835,8 @@ CODE can be a lambda expression, a macro, or byte-compiled code."
636 (cond 835 (cond
637 ((symbolp func) 836 ((symbolp func)
638 (or (elint-env-find-func env func) 837 (or (elint-env-find-func env func)
838 ;; FIXME potentially bogus, since it uses the current
839 ;; environment rather than a clean one.
639 (fboundp func) 840 (fboundp func)
640 (elint-warning "Reference to undefined function: %s" form)) 841 (elint-warning "Reference to undefined function: %s" form))
641 env) 842 env)
@@ -680,6 +881,72 @@ CODE can be a lambda expression, a macro, or byte-compiled code."
680 (elint-forms (cdr err) newenv)))) 881 (elint-forms (cdr err) newenv))))
681 resenv)) 882 resenv))
682 883
884;; For the featurep parts, an alternative is to have
885;; elint-get-top-forms skip the irrelevant branches.
886(defun elint-check-conditional-form (form env)
887 "Check the when/unless/and/or FORM in ENV.
888Does basic handling of `featurep' tests."
889 (let ((func (car form))
890 (test (cadr form))
891 sym)
892 ;; Misses things like (and t (featurep 'xemacs))
893 ;; Check byte-compile-maybe-guarded.
894 (cond ((and (memq func '(when and))
895 (eq (car-safe test) 'boundp)
896 (= 2 (length test))
897 (eq (car-safe (cadr test)) 'quote))
898 ;; Cf elint-check-let-form, which modifies the whole ENV.
899 (let ((elint-bound-variable (cadr (cadr test))))
900 (elint-form form env t)))
901 ((and (memq func '(when and))
902 (eq (car-safe test) 'fboundp)
903 (= 2 (length test))
904 (eq (car-safe (cadr test)) 'quote))
905 (let ((elint-bound-function (cadr (cadr test))))
906 (elint-form form env t)))
907 ;; Let's not worry about (if (not (boundp...
908 ((and (eq func 'if)
909 (eq (car-safe test) 'boundp)
910 (= 2 (length test))
911 (eq (car-safe (cadr test)) 'quote))
912 (let ((elint-bound-variable (cadr (cadr test))))
913 (elint-form (nth 2 form) env))
914 (dolist (f (nthcdr 3 form))
915 (elint-form f env)))
916 ((and (eq func 'if)
917 (eq (car-safe test) 'fboundp)
918 (= 2 (length test))
919 (eq (car-safe (cadr test)) 'quote))
920 (let ((elint-bound-function (cadr (cadr test))))
921 (elint-form (nth 2 form) env))
922 (dolist (f (nthcdr 3 form))
923 (elint-form f env)))
924 ((and (memq func '(when and)) ; skip all
925 (or (null test)
926 (member test '((featurep (quote xemacs))
927 (not (featurep (quote emacs)))))
928 (and (eq (car-safe test) 'and)
929 (equal (car-safe (cdr test))
930 '(featurep (quote xemacs)))))))
931 ((and (memq func '(unless or))
932 (equal test '(featurep (quote emacs)))))
933 ((and (eq func 'if)
934 (or (null test) ; eg custom-browse-insert-prefix
935 (member test '((featurep (quote xemacs))
936 (not (featurep (quote emacs)))))
937 (and (eq (car-safe test) 'and)
938 (equal (car-safe (cdr test))
939 '(featurep (quote xemacs))))))
940 (dolist (f (nthcdr 3 form))
941 (elint-form f env))) ; lint the else branch
942 ((and (eq func 'if)
943 (equal test '(featurep (quote emacs))))
944 (elint-form (nth 2 form) env)) ; lint the if branch
945 ;; Process conditional as normal, without handler.
946 (t
947 (elint-form form env t))))
948 env)
949
683;;; 950;;;
684;;; Message functions 951;;; Message functions
685;;; 952;;;
@@ -779,6 +1046,13 @@ Insert HEADER followed by a blank line if non-nil."
779;;; Initializing code 1046;;; Initializing code
780;;; 1047;;;
781 1048
1049(defun elint-put-function-args (func args)
1050 "Mark function FUNC as having argument list ARGS."
1051 (and (symbolp func)
1052 args
1053 (not (eq args 'unknown))
1054 (put func 'elint-args args)))
1055
782;;;###autoload 1056;;;###autoload
783(defun elint-initialize (&optional reinit) 1057(defun elint-initialize (&optional reinit)
784 "Initialize elint. 1058 "Initialize elint.
@@ -788,23 +1062,29 @@ optional prefix argument REINIT is non-nil."
788 (if (and elint-builtin-variables (not reinit)) 1062 (if (and elint-builtin-variables (not reinit))
789 (message "Elint is already initialized") 1063 (message "Elint is already initialized")
790 (message "Initializing elint...") 1064 (message "Initializing elint...")
791 (setq elint-builtin-variables (elint-find-builtin-variables) 1065 (setq elint-builtin-variables (elint-scan-doc-file)
792 elint-autoloaded-variables (elint-find-autoloaded-variables)) 1066 elint-autoloaded-variables (elint-find-autoloaded-variables))
793 (mapc (lambda (x) (or (not (symbolp (car x))) 1067 (mapc (lambda (x) (elint-put-function-args (car x) (cdr x)))
794 (eq (cdr x) 'unknown)
795 (put (car x) 'elint-args (cdr x))))
796 (elint-find-builtin-args)) 1068 (elint-find-builtin-args))
797 (if elint-unknown-builtin-args 1069 (if elint-unknown-builtin-args
798 (mapc (lambda (x) (put (car x) 'elint-args (cdr x))) 1070 (mapc (lambda (x) (elint-put-function-args (car x) (cdr x)))
799 elint-unknown-builtin-args)) 1071 elint-unknown-builtin-args))
1072 (when elint-scan-preloaded
1073 (dolist (lib preloaded-file-list)
1074 ;; Skip files that contain nothing of use to us.
1075 (unless (string-match elint-preloaded-skip-re lib)
1076 (setq elint-preloaded-env
1077 (elint-add-required-env elint-preloaded-env nil lib)))))
800 (message "Initializing elint...done"))) 1078 (message "Initializing elint...done")))
801 1079
802 1080
803(defun elint-find-builtin-variables () 1081;; This includes all the built-in and dumped things with documentation.
804 "Return a list of all built-in variables." 1082(defun elint-scan-doc-file ()
1083 "Scan the DOC file for function and variables.
1084Marks the function wih their arguments, and returns a list of variables."
805 ;; Cribbed from help-fns.el. 1085 ;; Cribbed from help-fns.el.
806 (let ((docbuf " *DOC*") 1086 (let ((docbuf " *DOC*")
807 vars var) 1087 vars sym args)
808 (save-excursion 1088 (save-excursion
809 (if (get-buffer docbuf) 1089 (if (get-buffer docbuf)
810 (progn 1090 (progn
@@ -813,12 +1093,25 @@ optional prefix argument REINIT is non-nil."
813 (set-buffer (get-buffer-create docbuf)) 1093 (set-buffer (get-buffer-create docbuf))
814 (insert-file-contents-literally 1094 (insert-file-contents-literally
815 (expand-file-name internal-doc-file-name doc-directory))) 1095 (expand-file-name internal-doc-file-name doc-directory)))
816 (while (search-forward "V" nil t) 1096 (while (re-search-forward "\\([VF]\\)" nil t)
817 (and (setq var (intern-soft 1097 (when (setq sym (intern-soft (buffer-substring (point)
818 (buffer-substring (point) (line-end-position)))) 1098 (line-end-position))))
819 (boundp var) 1099 (if (string-equal (match-string 1) "V")
820 (setq vars (cons var vars)))) 1100 ;; Excludes platform-specific stuff not relevant to the
821 vars))) 1101 ;; running platform.
1102 (if (boundp sym) (setq vars (cons sym vars)))
1103 ;; Function.
1104 (when (fboundp sym)
1105 (when (re-search-forward "\\(^(fn.*)\\)?" nil t)
1106 (backward-char 1)
1107 ;; FIXME distinguish no args from not found.
1108 (and (setq args (match-string 1))
1109 (setq args
1110 (ignore-errors
1111 (read
1112 (replace-regexp-in-string "^(fn ?" "(" args))))
1113 (elint-put-function-args sym args))))))))
1114 vars))
822 1115
823(defun elint-find-autoloaded-variables () 1116(defun elint-find-autoloaded-variables ()
824 "Return a list of all autoloaded variables." 1117 "Return a list of all autoloaded variables."