diff options
| author | Glenn Morris | 2009-09-22 02:28:28 +0000 |
|---|---|---|
| committer | Glenn Morris | 2009-09-22 02:28:28 +0000 |
| commit | 76f2c576919a3794e91ca9f6150d3b4cbbffb384 (patch) | |
| tree | 930603a2072b7d56966d0a1daf63654b3cde27ca | |
| parent | b578c9cce418bf6e9a46c3e0118b4e30a769ffa3 (diff) | |
| download | emacs-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.el | 501 |
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. | ||
| 63 | Otherwise, just scan the DOC file for functions and variables. | ||
| 64 | This is faster, but less accurate, since it misses undocumented features. | ||
| 65 | This 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. | ||
| 73 | This is useful if Elint has trouble understanding your code and | ||
| 74 | you need to suppress lots of spurious warnings. The valid list elements | ||
| 75 | are 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'. |
| 127 | This is actually all those documented in the DOC file, which includes | ||
| 128 | built-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. | ||
| 135 | Set 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. | ||
| 151 | We 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. |
| 270 | A 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. |
| 250 | Is measured in buffer-modified-ticks and is local in linted buffers.") | 331 | Is 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. |
| 254 | Don't do anything if the buffer hasn't been changed since this | 341 | Don'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. |
| 410 | The environment created by the form is returned." | 560 | Optional argument NOHANDLER non-nil means ignore `elint-special-forms'. |
| 561 | Returns 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. |
| 514 | Returns `unknown' if we couldn't find arguments." | 675 | Returns `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. | ||
| 888 | Does 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. | ||
| 1084 | Marks 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." |