More tweaks of skeleton documentation wrt \n behavior at bol/eol.
[bpt/emacs.git] / lisp / emacs-lisp / elint.el
CommitLineData
e8af40ee 1;;; elint.el --- Lint Emacs Lisp
020c3567 2
ba318903 3;; Copyright (C) 1997, 2001-2014 Free Software Foundation, Inc.
020c3567
RS
4
5;; Author: Peter Liljenberg <petli@lysator.liu.se>
6;; Created: May 1997
7;; Keywords: lisp
8
9;; This file is part of GNU Emacs.
10
d6cba7ae 11;; GNU Emacs is free software: you can redistribute it and/or modify
020c3567 12;; it under the terms of the GNU General Public License as published by
d6cba7ae
GM
13;; the Free Software Foundation, either version 3 of the License, or
14;; (at your option) any later version.
020c3567
RS
15
16;; GNU Emacs is distributed in the hope that it will be useful,
17;; but WITHOUT ANY WARRANTY; without even the implied warranty of
18;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19;; GNU General Public License for more details.
20
21;; You should have received a copy of the GNU General Public License
d6cba7ae 22;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
020c3567
RS
23
24;;; Commentary:
25
e2d5a67f
GM
26;; This is a linter for Emacs Lisp. Currently, it mainly catches
27;; misspellings and undefined variables, although it can also catch
020c3567
RS
28;; function calls with the wrong number of arguments.
29
7a8ae964
GM
30;; To use, call elint-current-buffer or elint-defun to lint a buffer
31;; or defun. The first call runs `elint-initialize' to set up some
32;; argument data, which may take a while.
020c3567
RS
33
34;; The linter will try to "include" any require'd libraries to find
e2d5a67f 35;; the variables defined in those. There is a fair amount of voodoo
020c3567
RS
36;; involved in this, but it seems to work in normal situations.
37
020c3567
RS
38;;; To do:
39
020c3567 40;; * Adding type checking. (Stop that sniggering!)
76f2c576
GM
41;; * Make eval-when-compile be sensitive to the difference between
42;; funcs and macros.
6974be68 43;; * Requires within function bodies.
76f2c576
GM
44;; * Handle defstruct.
45;; * Prevent recursive requires.
020c3567
RS
46
47;;; Code:
48
ef501ef0
SM
49(require 'help-fns)
50
76f2c576
GM
51(defgroup elint nil
52 "Linting for Emacs Lisp."
53 :prefix "elint-"
54 :group 'maint)
55
56(defcustom elint-log-buffer "*Elint*"
57 "The buffer in which to log lint messages."
58 :type 'string
59 :safe 'stringp
60 :group 'elint)
61
62(defcustom elint-scan-preloaded t
63 "Non-nil means to scan `preloaded-file-list' when initializing.
64Otherwise, just scan the DOC file for functions and variables.
65This is faster, but less accurate, since it misses undocumented features.
66This may result in spurious warnings about unknown functions, etc."
67 :type 'boolean
68 :safe 'booleanp
69 :group 'elint
70 :version "23.2")
71
72(defcustom elint-ignored-warnings nil
73 "If non-nil, a list of issue types that Elint should ignore.
74This is useful if Elint has trouble understanding your code and
75you need to suppress lots of spurious warnings. The valid list elements
76are as follows, and suppress messages about the indicated features:
77 undefined-functions - calls to unknown functions
78 unbound-reference - reference to unknown variables
79 unbound-assignment - assignment to unknown variables
80 macro-expansions - failure to expand macros
81 empty-let - let-bindings with empty variable lists"
82 :type '(choice (const :tag "Don't suppress any warnings" nil)
83 (repeat :tag "List of issues to ignore"
84 (choice (const undefined-functions
85 :tag "Calls to unknown functions")
86 (const unbound-reference
87 :tag "Reference to unknown variables")
88 (const unbound-assignment
89 :tag "Assignment to unknown variables")
90 (const macro-expansion
91 :tag "Failure to expand macros")
92 (const empty-let
93 :tag "Let-binding with empty varlist"))))
94 :safe (lambda (value) (or (null value)
95 (and (listp value)
96 (equal value
97 (mapcar
98 (lambda (e)
99 (if (memq e
100 '(undefined-functions
101 unbound-reference
102 unbound-assignment
103 macro-expansion
104 empty-let))
105 e))
106 value)))))
107 :version "23.2"
108 :group 'elint)
109
110(defcustom elint-directory-skip-re "\\(ldefs-boot\\|loaddefs\\)\\.el\\'"
111 "If nil, a regexp matching files to skip when linting a directory."
112 :type '(choice (const :tag "Lint all files" nil)
113 (regexp :tag "Regexp to skip"))
114 :safe 'string-or-null-p
115 :group 'elint
116 :version "23.2")
020c3567 117
8e2eba09
RS
118;;;
119;;; Data
120;;;
121
d501801e 122(defconst elint-standard-variables
a61344d6
GM
123 ;; Most of these are defined in C with no documentation.
124 ;; FIXME I don't see why they shouldn't just get doc-strings.
d501801e
GM
125 '(vc-mode local-write-file-hooks activate-menubar-hook buffer-name-history
126 coding-system-history extended-command-history
d501801e 127 yes-or-no-p-history)
a61344d6
GM
128 "Standard variables, excluding `elint-builtin-variables'.
129These are variables that we cannot detect automatically for some reason.")
e2d5a67f
GM
130
131(defvar elint-builtin-variables nil
76f2c576
GM
132 "List of built-in variables. Set by `elint-initialize'.
133This is actually all those documented in the DOC file, which includes
134built-in variables and those from dumped Lisp files.")
e2d5a67f
GM
135
136(defvar elint-autoloaded-variables nil
137 "List of `loaddefs.el' variables. Set by `elint-initialize'.")
138
76f2c576
GM
139(defvar elint-preloaded-env nil
140 "Environment defined by the preloaded (dumped) Lisp files.
141Set by `elint-initialize', if `elint-scan-preloaded' is non-nil.")
e2d5a67f 142
76f2c576
GM
143(defconst elint-unknown-builtin-args
144 ;; encode-time allows extra arguments for use with decode-time.
145 ;; For some reason, some people seem to like to use them in other cases.
146 '((encode-time second minute hour day month year &rest zone))
e2d5a67f
GM
147 "Those built-ins for which we can't find arguments, if any.")
148
76f2c576 149(defvar elint-extra-errors '(file-locked file-supersession ftp-error)
fa5f7c5f 150 "Errors without `error-message' or `error-conditions' properties.")
8e2eba09 151
76f2c576
GM
152(defconst elint-preloaded-skip-re
153 (regexp-opt '("loaddefs.el" "loadup.el" "cus-start" "language/"
154 "eucjp-ms" "mule-conf" "/characters" "/charprop"
155 "cp51932"))
156 "Regexp matching elements of `preloaded-file-list' to ignore.
157We ignore them because they contain no definitions of use to Elint.")
158
020c3567
RS
159;;;
160;;; ADT: top-form
161;;;
162
163(defsubst elint-make-top-form (form pos)
164 "Create a top form.
165FORM is the form, and POS is the point where it starts in the buffer."
166 (cons form pos))
167
168(defsubst elint-top-form-form (top-form)
169 "Extract the form from a TOP-FORM."
170 (car top-form))
171
172(defsubst elint-top-form-pos (top-form)
173 "Extract the position from a TOP-FORM."
174 (cdr top-form))
175
176;;;
177;;; ADT: env
178;;;
179
180(defsubst elint-make-env ()
181 "Create an empty environment."
182 (list (list nil) nil nil))
183
184(defsubst elint-env-add-env (env newenv)
185 "Augment ENV with NEWENV.
186None of them is modified, and the new env is returned."
187 (list (append (car env) (car newenv))
e2d5a67f 188 (append (cadr env) (cadr newenv))
020c3567
RS
189 (append (car (cdr (cdr env))) (car (cdr (cdr newenv))))))
190
191(defsubst elint-env-add-var (env var)
192 "Augment ENV with the variable VAR.
193The new environment is returned, the old is unmodified."
194 (cons (cons (list var) (car env)) (cdr env)))
195
196(defsubst elint-env-add-global-var (env var)
197 "Augment ENV with the variable VAR.
198ENV is modified so VAR is seen everywhere.
199ENV is returned."
200 (nconc (car env) (list (list var)))
201 env)
202
203(defsubst elint-env-find-var (env var)
204 "Non-nil if ENV contains the variable VAR.
205Actually, a list with VAR as a single element is returned."
206 (assq var (car env)))
207
208(defsubst elint-env-add-func (env func args)
209 "Augment ENV with the function FUNC, which has the arguments ARGS.
210The new environment is returned, the old is unmodified."
211 (list (car env)
e2d5a67f 212 (cons (list func args) (cadr env))
020c3567
RS
213 (car (cdr (cdr env)))))
214
215(defsubst elint-env-find-func (env func)
216 "Non-nil if ENV contains the function FUNC.
217Actually, a list of (FUNC ARGS) is returned."
e2d5a67f 218 (assq func (cadr env)))
020c3567
RS
219
220(defsubst elint-env-add-macro (env macro def)
221 "Augment ENV with the macro named MACRO.
222DEF is the macro definition (a lambda expression or similar).
223The new environment is returned, the old is unmodified."
224 (list (car env)
e2d5a67f 225 (cadr env)
020c3567
RS
226 (cons (cons macro def) (car (cdr (cdr env))))))
227
228(defsubst elint-env-macro-env (env)
229 "Return the macro environment of ENV.
230This environment can be passed to `macroexpand'."
231 (car (cdr (cdr env))))
232
233(defsubst elint-env-macrop (env macro)
234 "Non-nil if ENV contains MACRO."
235 (assq macro (elint-env-macro-env env)))
236
237;;;
238;;; User interface
239;;;
240
6974be68
GM
241;;;###autoload
242(defun elint-file (file)
243 "Lint the file FILE."
244 (interactive "fElint file: ")
245 (setq file (expand-file-name file))
246 (or elint-builtin-variables
247 (elint-initialize))
248 (let ((dir (file-name-directory file)))
249 (let ((default-directory dir))
250 (elint-display-log))
251 (elint-set-mode-line t)
252 (with-current-buffer elint-log-buffer
253 (unless (string-equal default-directory dir)
254 (elint-log-message (format "\f\nLeaving directory `%s'"
255 default-directory) t)
256 (elint-log-message (format "Entering directory `%s'" dir) t)
257 (setq default-directory dir))))
258 (let ((str (format "Linting file %s" file)))
259 (message "%s..." str)
260 (or noninteractive
261 (elint-log-message (format "\f\n%s at %s" str (current-time-string)) t))
262 ;; elint-current-buffer clears log.
263 (with-temp-buffer
264 (insert-file-contents file)
bf01513f
GM
265 (let ((buffer-file-name file)
266 (max-lisp-eval-depth (max 1000 max-lisp-eval-depth)))
6974be68
GM
267 (with-syntax-table emacs-lisp-mode-syntax-table
268 (mapc 'elint-top-form (elint-update-env)))))
269 (elint-set-mode-line)
270 (message "%s...done" str)))
271
272;; cf byte-recompile-directory.
273;;;###autoload
274(defun elint-directory (directory)
76f2c576
GM
275 "Lint all the .el files in DIRECTORY.
276A complicated directory may require a lot of memory."
6974be68
GM
277 (interactive "DElint directory: ")
278 (let ((elint-running t))
279 (dolist (file (directory-files directory t))
280 ;; Bytecomp has emacs-lisp-file-regexp.
281 (when (and (string-match "\\.el\\'" file)
282 (file-readable-p file)
283 (not (auto-save-file-name-p file)))
76f2c576
GM
284 (if (string-match elint-directory-skip-re file)
285 (message "Skipping file %s" file)
286 (elint-file file)))))
6974be68
GM
287 (elint-set-mode-line))
288
7a8ae964 289;;;###autoload
020c3567 290(defun elint-current-buffer ()
7a8ae964 291 "Lint the current buffer.
fa5f7c5f 292If necessary, this first calls `elint-initialize'."
020c3567 293 (interactive)
7a8ae964
GM
294 (or elint-builtin-variables
295 (elint-initialize))
e2d5a67f
GM
296 (elint-clear-log (format "Linting %s" (or (buffer-file-name)
297 (buffer-name))))
020c3567 298 (elint-display-log)
6974be68 299 (elint-set-mode-line t)
eb2b0009 300 (mapc 'elint-top-form (elint-update-env))
53964682 301 ;; Tell the user we're finished. This is terribly kludgy: we set
6974be68
GM
302 ;; elint-top-form-logged so elint-log-message doesn't print the
303 ;; ** top form ** header...
304 (elint-set-mode-line)
305 (elint-log-message "\nLinting finished.\n" t))
306
7f0fedda 307
7a8ae964 308;;;###autoload
020c3567 309(defun elint-defun ()
7a8ae964 310 "Lint the function at point.
fa5f7c5f 311If necessary, this first calls `elint-initialize'."
020c3567 312 (interactive)
7a8ae964
GM
313 (or elint-builtin-variables
314 (elint-initialize))
020c3567 315 (save-excursion
e2d5a67f 316 (or (beginning-of-defun) (error "Lint what?"))
020c3567
RS
317 (let ((pos (point))
318 (def (read (current-buffer))))
319 (elint-display-log)
020c3567
RS
320 (elint-update-env)
321 (elint-top-form (elint-make-top-form def pos)))))
322
323;;;
324;;; Top form functions
325;;;
326
327(defvar elint-buffer-env nil
fa5f7c5f 328 "The environment of an elisp buffer.
020c3567
RS
329Will be local in linted buffers.")
330
331(defvar elint-buffer-forms nil
332 "The top forms in a buffer.
333Will be local in linted buffers.")
334
335(defvar elint-last-env-time nil
336 "The last time the buffers env was updated.
337Is measured in buffer-modified-ticks and is local in linted buffers.")
338
76f2c576 339;; This is a minor optimization. It is local to every buffer, and so
40ba43b4 340;; does not prevent recursive requires. It does not list the requires
76f2c576
GM
341;; of requires.
342(defvar elint-features nil
343 "List of all libraries this buffer has required, or that have been provided.")
344
020c3567
RS
345(defun elint-update-env ()
346 "Update the elint environment in the current buffer.
347Don't do anything if the buffer hasn't been changed since this
348function was called the last time.
349Returns the forms."
350 (if (and (local-variable-p 'elint-buffer-env (current-buffer))
351 (local-variable-p 'elint-buffer-forms (current-buffer))
352 (local-variable-p 'elint-last-env-time (current-buffer))
353 (= (buffer-modified-tick) elint-last-env-time))
354 ;; Env is up to date
355 elint-buffer-forms
356 ;; Remake env
357 (set (make-local-variable 'elint-buffer-forms) (elint-get-top-forms))
76f2c576 358 (set (make-local-variable 'elint-features) nil)
020c3567
RS
359 (set (make-local-variable 'elint-buffer-env)
360 (elint-init-env elint-buffer-forms))
76f2c576 361 (if elint-preloaded-env
61b108cc
SM
362 ;; FIXME: This doesn't do anything! Should we setq the result to
363 ;; elint-buffer-env?
76f2c576 364 (elint-env-add-env elint-preloaded-env elint-buffer-env))
020c3567
RS
365 (set (make-local-variable 'elint-last-env-time) (buffer-modified-tick))
366 elint-buffer-forms))
7f0fedda 367
020c3567
RS
368(defun elint-get-top-forms ()
369 "Collect all the top forms in the current buffer."
370 (save-excursion
76f2c576 371 (let (tops)
020c3567
RS
372 (goto-char (point-min))
373 (while (elint-find-next-top-form)
76f2c576
GM
374 (let ((elint-current-pos (point)))
375 ;; non-list check could be here too. errors may be out of seq.
376 ;; quoted check cannot be elsewhere, since quotes skipped.
377 (if (looking-back "'")
378 ;; Eg cust-print.el uses ' as a comment syntax.
379 (elint-warning "Skipping quoted form `'%.20s...'"
380 (read (current-buffer)))
381 (condition-case nil
382 (setq tops (cons
383 (elint-make-top-form (read (current-buffer))
384 elint-current-pos)
385 tops))
386 (end-of-file
387 (goto-char elint-current-pos)
388 (error "Missing ')' in top form: %s"
389 (buffer-substring elint-current-pos
390 (line-end-position))))))))
020c3567
RS
391 (nreverse tops))))
392
393(defun elint-find-next-top-form ()
394 "Find the next top form from point.
f0529b5b 395Return nil if there are no more forms, t otherwise."
020c3567
RS
396 (parse-partial-sexp (point) (point-max) nil t)
397 (not (eobp)))
398
79d1dabe 399(defvar elint-env) ; from elint-init-env
76f2c576
GM
400
401(defun elint-init-form (form)
79d1dabe 402 "Process FORM, adding to ELINT-ENV if recognized."
76f2c576
GM
403 (cond
404 ;; Eg nnmaildir seems to use [] as a form of comment syntax.
405 ((not (listp form))
406 (elint-warning "Skipping non-list form `%s'" form))
407 ;; Add defined variable
408 ((memq (car form) '(defvar defconst defcustom))
79d1dabe 409 (setq elint-env (elint-env-add-var elint-env (cadr form))))
76f2c576
GM
410 ;; Add function
411 ((memq (car form) '(defun defsubst))
79d1dabe 412 (setq elint-env (elint-env-add-func elint-env (cadr form) (nth 2 form))))
76f2c576
GM
413 ;; FIXME needs a handler to say second arg is not a variable when we come
414 ;; to scan the form.
415 ((eq (car form) 'define-derived-mode)
79d1dabe
GM
416 (setq elint-env (elint-env-add-func elint-env (cadr form) ())
417 elint-env (elint-env-add-var elint-env (cadr form))
418 elint-env (elint-env-add-var elint-env
419 (intern (format "%s-map" (cadr form))))))
76f2c576 420 ((eq (car form) 'define-minor-mode)
79d1dabe 421 (setq elint-env (elint-env-add-func elint-env (cadr form) '(&optional arg))
76f2c576 422 ;; FIXME mode map?
79d1dabe 423 elint-env (elint-env-add-var elint-env (cadr form))))
76f2c576
GM
424 ((and (eq (car form) 'easy-menu-define)
425 (cadr form))
79d1dabe
GM
426 (setq elint-env (elint-env-add-func elint-env (cadr form) '(event))
427 elint-env (elint-env-add-var elint-env (cadr form))))
76f2c576
GM
428 ;; FIXME it would be nice to check the autoloads are correct.
429 ((eq (car form) 'autoload)
79d1dabe 430 (setq elint-env (elint-env-add-func elint-env (cadr (cadr form)) 'unknown)))
76f2c576 431 ((eq (car form) 'declare-function)
79d1dabe
GM
432 (setq elint-env (elint-env-add-func
433 elint-env (cadr form)
494f4fc7
GM
434 (if (or (< (length form) 4)
435 (eq (nth 3 form) t)
436 (unless (stringp (nth 2 form))
437 (elint-error "Malformed declaration for `%s'"
438 (cadr form))
439 t))
440 'unknown
441 (nth 3 form)))))
76f2c576
GM
442 ((and (eq (car form) 'defalias) (listp (nth 2 form)))
443 ;; If the alias points to something already in the environment,
444 ;; add the alias to the environment with the same arguments.
445 ;; FIXME symbol-function, eg backquote.el?
79d1dabe
GM
446 (let ((def (elint-env-find-func elint-env (cadr (nth 2 form)))))
447 (setq elint-env (elint-env-add-func elint-env (cadr (cadr form))
76f2c576
GM
448 (if def (cadr def) 'unknown)))))
449 ;; Add macro, both as a macro and as a function
450 ((eq (car form) 'defmacro)
79d1dabe 451 (setq elint-env (elint-env-add-macro elint-env (cadr form)
76f2c576 452 (cons 'lambda (cddr form)))
79d1dabe 453 elint-env (elint-env-add-func elint-env (cadr form) (nth 2 form))))
76f2c576
GM
454 ((and (eq (car form) 'put)
455 (= 4 (length form))
456 (eq (car-safe (cadr form)) 'quote)
457 (equal (nth 2 form) '(quote error-conditions)))
458 (set (make-local-variable 'elint-extra-errors)
459 (cons (cadr (cadr form)) elint-extra-errors)))
460 ((eq (car form) 'provide)
461 (add-to-list 'elint-features (eval (cadr form))))
462 ;; Import variable definitions
463 ((memq (car form) '(require cc-require cc-require-when-compile))
464 (let ((name (eval (cadr form)))
465 (file (eval (nth 2 form)))
466 (elint-doing-cl (bound-and-true-p elint-doing-cl)))
467 (unless (memq name elint-features)
468 (add-to-list 'elint-features name)
469 ;; cl loads cl-macs in an opaque manner.
470 ;; Since cl-macs requires cl, we can just process cl-macs.
a464a6c7
SM
471 ;; FIXME: AFAIK, `cl' now behaves properly and does not need any
472 ;; special treatment any more. Can someone who understands this
473 ;; code confirm? --Stef
76f2c576
GM
474 (and (eq name 'cl) (not elint-doing-cl)
475 ;; We need cl if elint-form is to be able to expand cl macros.
476 (require 'cl)
477 (setq name 'cl-macs
478 file nil
479 elint-doing-cl t)) ; blech
79d1dabe
GM
480 (setq elint-env (elint-add-required-env elint-env name file))))))
481 elint-env)
76f2c576 482
020c3567 483(defun elint-init-env (forms)
1936c9a9 484 "Initialize the environment from FORMS."
79d1dabe 485 (let ((elint-env (elint-make-env))
020c3567
RS
486 form)
487 (while forms
488 (setq form (elint-top-form-form (car forms))
489 forms (cdr forms))
76f2c576
GM
490 ;; FIXME eval-when-compile should be treated differently (macros).
491 ;; Could bind something that makes elint-init-form only check
492 ;; defmacros.
493 (if (memq (car-safe form)
494 '(eval-and-compile eval-when-compile progn prog1 prog2
495 with-no-warnings))
496 (mapc 'elint-init-form (cdr form))
497 (elint-init-form form)))
79d1dabe 498 elint-env))
020c3567
RS
499
500(defun elint-add-required-env (env name file)
e2d5a67f 501 "Augment ENV with the variables defined by feature NAME in FILE."
4cf8971b 502 (condition-case err
020c3567
RS
503 (let* ((libname (if (stringp file)
504 file
505 (symbol-name name)))
506
507 ;; First try to find .el files, then the raw name
508 (lib1 (locate-library (concat libname ".el") t))
e2d5a67f 509 (lib (or lib1 (locate-library libname t))))
020c3567 510 ;; Clear the messages :-/
76f2c576
GM
511 ;; (Messes up the "Initializing elint..." message.)
512;;; (message nil)
020c3567 513 (if lib
de3a1fe9 514 (with-current-buffer (find-file-noselect lib)
d62e5bf2
GM
515 ;; FIXME this doesn't use a temp buffer, because it
516 ;; stores the result in buffer-local variables so that
517 ;; it can be reused.
76f2c576
GM
518 (elint-update-env)
519 (setq env (elint-env-add-env env elint-buffer-env)))
d62e5bf2
GM
520 ;;; (with-temp-buffer
521 ;;; (insert-file-contents lib)
522 ;;; (with-syntax-table emacs-lisp-mode-syntax-table
523 ;;; (elint-update-env))
524 ;;; (setq env (elint-env-add-env env elint-buffer-env))))
6974be68 525 ;;(message "Elint processed (require '%s)" name))
4cf8971b 526 (error "%s.el not found in load-path" libname)))
020c3567 527 (error
4cf8971b
KR
528 (message "Can't get variables from require'd library %s: %s"
529 name (error-message-string err))))
020c3567 530 env)
7f0fedda 531
020c3567
RS
532(defvar elint-top-form nil
533 "The currently linted top form, or nil.")
534
535(defvar elint-top-form-logged nil
fa5f7c5f 536 "The value t if the currently linted top form has been mentioned in the log buffer.")
020c3567
RS
537
538(defun elint-top-form (form)
539 "Lint a top FORM."
540 (let ((elint-top-form form)
e2d5a67f
GM
541 (elint-top-form-logged nil)
542 (elint-current-pos (elint-top-form-pos form)))
020c3567
RS
543 (elint-form (elint-top-form-form form) elint-buffer-env)))
544
545;;;
546;;; General form linting functions
547;;;
548
549(defconst elint-special-forms
550 '((let . elint-check-let-form)
551 (let* . elint-check-let-form)
552 (setq . elint-check-setq-form)
553 (quote . elint-check-quote-form)
76f2c576 554 (function . elint-check-quote-form)
020c3567
RS
555 (cond . elint-check-cond-form)
556 (lambda . elint-check-defun-form)
557 (function . elint-check-function-form)
558 (setq-default . elint-check-setq-form)
76f2c576 559 (defalias . elint-check-defalias-form)
020c3567
RS
560 (defun . elint-check-defun-form)
561 (defsubst . elint-check-defun-form)
562 (defmacro . elint-check-defun-form)
563 (defvar . elint-check-defvar-form)
564 (defconst . elint-check-defvar-form)
7f0fedda 565 (defcustom . elint-check-defcustom-form)
020c3567 566 (macro . elint-check-macro-form)
76f2c576
GM
567 (condition-case . elint-check-condition-case-form)
568 (if . elint-check-conditional-form)
569 (when . elint-check-conditional-form)
570 (unless . elint-check-conditional-form)
571 (and . elint-check-conditional-form)
572 (or . elint-check-conditional-form))
020c3567 573 "Functions to call when some special form should be linted.")
7f0fedda 574
76f2c576 575(defun elint-form (form env &optional nohandler)
020c3567 576 "Lint FORM in the environment ENV.
76f2c576
GM
577Optional argument NOHANDLER non-nil means ignore `elint-special-forms'.
578Returns the environment created by the form."
020c3567
RS
579 (cond
580 ((consp form)
581 (let ((func (cdr (assq (car form) elint-special-forms))))
76f2c576 582 (if (and func (not nohandler))
020c3567
RS
583 ;; Special form
584 (funcall func form env)
585
586 (let* ((func (car form))
587 (args (elint-get-args func env))
588 (argsok t))
589 (cond
590 ((eq args 'undefined)
591 (setq argsok nil)
76f2c576
GM
592 (or (memq 'undefined-functions elint-ignored-warnings)
593 (elint-error "Call to undefined function: %s" func)))
7f0fedda 594
020c3567 595 ((eq args 'unknown) nil)
7f0fedda 596
020c3567
RS
597 (t (setq argsok (elint-match-args form args))))
598
599 ;; Is this a macro?
600 (if (elint-env-macrop env func)
601 ;; Macro defined in buffer, expand it
602 (if argsok
e2d5a67f
GM
603 ;; FIXME error if macro uses macro, eg bytecomp.el.
604 (condition-case nil
605 (elint-form
606 (macroexpand form (elint-env-macro-env env)) env)
607 (error
76f2c576
GM
608 (or (memq 'macro-expansion elint-ignored-warnings)
609 (elint-error "Elint failed to expand macro: %s" func))
6974be68 610 env))
020c3567
RS
611 env)
612
613 (let ((fcode (if (symbolp func)
614 (if (fboundp func)
e2d5a67f 615 (indirect-function func))
020c3567
RS
616 func)))
617 (if (and (listp fcode) (eq (car fcode) 'macro))
618 ;; Macro defined outside buffer
619 (if argsok
620 (elint-form (macroexpand form) env)
621 env)
622 ;; Function, lint its parameters
e2d5a67f 623 (elint-forms (cdr form) env))))))))
020c3567
RS
624 ((symbolp form)
625 ;; :foo variables are quoted
76f2c576
GM
626 (and (/= (aref (symbol-name form) 0) ?:)
627 (not (memq 'unbound-reference elint-ignored-warnings))
628 (elint-unbound-variable form env)
629 (elint-warning "Reference to unbound symbol: %s" form))
020c3567
RS
630 env)
631
e2d5a67f 632 (t env)))
020c3567
RS
633
634(defun elint-forms (forms env)
635 "Lint the FORMS, accumulating an environment, starting with ENV."
636 ;; grumblegrumbletailrecursiongrumblegrumble
6974be68
GM
637 (if (listp forms)
638 (dolist (f forms env)
639 (setq env (elint-form f env)))
640 ;; Loop macro?
bf01513f
GM
641 (elint-error "Elint failed to parse form: %s" forms)
642 env))
020c3567 643
76f2c576
GM
644(defvar elint-bound-variable nil
645 "Name of a temporarily bound symbol.")
646
020c3567 647(defun elint-unbound-variable (var env)
fa5f7c5f 648 "Return t if VAR is unbound in ENV."
a61344d6
GM
649 ;; #1063 suggests adding (symbol-file var) here, but I don't think
650 ;; this is right, because it depends on what files you happen to have
651 ;; loaded at the time, which might not be the same when the code runs.
652 ;; It also suggests adding:
653 ;; (numberp (get var 'variable-documentation))
654 ;; (numberp (cdr-safe (get var 'variable-documentation)))
655 ;; but this is not needed now elint-scan-doc-file exists.
e2d5a67f 656 (not (or (memq var '(nil t))
76f2c576 657 (eq var elint-bound-variable)
020c3567 658 (elint-env-find-var env var)
e2d5a67f
GM
659 (memq var elint-builtin-variables)
660 (memq var elint-autoloaded-variables)
020c3567
RS
661 (memq var elint-standard-variables))))
662
663;;;
664;;; Function argument checking
665;;;
666
667(defun elint-match-args (arglist argpattern)
668 "Match ARGLIST against ARGPATTERN."
020c3567
RS
669 (let ((state 'all)
670 (al (cdr arglist))
671 (ap argpattern)
672 (ok t))
673 (while
674 (cond
675 ((and (null al) (null ap)) nil)
676 ((eq (car ap) '&optional)
677 (setq state 'optional)
678 (setq ap (cdr ap))
679 t)
680 ((eq (car ap) '&rest)
681 nil)
682 ((or (and (eq state 'all) (or (null al) (null ap)))
683 (and (eq state 'optional) (and al (null ap))))
684 (elint-error "Wrong number of args: %s, %s" arglist argpattern)
685 (setq ok nil)
686 nil)
687 ((and (eq state 'optional) (null al))
688 nil)
689 (t (setq al (cdr al)
690 ap (cdr ap))
691 t)))
692 ok))
693
76f2c576
GM
694(defvar elint-bound-function nil
695 "Name of a temporarily bound function symbol.")
696
020c3567
RS
697(defun elint-get-args (func env)
698 "Find the args of FUNC in ENV.
699Returns `unknown' if we couldn't find arguments."
700 (let ((f (elint-env-find-func env func)))
701 (if f
e2d5a67f 702 (cadr f)
020c3567 703 (if (symbolp func)
76f2c576
GM
704 (if (eq func elint-bound-function)
705 'unknown
706 (if (fboundp func)
707 (let ((fcode (indirect-function func)))
708 (if (subrp fcode)
709 ;; FIXME builtins with no args have args = nil.
710 (or (get func 'elint-args) 'unknown)
711 (elint-find-args-in-code fcode)))
712 'undefined))
020c3567
RS
713 (elint-find-args-in-code func)))))
714
715(defun elint-find-args-in-code (code)
716 "Extract the arguments from CODE.
717CODE can be a lambda expression, a macro, or byte-compiled code."
ef501ef0
SM
718 (let ((args (help-function-arglist code)))
719 (if (listp args) args 'unknown)))
020c3567
RS
720
721;;;
722;;; Functions to check some special forms
723;;;
724
725(defun elint-check-cond-form (form env)
726 "Lint a cond FORM in ENV."
76f2c576 727 (dolist (f (cdr form))
e2d5a67f 728 (if (consp f)
76f2c576
GM
729 (let ((test (car f)))
730 (cond ((equal test '(featurep (quote xemacs))))
731 ((equal test '(not (featurep (quote emacs)))))
732 ;; FIXME (and (boundp 'foo)
733 ((and (eq (car-safe test) 'fboundp)
734 (= 2 (length test))
735 (eq (car-safe (cadr test)) 'quote))
736 (let ((elint-bound-function (cadr (cadr test))))
737 (elint-forms f env)))
738 ((and (eq (car-safe test) 'boundp)
739 (= 2 (length test))
740 (eq (car-safe (cadr test)) 'quote))
741 (let ((elint-bound-variable (cadr (cadr test))))
742 (elint-forms f env)))
743 (t (elint-forms f env))))
744 (elint-error "cond clause should be a list: %s" f)))
745 env)
020c3567
RS
746
747(defun elint-check-defun-form (form env)
748 "Lint a defun/defmacro/lambda FORM in ENV."
e2d5a67f
GM
749 (setq form (if (eq (car form) 'lambda) (cdr form) (cddr form)))
750 (mapc (lambda (p)
751 (or (memq p '(&optional &rest))
752 (setq env (elint-env-add-var env p))))
eb2b0009 753 (car form))
020c3567
RS
754 (elint-forms (cdr form) env))
755
76f2c576
GM
756(defun elint-check-defalias-form (form env)
757 "Lint a defalias FORM in ENV."
758 (let ((alias (cadr form))
759 (target (nth 2 form)))
760 (and (eq (car-safe alias) 'quote)
761 (eq (car-safe target) 'quote)
762 (eq (elint-get-args (cadr target) env) 'undefined)
763 (elint-warning "Alias `%s' has unknown target `%s'"
764 (cadr alias) (cadr target))))
765 (elint-form form env t))
766
020c3567
RS
767(defun elint-check-let-form (form env)
768 "Lint the let/let* FORM in ENV."
e2d5a67f 769 (let ((varlist (cadr form)))
020c3567 770 (if (not varlist)
76f2c576
GM
771 (if (> (length form) 2)
772 ;; An empty varlist is not really an error. Eg some cl macros
773 ;; can expand to such a form.
774 (progn
775 (or (memq 'empty-let elint-ignored-warnings)
776 (elint-warning "Empty varlist in let: %s" form))
777 ;; Lint the body forms
778 (elint-forms (cddr form) env))
779 (elint-error "Malformed let: %s" form)
020c3567 780 env)
020c3567
RS
781 ;; Check for (let (a (car b)) ...) type of error
782 (if (and (= (length varlist) 2)
783 (symbolp (car varlist))
784 (listp (car (cdr varlist)))
785 (fboundp (car (car (cdr varlist)))))
786 (elint-warning "Suspect varlist: %s" form))
020c3567
RS
787 ;; Add variables to environment, and check the init values
788 (let ((newenv env))
e2d5a67f
GM
789 (mapc (lambda (s)
790 (cond
791 ((symbolp s)
792 (setq newenv (elint-env-add-var newenv s)))
793 ((and (consp s) (<= (length s) 2))
794 (elint-form (cadr s)
795 (if (eq (car form) 'let)
796 env
797 newenv))
798 (setq newenv
799 (elint-env-add-var newenv (car s))))
800 (t (elint-error
801 "Malformed `let' declaration: %s" s))))
eb2b0009 802 varlist)
020c3567
RS
803
804 ;; Lint the body forms
e2d5a67f 805 (elint-forms (cddr form) newenv)))))
020c3567
RS
806
807(defun elint-check-setq-form (form env)
808 "Lint the setq FORM in ENV."
809 (or (= (mod (length form) 2) 1)
76f2c576
GM
810 ;; (setq foo) is valid and equivalent to (setq foo nil).
811 (elint-warning "Missing value in setq: %s" form))
020c3567
RS
812 (let ((newenv env)
813 sym val)
814 (setq form (cdr form))
815 (while form
816 (setq sym (car form)
817 val (car (cdr form))
818 form (cdr (cdr form)))
819 (if (symbolp sym)
76f2c576
GM
820 (and (not (memq 'unbound-assignment elint-ignored-warnings))
821 (elint-unbound-variable sym newenv)
822 (elint-warning "Setting previously unbound symbol: %s" sym))
020c3567
RS
823 (elint-error "Setting non-symbol in setq: %s" sym))
824 (elint-form val newenv)
825 (if (symbolp sym)
826 (setq newenv (elint-env-add-var newenv sym))))
827 newenv))
7f0fedda 828
020c3567
RS
829(defun elint-check-defvar-form (form env)
830 "Lint the defvar/defconst FORM in ENV."
831 (if (or (= (length form) 2)
832 (= (length form) 3)
76f2c576
GM
833 ;; Eg the defcalcmodevar macro can expand with a nil doc-string.
834 (and (= (length form) 4) (string-or-null-p (nth 3 form))))
020c3567
RS
835 (elint-env-add-global-var (elint-form (nth 2 form) env)
836 (car (cdr form)))
837 (elint-error "Malformed variable declaration: %s" form)
838 env))
7f0fedda
KH
839
840(defun elint-check-defcustom-form (form env)
841 "Lint the defcustom FORM in ENV."
842 (if (and (> (length form) 3)
748fb1aa
JPW
843 ;; even no. of keyword/value args ?
844 (zerop (logand (length form) 1)))
7f0fedda
KH
845 (elint-env-add-global-var (elint-form (nth 2 form) env)
846 (car (cdr form)))
847 (elint-error "Malformed variable declaration: %s" form)
848 env))
849
020c3567
RS
850(defun elint-check-function-form (form env)
851 "Lint the function FORM in ENV."
852 (let ((func (car (cdr-safe form))))
853 (cond
854 ((symbolp func)
855 (or (elint-env-find-func env func)
76f2c576
GM
856 ;; FIXME potentially bogus, since it uses the current
857 ;; environment rather than a clean one.
020c3567
RS
858 (fboundp func)
859 (elint-warning "Reference to undefined function: %s" form))
860 env)
861 ((and (consp func) (memq (car func) '(lambda macro)))
862 (elint-form func env))
863 ((stringp func) env)
864 (t (elint-error "Not a function object: %s" form)
e2d5a67f 865 env))))
020c3567
RS
866
867(defun elint-check-quote-form (form env)
868 "Lint the quote FORM in ENV."
869 env)
870
871(defun elint-check-macro-form (form env)
872 "Check the macro FORM in ENV."
873 (elint-check-function-form (list (car form) (cdr form)) env))
874
875(defun elint-check-condition-case-form (form env)
e2d5a67f 876 "Check the `condition-case' FORM in ENV."
020c3567
RS
877 (let ((resenv env))
878 (if (< (length form) 3)
879 (elint-error "Malformed condition-case: %s" form)
e2d5a67f 880 (or (symbolp (cadr form))
020c3567
RS
881 (elint-warning "First parameter should be a symbol: %s" form))
882 (setq resenv (elint-form (nth 2 form) env))
e2d5a67f 883 (let ((newenv (elint-env-add-var env (cadr form)))
020c3567 884 errlist)
e2d5a67f
GM
885 (dolist (err (nthcdr 3 form))
886 (setq errlist (car err))
887 (mapc (lambda (s)
888 (or (get s 'error-conditions)
889 (get s 'error-message)
890 (memq s elint-extra-errors)
891 (elint-warning
892 "Not an error symbol in error handler: %s" s)))
eb2b0009 893 (cond
e2d5a67f
GM
894 ((symbolp errlist) (list errlist))
895 ((listp errlist) errlist)
896 (t (elint-error "Bad error list in error handler: %s"
897 errlist)
898 nil)))
899 (elint-forms (cdr err) newenv))))
020c3567 900 resenv))
7f0fedda 901
76f2c576
GM
902;; For the featurep parts, an alternative is to have
903;; elint-get-top-forms skip the irrelevant branches.
904(defun elint-check-conditional-form (form env)
905 "Check the when/unless/and/or FORM in ENV.
906Does basic handling of `featurep' tests."
907 (let ((func (car form))
908 (test (cadr form))
909 sym)
910 ;; Misses things like (and t (featurep 'xemacs))
911 ;; Check byte-compile-maybe-guarded.
912 (cond ((and (memq func '(when and))
913 (eq (car-safe test) 'boundp)
914 (= 2 (length test))
915 (eq (car-safe (cadr test)) 'quote))
916 ;; Cf elint-check-let-form, which modifies the whole ENV.
917 (let ((elint-bound-variable (cadr (cadr test))))
918 (elint-form form env t)))
919 ((and (memq func '(when and))
920 (eq (car-safe test) 'fboundp)
921 (= 2 (length test))
922 (eq (car-safe (cadr test)) 'quote))
923 (let ((elint-bound-function (cadr (cadr test))))
924 (elint-form form env t)))
925 ;; Let's not worry about (if (not (boundp...
926 ((and (eq func 'if)
927 (eq (car-safe test) 'boundp)
928 (= 2 (length test))
929 (eq (car-safe (cadr test)) 'quote))
930 (let ((elint-bound-variable (cadr (cadr test))))
931 (elint-form (nth 2 form) env))
932 (dolist (f (nthcdr 3 form))
933 (elint-form f env)))
934 ((and (eq func 'if)
935 (eq (car-safe test) 'fboundp)
936 (= 2 (length test))
937 (eq (car-safe (cadr test)) 'quote))
938 (let ((elint-bound-function (cadr (cadr test))))
939 (elint-form (nth 2 form) env))
940 (dolist (f (nthcdr 3 form))
941 (elint-form f env)))
942 ((and (memq func '(when and)) ; skip all
943 (or (null test)
944 (member test '((featurep (quote xemacs))
945 (not (featurep (quote emacs)))))
946 (and (eq (car-safe test) 'and)
947 (equal (car-safe (cdr test))
948 '(featurep (quote xemacs)))))))
949 ((and (memq func '(unless or))
950 (equal test '(featurep (quote emacs)))))
951 ((and (eq func 'if)
952 (or (null test) ; eg custom-browse-insert-prefix
953 (member test '((featurep (quote xemacs))
954 (not (featurep (quote emacs)))))
955 (and (eq (car-safe test) 'and)
956 (equal (car-safe (cdr test))
957 '(featurep (quote xemacs))))))
958 (dolist (f (nthcdr 3 form))
959 (elint-form f env))) ; lint the else branch
960 ((and (eq func 'if)
961 (equal test '(featurep (quote emacs))))
962 (elint-form (nth 2 form) env)) ; lint the if branch
963 ;; Process conditional as normal, without handler.
964 (t
965 (elint-form form env t))))
966 env)
967
020c3567
RS
968;;;
969;;; Message functions
970;;;
971
e2d5a67f
GM
972(defvar elint-current-pos) ; dynamically bound in elint-top-form
973
974(defun elint-log (type string args)
975 (elint-log-message (format "%s:%d:%s: %s"
7a8ae964
GM
976 (let ((f (buffer-file-name)))
977 (if f
978 (file-name-nondirectory f)
979 (buffer-name)))
afdceaec
GM
980 (if (boundp 'elint-current-pos)
981 (save-excursion
982 (goto-char elint-current-pos)
983 (1+ (count-lines (point-min)
984 (line-beginning-position))))
985 0) ; unknown position
e2d5a67f
GM
986 type
987 (apply 'format string args))))
020c3567
RS
988
989(defun elint-error (string &rest args)
a62396cc 990 "Report a linting error.
020c3567 991STRING and ARGS are thrown on `format' to get the message."
e2d5a67f 992 (elint-log "Error" string args))
7f0fedda 993
020c3567 994(defun elint-warning (string &rest args)
a62396cc 995 "Report a linting warning.
e2d5a67f
GM
996See `elint-error'."
997 (elint-log "Warning" string args))
020c3567 998
6974be68
GM
999(defun elint-output (string)
1000 "Print or insert STRING, depending on value of `noninteractive'."
1001 (if noninteractive
1002 (message "%s" string)
1003 (insert string "\n")))
1004
1005(defun elint-log-message (errstr &optional top)
1006 "Insert ERRSTR last in the lint log buffer.
1007Optional argument TOP non-nil means pretend `elint-top-form-logged' is non-nil."
e2d5a67f 1008 (with-current-buffer (elint-get-log-buffer)
020c3567 1009 (goto-char (point-max))
e2d5a67f
GM
1010 (let ((inhibit-read-only t))
1011 (or (bolp) (newline))
1012 ;; Do we have to say where we are?
6974be68
GM
1013 (unless (or elint-top-form-logged top)
1014 (let* ((form (elint-top-form-form elint-top-form))
1015 (top (car form)))
1016 (elint-output (cond
1017 ((memq top '(defun defsubst))
1018 (format "\nIn function %s:" (cadr form)))
1019 ((eq top 'defmacro)
1020 (format "\nIn macro %s:" (cadr form)))
1021 ((memq top '(defvar defconst))
1022 (format "\nIn variable %s:" (cadr form)))
1023 (t "\nIn top level expression:"))))
e2d5a67f 1024 (setq elint-top-form-logged t))
6974be68 1025 (elint-output errstr))))
020c3567
RS
1026
1027(defun elint-clear-log (&optional header)
1028 "Clear the lint log buffer.
1029Insert HEADER followed by a blank line if non-nil."
e2d5a67f
GM
1030 (let ((dir default-directory))
1031 (with-current-buffer (elint-get-log-buffer)
1032 (setq default-directory dir)
1033 (let ((inhibit-read-only t))
1034 (erase-buffer)
1035 (if header (insert header "\n"))))))
020c3567
RS
1036
1037(defun elint-display-log ()
1038 "Display the lint log buffer."
1039 (let ((pop-up-windows t))
1040 (display-buffer (elint-get-log-buffer))
1041 (sit-for 0)))
1042
6974be68
GM
1043(defvar elint-running)
1044
1045(defun elint-set-mode-line (&optional on)
1046 "Set the mode-line-process of the Elint log buffer."
1047 (with-current-buffer (elint-get-log-buffer)
1048 (and (eq major-mode 'compilation-mode)
1049 (setq mode-line-process
1050 (list (if (or on (bound-and-true-p elint-running))
1051 (propertize ":run" 'face 'compilation-warning)
1052 (propertize ":finished" 'face 'compilation-info)))))))
1053
020c3567
RS
1054(defun elint-get-log-buffer ()
1055 "Return a log buffer for elint."
e2d5a67f
GM
1056 (or (get-buffer elint-log-buffer)
1057 (with-current-buffer (get-buffer-create elint-log-buffer)
1058 (or (eq major-mode 'compilation-mode)
1059 (compilation-mode))
1060 (setq buffer-undo-list t)
1061 (current-buffer))))
7f0fedda 1062
020c3567
RS
1063;;;
1064;;; Initializing code
1065;;;
7f0fedda 1066
76f2c576
GM
1067(defun elint-put-function-args (func args)
1068 "Mark function FUNC as having argument list ARGS."
1069 (and (symbolp func)
1070 args
1071 (not (eq args 'unknown))
1072 (put func 'elint-args args)))
1073
020c3567 1074;;;###autoload
7a8ae964
GM
1075(defun elint-initialize (&optional reinit)
1076 "Initialize elint.
1077If elint is already initialized, this does nothing, unless
1078optional prefix argument REINIT is non-nil."
1079 (interactive "P")
1080 (if (and elint-builtin-variables (not reinit))
1081 (message "Elint is already initialized")
1082 (message "Initializing elint...")
76f2c576 1083 (setq elint-builtin-variables (elint-scan-doc-file)
7a8ae964 1084 elint-autoloaded-variables (elint-find-autoloaded-variables))
76f2c576 1085 (mapc (lambda (x) (elint-put-function-args (car x) (cdr x)))
7a8ae964
GM
1086 (elint-find-builtin-args))
1087 (if elint-unknown-builtin-args
76f2c576 1088 (mapc (lambda (x) (elint-put-function-args (car x) (cdr x)))
7a8ae964 1089 elint-unknown-builtin-args))
76f2c576
GM
1090 (when elint-scan-preloaded
1091 (dolist (lib preloaded-file-list)
1092 ;; Skip files that contain nothing of use to us.
1093 (unless (string-match elint-preloaded-skip-re lib)
1094 (setq elint-preloaded-env
1095 (elint-add-required-env elint-preloaded-env nil lib)))))
7a8ae964 1096 (message "Initializing elint...done")))
020c3567
RS
1097
1098
76f2c576
GM
1099;; This includes all the built-in and dumped things with documentation.
1100(defun elint-scan-doc-file ()
1101 "Scan the DOC file for function and variables.
ee7683eb 1102Marks the function with their arguments, and returns a list of variables."
e2d5a67f
GM
1103 ;; Cribbed from help-fns.el.
1104 (let ((docbuf " *DOC*")
76f2c576 1105 vars sym args)
7a8ae964
GM
1106 (save-excursion
1107 (if (get-buffer docbuf)
1108 (progn
1109 (set-buffer docbuf)
1110 (goto-char (point-min)))
1111 (set-buffer (get-buffer-create docbuf))
1112 (insert-file-contents-literally
1113 (expand-file-name internal-doc-file-name doc-directory)))
76f2c576
GM
1114 (while (re-search-forward "\1f\\([VF]\\)" nil t)
1115 (when (setq sym (intern-soft (buffer-substring (point)
1116 (line-end-position))))
1117 (if (string-equal (match-string 1) "V")
1118 ;; Excludes platform-specific stuff not relevant to the
1119 ;; running platform.
1120 (if (boundp sym) (setq vars (cons sym vars)))
1121 ;; Function.
1122 (when (fboundp sym)
1123 (when (re-search-forward "\\(^(fn.*)\\)?\1f" nil t)
1124 (backward-char 1)
1125 ;; FIXME distinguish no args from not found.
1126 (and (setq args (match-string 1))
1127 (setq args
1128 (ignore-errors
1129 (read
1130 (replace-regexp-in-string "^(fn ?" "(" args))))
1131 (elint-put-function-args sym args))))))))
1132 vars))
e2d5a67f
GM
1133
1134(defun elint-find-autoloaded-variables ()
1135 "Return a list of all autoloaded variables."
1136 (let (var vars)
1137 (with-temp-buffer
1138 (insert-file-contents (locate-library "loaddefs.el"))
1139 (while (re-search-forward "^(defvar \\([[:alnum:]_-]+\\)" nil t)
1140 (and (setq var (intern-soft (match-string 1)))
1141 (boundp var)
1142 (setq vars (cons var vars)))))
1143 vars))
1144
020c3567 1145(defun elint-find-builtins ()
e2d5a67f
GM
1146 "Return a list of all built-in functions."
1147 (let (subrs)
6bdd9204
SM
1148 (mapatoms (lambda (s) (and (subrp (symbol-function s))
1149 (push s subrs))))
e2d5a67f 1150 subrs))
020c3567
RS
1151
1152(defun elint-find-builtin-args (&optional list)
e2d5a67f 1153 "Return a list of the built-in functions and their arguments.
020c3567
RS
1154If LIST is nil, call `elint-find-builtins' to get a list of all built-in
1155functions, otherwise use LIST.
1156
e2d5a67f 1157Each function is represented by a cons cell:
020c3567
RS
1158\(function-symbol . args)
1159If no documentation could be found args will be `unknown'."
92bd667f
GM
1160 (mapcar (lambda (f)
1161 (let ((doc (documentation f t)))
1162 (or (and doc
1163 (string-match "\n\n(fn\\(.*)\\)\\'" doc)
1164 (ignore-errors
e2d5a67f
GM
1165 ;; "BODY...)" -> "&rest BODY)".
1166 (read (replace-regexp-in-string
1167 "\\([^ ]+\\)\\.\\.\\.)\\'" "&rest \\1)"
1168 (format "(%s %s" f (match-string 1 doc)) t))))
92bd667f
GM
1169 (cons f 'unknown))))
1170 (or list (elint-find-builtins))))
020c3567 1171
020c3567
RS
1172(provide 'elint)
1173
1174;;; elint.el ends here