From e1fd63db70ba86595675d1d5c2f127d3728147f8 Mon Sep 17 00:00:00 2001 From: Eshel Yaron Date: Wed, 18 Jun 2025 09:57:27 +0200 Subject: [PATCH] Drop elint.el --- lisp/emacs-lisp/elint.el | 1168 ---------------------------------- lisp/progmodes/elisp-mode.el | 9 - 2 files changed, 1177 deletions(-) delete mode 100644 lisp/emacs-lisp/elint.el diff --git a/lisp/emacs-lisp/elint.el b/lisp/emacs-lisp/elint.el deleted file mode 100644 index 5ae8880167d..00000000000 --- a/lisp/emacs-lisp/elint.el +++ /dev/null @@ -1,1168 +0,0 @@ -;;; elint.el --- Lint Emacs Lisp -*- lexical-binding: t -*- - -;; Copyright (C) 1997-2025 Free Software Foundation, Inc. - -;; Author: Peter Liljenberg -;; Maintainer: emacs-devel@gnu.org -;; Created: May 1997 -;; Keywords: lisp - -;; This file is part of GNU Emacs. - -;; GNU Emacs is free software: you can redistribute it and/or modify -;; it under the terms of the GNU General Public License as published by -;; the Free Software Foundation, either version 3 of the License, or -;; (at your option) any later version. - -;; GNU Emacs is distributed in the hope that it will be useful, -;; but WITHOUT ANY WARRANTY; without even the implied warranty of -;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -;; GNU General Public License for more details. - -;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs. If not, see . - -;;; Commentary: - -;; This is a linter for Emacs Lisp. Currently, it mainly catches -;; misspellings and undefined variables, although it can also catch -;; function calls with the wrong number of arguments. - -;; To use it, call `elint-current-buffer' or `elint-defun' to lint a buffer -;; or defun. The first call runs `elint-initialize' to set up some -;; argument data, which may take a while. - -;; The linter will try to "include" any require'd libraries to find -;; the variables defined in those. There is a fair amount of voodoo -;; involved in this, but it seems to work in normal situations. - -;;; To do: - -;; * Adding type checking. (Stop that sniggering!) -;; * Make eval-when-compile be sensitive to the difference between -;; functions and macros. -;; * Requires within function bodies. -;; * Handle defstruct. -;; * Prevent recursive requires. - -;;; Code: - -(defgroup elint nil - "Linting for Emacs Lisp." - :prefix "elint-" - :group 'maint) - -(defcustom elint-log-buffer "*Elint*" - "The buffer in which to log lint messages." - :type 'string - :safe 'stringp - :group 'elint) - -(defcustom elint-scan-preloaded t - "Non-nil means to scan `preloaded-file-list' when initializing. -Otherwise, just scan the DOC file for functions and variables. -This is faster, but less accurate, since it misses undocumented features. -This may result in spurious warnings about unknown functions, etc." - :type 'boolean - :safe 'booleanp - :group 'elint - :version "23.2") - -(defcustom elint-ignored-warnings nil - "If non-nil, a list of issue types that Elint should ignore. -This is useful if Elint has trouble understanding your code and -you need to suppress lots of spurious warnings. The valid list elements -are as follows, and suppress messages about the indicated features: - undefined-functions - calls to unknown functions - unbound-reference - reference to unknown variables - unbound-assignment - assignment to unknown variables - macro-expansions - failure to expand macros - empty-let - let-bindings with empty variable lists" - :type '(choice (const :tag "Don't suppress any warnings" nil) - (repeat :tag "List of issues to ignore" - (choice (const :tag "Calls to unknown functions" - undefined-functions) - (const :tag "Reference to unknown variables" - unbound-reference) - (const :tag "Assignment to unknown variables" - unbound-assignment) - (const :tag "Failure to expand macros" - macro-expansion) - (const :tag "Let-binding with empty varlist" - empty-let)))) - :safe (lambda (value) (or (null value) - (and (listp value) - (equal value - (mapcar - (lambda (e) - (if (memq e - '(undefined-functions - unbound-reference - unbound-assignment - macro-expansion - empty-let)) - e)) - value))))) - :version "23.2" - :group 'elint) - -(defcustom elint-directory-skip-re "\\(ldefs-boot\\|loaddefs\\)\\.el\\'" - "If non-nil, a regexp matching files to skip when linting a directory." - :type '(choice (const :tag "Lint all files" nil) - (regexp :tag "Regexp to skip")) - :safe 'string-or-null-p - :group 'elint - :version "23.2") - -;;; -;;; Data -;;; - -(defconst elint-standard-variables - ;; Most of these are defined in C with no documentation. - ;; FIXME I don't see why they shouldn't just get doc-strings. - '(vc-mode local-write-file-hooks activate-menubar-hook buffer-name-history - coding-system-history extended-command-history - yes-or-no-p-history) - "Standard variables, excluding `elint-builtin-variables'. -These are variables that we cannot detect automatically for some reason.") - -(defvar elint-builtin-variables nil - "List of built-in variables. Set by `elint-initialize'. -This is actually all those documented in the DOC file, which includes -built-in variables and those from dumped Lisp files.") - -(defvar elint-autoloaded-variables nil - "List of `loaddefs.el' variables. Set by `elint-initialize'.") - -(defvar elint-preloaded-env nil - "Environment defined by the preloaded (dumped) Lisp files. -Set by `elint-initialize', if `elint-scan-preloaded' is non-nil.") - -(defconst elint-unknown-builtin-args - ;; encode-time allows extra arguments for use with decode-time. - ;; For some reason, some people seem to like to use them in other cases. - '((encode-time time &rest obsolescent-arguments)) - "Those built-ins for which we can't find arguments, if any.") - -(defvar elint-extra-errors '(file-locked file-supersession ftp-error) - "Errors without `error-message' or `error-conditions' properties.") - -(defconst elint-preloaded-skip-re - (regexp-opt '("loaddefs.el" "loadup.el" "cus-start" "language/" - "eucjp-ms" "mule-conf" "/characters" "/charprop" - "cp51932")) - "Regexp matching elements of `preloaded-file-list' to ignore. -We ignore them because they contain no definitions of use to Elint.") - -(defvar elint-running) -(defvar elint-current-pos) ; dynamically bound in elint-top-form - -;;; -;;; ADT: top-form -;;; - -(defsubst elint-make-top-form (form pos) - "Create a top form. -FORM is the form, and POS is the point where it starts in the buffer." - (cons form pos)) - -(defsubst elint-top-form-form (top-form) - "Extract the form from a TOP-FORM." - (car top-form)) - -(defsubst elint-top-form-pos (top-form) - "Extract the position from a TOP-FORM." - (cdr top-form)) - -;;; -;;; ADT: env -;;; - -(defsubst elint-make-env () - "Create an empty environment." - (list (list nil) nil nil)) - -(defsubst elint-env-add-env (env newenv) - "Augment ENV with NEWENV. -None of them is modified, and the new env is returned." - (list (append (car env) (car newenv)) - (append (cadr env) (cadr newenv)) - (append (car (cdr (cdr env))) (car (cdr (cdr newenv)))))) - -(defsubst elint-env-add-var (env var) - "Augment ENV with the variable VAR. -The new environment is returned, the old is unmodified." - (cons (cons (list var) (car env)) (cdr env))) - -(defsubst elint-env-add-global-var (env var) - "Augment ENV with the variable VAR. -ENV is modified so VAR is seen everywhere. -ENV is returned." - (nconc (car env) (list (list var))) - env) - -(defsubst elint-env-find-var (env var) - "Non-nil if ENV contains the variable VAR. -Actually, a list with VAR as a single element is returned." - (assq var (car env))) - -(defsubst elint-env-add-func (env func args) - "Augment ENV with the function FUNC, which has the arguments ARGS. -The new environment is returned, the old is unmodified." - (list (car env) - (cons (list func args) (cadr env)) - (car (cdr (cdr env))))) - -(defsubst elint-env-find-func (env func) - "Non-nil if ENV contains the function FUNC. -Actually, a list of (FUNC ARGS) is returned." - (assq func (cadr env))) - -(defsubst elint-env-add-macro (env macro def) - "Augment ENV with the macro named MACRO. -DEF is the macro definition (a lambda expression or similar). -The new environment is returned, the old is unmodified." - (list (car env) - (cadr env) - (cons (cons macro def) (car (cdr (cdr env)))))) - -(defsubst elint-env-macro-env (env) - "Return the macro environment of ENV. -This environment can be passed to `macroexpand'." - (car (cdr (cdr env)))) - -(defsubst elint-env-macrop (env macro) - "Non-nil if ENV contains MACRO." - (assq macro (elint-env-macro-env env))) - -;;; -;;; User interface -;;; - -;;;###autoload -(defun elint-file (file) - "Lint the file FILE." - (interactive "fElint file: ") - (setq file (expand-file-name file)) - (or elint-builtin-variables - (elint-initialize)) - (let ((dir (file-name-directory file))) - (let ((default-directory dir)) - (elint-display-log)) - (elint-set-mode-line t) - (with-current-buffer elint-log-buffer - (unless (string-equal default-directory dir) - (elint-log-message (format-message " \nLeaving directory `%s'" - default-directory) t) - (elint-log-message (format-message "Entering directory `%s'" dir) t) - (setq default-directory dir)))) - (let ((str (format "Linting file %s" file))) - (message "%s..." str) - (or noninteractive - (elint-log-message (format " \n%s at %s" str (current-time-string)) t)) - ;; elint-current-buffer clears log. - (with-temp-buffer - (insert-file-contents file) - (let ((buffer-file-name file) - (max-lisp-eval-depth (max 1000 max-lisp-eval-depth))) - (hack-local-variables) - (with-syntax-table emacs-lisp-mode-syntax-table - (mapc 'elint-top-form (elint-update-env))))) - (elint-set-mode-line) - (message "%s...done" str))) - -;; cf byte-recompile-directory. -;;;###autoload -(defun elint-directory (directory) - "Lint all the .el files in DIRECTORY. -A complicated directory may require a lot of memory." - (interactive "DElint directory: ") - (let ((elint-running t)) - (dolist (file (directory-files directory t)) - ;; Bytecomp has emacs-lisp-file-regexp. - (when (and (string-match "\\.el\\'" file) - (file-readable-p file) - (not (auto-save-file-name-p file))) - (if (string-match elint-directory-skip-re file) - (message "Skipping file %s" file) - (elint-file file))))) - (elint-set-mode-line)) - -;;;###autoload -(defun elint-current-buffer () - "Lint the current buffer. -If necessary, this first calls `elint-initialize'." - (interactive) - (or elint-builtin-variables - (elint-initialize)) - (elint-clear-log (format "Linting %s" (or (buffer-file-name) - (buffer-name)))) - (elint-display-log) - (elint-set-mode-line t) - (mapc 'elint-top-form (elint-update-env)) - ;; Tell the user we're finished. This is terribly kludgy: we set - ;; elint-top-form-logged so elint-log-message doesn't print the - ;; ** top form ** header... - (elint-set-mode-line) - (elint-log-message "\nLinting finished.\n" t)) - - -;;;###autoload -(defun elint-defun () - "Lint the function at point. -If necessary, this first calls `elint-initialize'." - (interactive) - (or elint-builtin-variables - (elint-initialize)) - (save-excursion - (or (beginning-of-defun) (error "Lint what?")) - (let ((pos (point)) - (def (read (current-buffer)))) - (elint-display-log) - (elint-update-env) - (elint-top-form (elint-make-top-form def pos))))) - -;;; -;;; Top form functions -;;; - -(defvar elint-buffer-env nil - "The environment of an elisp buffer. -Will be local in linted buffers.") - -(defvar elint-buffer-forms nil - "The top forms in a buffer. -Will be local in linted buffers.") - -(defvar elint-last-env-time nil - "The last time the buffers env was updated. -Is measured in buffer-modified-ticks and is local in linted buffers.") - -;; This is a minor optimization. It is local to every buffer, and so -;; does not prevent recursive requires. It does not list the requires -;; of requires. -(defvar elint-features nil - "List of all libraries this buffer has required, or that have been provided.") - -(defun elint-update-env () - "Update the elint environment in the current buffer. -Don't do anything if the buffer hasn't been changed since this -function was called the last time. -Returns the forms." - (if (and (local-variable-p 'elint-buffer-env (current-buffer)) - (local-variable-p 'elint-buffer-forms (current-buffer)) - (local-variable-p 'elint-last-env-time (current-buffer)) - (= (buffer-modified-tick) elint-last-env-time)) - ;; Env is up to date - elint-buffer-forms - ;; Remake env - (setq-local elint-buffer-forms (elint-get-top-forms)) - (setq-local elint-features nil) - (setq-local elint-buffer-env (elint-init-env elint-buffer-forms)) - (if elint-preloaded-env - ;; FIXME: This doesn't do anything! Should we setq the result to - ;; elint-buffer-env? - (elint-env-add-env elint-preloaded-env elint-buffer-env)) - (setq-local elint-last-env-time (buffer-modified-tick)) - elint-buffer-forms)) - -(defun elint-get-top-forms () - "Collect all the top forms in the current buffer." - (save-excursion - (let (tops) - (goto-char (point-min)) - (while (elint-find-next-top-form) - (let ((elint-current-pos (point))) - ;; non-list check could be here too. errors may be out of seq. - ;; quoted check cannot be elsewhere, since quotes skipped. - (if (= (preceding-char) ?\') - ;; Eg cust-print.el uses ' as a comment syntax. - (elint-warning "Skipping quoted form `%c%.20s...'" ?\' - (read (current-buffer))) - (condition-case nil - (setq tops (cons - (elint-make-top-form (read (current-buffer)) - elint-current-pos) - tops)) - (end-of-file - (goto-char elint-current-pos) - (error "Missing `)' in top form: %s" - (buffer-substring elint-current-pos - (line-end-position)))))))) - (nreverse tops)))) - -(defun elint-find-next-top-form () - "Find the next top form from point. -Return nil if there are no more forms, t otherwise." - (parse-partial-sexp (point) (point-max) nil t) - (not (eobp))) - -(defvar elint-env) ; from elint-init-env - -(defun elint-init-form (form) - "Process FORM, adding to ELINT-ENV if recognized." - (cond - ;; Eg nnmaildir seems to use [] as a form of comment syntax. - ((not (listp form)) - (elint-warning "Skipping non-list form `%s'" form)) - ;; Add defined variable - ((memq (car form) '(defvar defconst defcustom)) - (setq elint-env (elint-env-add-var elint-env (cadr form)))) - ;; Add function - ((memq (car form) '(defun defsubst)) - (setq elint-env (elint-env-add-func elint-env (cadr form) (nth 2 form)))) - ;; FIXME needs a handler to say second arg is not a variable when we come - ;; to scan the form. - ((eq (car form) 'define-derived-mode) - (setq elint-env (elint-env-add-func elint-env (cadr form) ()) - elint-env (elint-env-add-var elint-env (cadr form)) - elint-env (elint-env-add-var elint-env - (intern (format "%s-map" (cadr form)))))) - ((eq (car form) 'define-minor-mode) - (setq elint-env (elint-env-add-func elint-env (cadr form) '(&optional arg)) - ;; FIXME mode map? - elint-env (elint-env-add-var elint-env (cadr form)))) - ((and (eq (car form) 'easy-menu-define) - (cadr form)) - (setq elint-env (elint-env-add-func elint-env (cadr form) '(event)) - elint-env (elint-env-add-var elint-env (cadr form)))) - ;; FIXME it would be nice to check the autoloads are correct. - ((eq (car form) 'autoload) - (setq elint-env (elint-env-add-func elint-env (cadr (cadr form)) 'unknown))) - ((eq (car form) 'declare-function) - (setq elint-env (elint-env-add-func - elint-env (cadr form) - (if (or (< (length form) 4) - (eq (nth 3 form) t) - (unless (stringp (nth 2 form)) - (elint-error "Malformed declaration for `%s'" - (cadr form)) - t)) - 'unknown - (nth 3 form))))) - ((and (eq (car form) 'defalias) (listp (nth 2 form))) - ;; If the alias points to something already in the environment, - ;; add the alias to the environment with the same arguments. - ;; FIXME symbol-function, eg backquote.el? - (let ((def (elint-env-find-func elint-env (cadr (nth 2 form))))) - (setq elint-env (elint-env-add-func elint-env (cadr (cadr form)) - (if def (cadr def) 'unknown))))) - ;; Add macro, both as a macro and as a function - ((eq (car form) 'defmacro) - (setq elint-env (elint-env-add-macro elint-env (cadr form) - (cons 'lambda (cddr form))) - elint-env (elint-env-add-func elint-env (cadr form) (nth 2 form)))) - ((and (eq (car form) 'put) - (= 4 (length form)) - (eq (car-safe (cadr form)) 'quote) - (equal (nth 2 form) '(quote error-conditions))) - (setq-local elint-extra-errors - (cons (cadr (cadr form)) elint-extra-errors))) - ((eq (car form) 'provide) - (add-to-list 'elint-features (eval (cadr form)))) - ;; Import variable definitions - ((memq (car form) '(require cc-require cc-require-when-compile)) - (let ((name (eval (cadr form))) - (file (eval (nth 2 form)))) - (unless (memq name elint-features) - (add-to-list 'elint-features name) - (setq elint-env (elint-add-required-env elint-env name file)))))) - elint-env) - -(defun elint-init-env (forms) - "Initialize the environment from FORMS." - (let ((elint-env (elint-make-env)) - form) - (while forms - (setq form (elint-top-form-form (car forms)) - forms (cdr forms)) - ;; FIXME eval-when-compile should be treated differently (macros). - ;; Could bind something that makes elint-init-form only check - ;; defmacros. - (if (memq (car-safe form) - '(eval-and-compile eval-when-compile progn prog1 prog2 - with-no-warnings)) - (mapc 'elint-init-form (cdr form)) - (elint-init-form form))) - elint-env)) - -(defun elint-add-required-env (env name file) - "Augment ENV with the variables defined by feature NAME in FILE." - (condition-case err - (let* ((libname (if (stringp file) - file - (symbol-name name))) - - ;; First try to find .el files, then the raw name - (lib1 (locate-library (concat libname ".el") t)) - (lib (or lib1 (locate-library libname t)))) - ;; Clear the messages :-/ - ;; (Messes up the "Initializing elint..." message.) -;;; (message nil) - (if lib - (with-current-buffer (find-file-noselect lib) - ;; FIXME this doesn't use a temp buffer, because it - ;; stores the result in buffer-local variables so that - ;; it can be reused. - (elint-update-env) - (setq env (elint-env-add-env env elint-buffer-env))) - ;;; (with-temp-buffer - ;;; (insert-file-contents lib) - ;;; (with-syntax-table emacs-lisp-mode-syntax-table - ;;; (elint-update-env)) - ;;; (setq env (elint-env-add-env env elint-buffer-env)))) - ;;(message "%s" (format "Elint processed (require '%s)" name)) - (error "%s.el not found in load-path" libname))) - (error - (message "Can't get variables from require'd library %s: %s" - name (error-message-string err)))) - env) - -(defvar elint-top-form nil - "The currently linted top form, or nil.") - -(defvar elint-top-form-logged nil - "Non-nil if the currently linted top form has been mentioned in the log buffer.") - -(defun elint-top-form (form) - "Lint a top FORM." - (let ((elint-top-form form) - (elint-top-form-logged nil) - (elint-current-pos (elint-top-form-pos form))) - (elint-form (elint-top-form-form form) elint-buffer-env))) - -;;; -;;; General form linting functions -;;; - -(defconst elint-special-forms - '((let . elint-check-let-form) - (let* . elint-check-let-form) - (setq . elint-check-setq-form) - (quote . elint-check-quote-form) - (function . elint-check-quote-form) - (cond . elint-check-cond-form) - (lambda . elint-check-defun-form) - (function . elint-check-function-form) - (setq-default . elint-check-setq-form) - (defalias . elint-check-defalias-form) - (defun . elint-check-defun-form) - (defsubst . elint-check-defun-form) - (defmacro . elint-check-defun-form) - (defvar . elint-check-defvar-form) - (defconst . elint-check-defvar-form) - (defcustom . elint-check-defcustom-form) - (macro . elint-check-macro-form) - (condition-case . elint-check-condition-case-form) - (condition-case-unless-debug . elint-check-condition-case-form) - (if . elint-check-conditional-form) - (when . elint-check-conditional-form) - (unless . elint-check-conditional-form) - (and . elint-check-conditional-form) - (or . elint-check-conditional-form) - (require . elint-require-form)) - "Functions to call when some special form should be linted.") - -(defun elint-form (form env &optional nohandler) - "Lint FORM in the environment ENV. -Optional argument NOHANDLER non-nil means ignore `elint-special-forms'. -Returns the environment created by the form." - (cond - ((consp form) - (let ((func (cdr (assq (car form) elint-special-forms)))) - (if (and func (not nohandler)) - ;; Special form - (funcall func form env) - - (let* ((func (car form)) - (args (elint-get-args func env)) - (argsok t)) - (cond - ((eq args 'undefined) - (setq argsok nil) - (or (memq 'undefined-functions elint-ignored-warnings) - (elint-error "Call to undefined function: %s" func))) - - ((eq args 'unknown) nil) - - (t (setq argsok (elint-match-args form args)))) - - ;; Is this a macro? - (if (elint-env-macrop env func) - ;; Macro defined in buffer, expand it - (if argsok - ;; FIXME error if macro uses macro, eg bytecomp.el. - (condition-case nil - (elint-form - (macroexpand form (elint-env-macro-env env)) env) - (error - (or (memq 'macro-expansion elint-ignored-warnings) - (elint-error "Elint failed to expand macro: %s" func)) - env)) - env) - - (let ((fcode (if (symbolp func) - (if (fboundp func) - (indirect-function func)) - func))) - (if (and (listp fcode) (eq (car fcode) 'macro)) - ;; Macro defined outside buffer - (if argsok - (elint-form (macroexpand form) env) - env) - ;; Function, lint its parameters - (elint-forms (cdr form) env)))))))) - ((symbolp form) - ;; :foo variables are quoted - (and (/= (aref (symbol-name form) 0) ?:) - (not (memq 'unbound-reference elint-ignored-warnings)) - (elint-unbound-variable form env) - (elint-warning "Reference to unbound symbol: %s" form)) - env) - - (t env))) - -(defun elint-forms (forms env) - "Lint the FORMS, accumulating an environment, starting with ENV." - ;; grumblegrumbletailrecursiongrumblegrumble - (if (listp forms) - (dolist (f forms env) - (setq env (elint-form f env))) - ;; Loop macro? - (elint-error "Elint failed to parse form: %s" forms) - env)) - -(defvar elint-bound-variable nil - "Name of a temporarily bound symbol.") - -(defun elint-unbound-variable (var env) - "Return t if VAR is unbound in ENV." - ;; #1063 suggests adding (symbol-file var) here, but I don't think - ;; this is right, because it depends on what files you happen to have - ;; loaded at the time, which might not be the same when the code runs. - ;; It also suggests adding: - ;; (numberp (get var 'variable-documentation)) - ;; (numberp (cdr-safe (get var 'variable-documentation))) - ;; but this is not needed now elint-scan-doc-file exists. - (not (or (memq var '(nil t)) - (eq var elint-bound-variable) - (elint-env-find-var env var) - (memq var elint-builtin-variables) - (memq var elint-autoloaded-variables) - (memq var elint-standard-variables)))) - -;;; -;;; Function argument checking -;;; - -(defun elint-match-args (arglist argpattern) - "Match ARGLIST against ARGPATTERN." - (let ((state 'all) - (al (cdr arglist)) - (ap argpattern) - (ok t)) - (while - (cond - ((and (null al) (null ap)) nil) - ((eq (car ap) '&optional) - (setq state 'optional) - (setq ap (cdr ap)) - t) - ((eq (car ap) '&rest) - nil) - ((or (and (eq state 'all) (or (null al) (null ap))) - (and (eq state 'optional) (and al (null ap)))) - (elint-error "Wrong number of args: %s, %s" arglist argpattern) - (setq ok nil) - nil) - ((and (eq state 'optional) (null al)) - nil) - (t (setq al (cdr al) - ap (cdr ap)) - t))) - ok)) - -(defvar elint-bound-function nil - "Name of a temporarily bound function symbol.") - -(defun elint-get-args (func env) - "Find the args of FUNC in ENV. -Returns `unknown' if we couldn't find arguments." - (let ((f (elint-env-find-func env func))) - (if f - (cadr f) - (if (symbolp func) - (if (eq func elint-bound-function) - 'unknown - (if (fboundp func) - (let ((fcode (indirect-function func))) - (if (subrp fcode) - ;; FIXME builtins with no args have args = nil. - (or (get func 'elint-args) 'unknown) - (elint-find-args-in-code fcode))) - 'undefined)) - (elint-find-args-in-code func))))) - -(defun elint-find-args-in-code (code) - "Extract the arguments from CODE. -CODE can be a lambda expression, a macro, or byte-compiled code." - (let ((args (help-function-arglist code))) - (if (listp args) args 'unknown))) - -;;; -;;; Functions to check some special forms -;;; - -(defun elint-check-cond-form (form env) - "Lint a cond FORM in ENV." - (dolist (f (cdr form)) - (if (consp f) - (let ((test (car f))) - (cond ((equal test '(featurep (quote xemacs)))) - ((equal test '(not (featurep (quote emacs))))) - ;; FIXME (and (boundp 'foo) - ((and (eq (car-safe test) 'fboundp) - (= 2 (length test)) - (eq (car-safe (cadr test)) 'quote)) - (let ((elint-bound-function (cadr (cadr test)))) - (elint-forms f env))) - ((and (eq (car-safe test) 'boundp) - (= 2 (length test)) - (eq (car-safe (cadr test)) 'quote)) - (let ((elint-bound-variable (cadr (cadr test)))) - (elint-forms f env))) - (t (elint-forms f env)))) - (elint-error "cond clause should be a list: %s" f))) - env) - -(defun elint-check-defun-form (form env) - "Lint a defun/defmacro/lambda FORM in ENV." - (setq form (if (eq (car form) 'lambda) (cdr form) (cddr form))) - (mapc (lambda (p) - (or (memq p '(&optional &rest)) - (setq env (elint-env-add-var env p)))) - (car form)) - (elint-forms (cdr form) env)) - -(defun elint-check-defalias-form (form env) - "Lint a defalias FORM in ENV." - (let ((alias (cadr form)) - (target (nth 2 form))) - (and (eq (car-safe alias) 'quote) - (eq (car-safe target) 'quote) - (eq (elint-get-args (cadr target) env) 'undefined) - (elint-warning "Alias `%s' has unknown target `%s'" - (cadr alias) (cadr target)))) - (elint-form form env t)) - -(defun elint-check-let-form (form env) - "Lint the let/let* FORM in ENV." - (let ((varlist (cadr form))) - (if (not varlist) - (if (> (length form) 2) - ;; An empty varlist is not really an error. Eg some cl macros - ;; can expand to such a form. - (progn - (or (memq 'empty-let elint-ignored-warnings) - (elint-warning "Empty varlist in let: %s" form)) - ;; Lint the body forms - (elint-forms (cddr form) env)) - (elint-error "Malformed let: %s" form) - env) - ;; Check for (let (a (car b)) ...) type of error - (if (and (= (length varlist) 2) - (symbolp (car varlist)) - (listp (car (cdr varlist))) - (fboundp (car (car (cdr varlist))))) - (elint-warning "Suspect varlist: %s" form)) - ;; Add variables to environment, and check the init values - (let ((newenv env)) - (mapc (lambda (s) - (cond - ((symbolp s) - (setq newenv (elint-env-add-var newenv s))) - ((and (consp s) (<= (length s) 2)) - (elint-form (cadr s) - (if (eq (car form) 'let) - env - newenv)) - (setq newenv - (elint-env-add-var newenv (car s)))) - (t (elint-error - "Malformed `let' declaration: %s" s)))) - varlist) - - ;; Lint the body forms - (elint-forms (cddr form) newenv))))) - -(defun elint-check-setq-form (form env) - "Lint the setq FORM in ENV." - (or (oddp (length form)) - ;; (setq foo) is valid and equivalent to (setq foo nil). - (elint-warning "Missing value in setq: %s" form)) - (let ((newenv env) - sym val) - (setq form (cdr form)) - (while form - (setq sym (car form) - val (car (cdr form)) - form (cdr (cdr form))) - (if (symbolp sym) - (and (not (memq 'unbound-assignment elint-ignored-warnings)) - (elint-unbound-variable sym newenv) - (elint-warning "Setting previously unbound symbol: %s" sym)) - (elint-error "Setting non-symbol in setq: %s" sym)) - (elint-form val newenv) - (if (symbolp sym) - (setq newenv (elint-env-add-var newenv sym)))) - newenv)) - -(defun elint-check-defvar-form (form env) - "Lint the defvar/defconst FORM in ENV." - (if (or (= (length form) 2) - (= (length form) 3) - ;; Eg the defcalcmodevar macro can expand with a nil doc-string. - (and (= (length form) 4) (string-or-null-p (nth 3 form)))) - (elint-env-add-global-var (elint-form (nth 2 form) env) - (car (cdr form))) - (elint-error "Malformed variable declaration: %s" form) - env)) - -(defun elint-check-defcustom-form (form env) - "Lint the defcustom FORM in ENV." - (if (and (> (length form) 3) - ;; even no. of keyword/value args ? - (evenp (length form))) - (elint-env-add-global-var (elint-form (nth 2 form) env) - (car (cdr form))) - (elint-error "Malformed variable declaration: %s" form) - env)) - -(defun elint-check-function-form (form env) - "Lint the function FORM in ENV." - (let ((func (car (cdr-safe form)))) - (cond - ((symbolp func) - (or (elint-env-find-func env func) - ;; FIXME potentially bogus, since it uses the current - ;; environment rather than a clean one. - (fboundp func) - (elint-warning "Reference to undefined function: %s" form)) - env) - ((and (consp func) (memq (car func) '(lambda macro))) - (elint-form func env)) - ((stringp func) env) - (t (elint-error "Not a function object: %s" form) - env)))) - -(defun elint-check-quote-form (_form env) - "Lint the quote FORM in ENV." - env) - -(defun elint-check-macro-form (form env) - "Check the macro FORM in ENV." - (elint-check-function-form (list (car form) (cdr form)) env)) - -(defun elint-check-condition-case-form (form env) - "Check the `condition-case' FORM in ENV." - (let ((resenv env)) - (if (< (length form) 3) - (elint-error "Malformed condition-case: %s" form) - (or (symbolp (cadr form)) - (elint-warning "First parameter should be a symbol: %s" form)) - (setq resenv (elint-form (nth 2 form) env)) - (let ((newenv (elint-env-add-var env (cadr form))) - errlist) - (dolist (err (nthcdr 3 form)) - (setq errlist (car err)) - (mapc (lambda (s) - (or (get s 'error-conditions) - (get s 'error-message) - (memq s elint-extra-errors) - (elint-warning - "Not an error symbol in error handler: %s" s))) - (cond - ((symbolp errlist) (list errlist)) - ((listp errlist) errlist) - (t (elint-error "Bad error list in error handler: %s" - errlist) - nil))) - (elint-forms (cdr err) newenv)))) - resenv)) - -;; For the featurep parts, an alternative is to have -;; elint-get-top-forms skip the irrelevant branches. -(defun elint-check-conditional-form (form env) - "Check the when/unless/and/or FORM in ENV. -Does basic handling of `featurep' tests." - (let ((func (car form)) - (test (cadr form))) - ;; Misses things like (and t (featurep 'xemacs)) - ;; Check byte-compile-maybe-guarded. - (cond ((and (memq func '(when and)) - (eq (car-safe test) 'boundp) - (= 2 (length test)) - (eq (car-safe (cadr test)) 'quote)) - ;; Cf elint-check-let-form, which modifies the whole ENV. - (let ((elint-bound-variable (cadr (cadr test)))) - (elint-form form env t))) - ((and (memq func '(when and)) - (eq (car-safe test) 'fboundp) - (= 2 (length test)) - (eq (car-safe (cadr test)) 'quote)) - (let ((elint-bound-function (cadr (cadr test)))) - (elint-form form env t))) - ;; Let's not worry about (if (not (boundp... - ((and (eq func 'if) - (eq (car-safe test) 'boundp) - (= 2 (length test)) - (eq (car-safe (cadr test)) 'quote)) - (let ((elint-bound-variable (cadr (cadr test)))) - (elint-form (nth 2 form) env)) - (dolist (f (nthcdr 3 form)) - (elint-form f env))) - ((and (eq func 'if) - (eq (car-safe test) 'fboundp) - (= 2 (length test)) - (eq (car-safe (cadr test)) 'quote)) - (let ((elint-bound-function (cadr (cadr test)))) - (elint-form (nth 2 form) env)) - (dolist (f (nthcdr 3 form)) - (elint-form f env))) - ((and (memq func '(when and)) ; skip all - (or (null test) - (member test '((featurep (quote xemacs)) - (not (featurep (quote emacs))))) - (and (eq (car-safe test) 'and) - (equal (car-safe (cdr test)) - '(featurep (quote xemacs))))))) - ((and (memq func '(unless or)) - (equal test '(featurep (quote emacs))))) - ((and (eq func 'if) - (or (null test) - (member test '((featurep (quote xemacs)) - (not (featurep (quote emacs))))) - (and (eq (car-safe test) 'and) - (equal (car-safe (cdr test)) - '(featurep (quote xemacs)))))) - (dolist (f (nthcdr 3 form)) - (elint-form f env))) ; lint the else branch - ((and (eq func 'if) - (equal test '(featurep (quote emacs)))) - (elint-form (nth 2 form) env)) ; lint the if branch - ;; Process conditional as normal, without handler. - (t - (elint-form form env t)))) - env) - -(defun elint-require-form (form _env) - "Load `require'd files." - (pcase form - (`(require ',x) - (require x))) - nil) - -;;; -;;; Message functions -;;; - -(defun elint-log (type string args) - (elint-log-message (format "%s:%d:%s: %s" - (let ((f (buffer-file-name))) - (if f - (file-name-nondirectory f) - (buffer-name))) - (if (boundp 'elint-current-pos) - (save-excursion - (goto-char elint-current-pos) - (1+ (count-lines (point-min) - (line-beginning-position)))) - 0) ; unknown position - type - (apply #'format-message string args)))) - -(defun elint-error (string &rest args) - "Report a linting error. -STRING and ARGS are thrown on `format' to get the message." - (elint-log "Error" string args)) - -(defun elint-warning (string &rest args) - "Report a linting warning. -See `elint-error'." - (elint-log "Warning" string args)) - -(defun elint-output (string) - "Print or insert STRING, depending on value of `noninteractive'." - (if noninteractive - (message "%s" string) - (insert string "\n"))) - -(defun elint-log-message (errstr &optional top) - "Insert ERRSTR last in the lint log buffer. -Optional argument TOP non-nil means pretend `elint-top-form-logged' is non-nil." - (with-current-buffer (elint-get-log-buffer) - (goto-char (point-max)) - (let ((inhibit-read-only t)) - (or (bolp) (newline)) - ;; Do we have to say where we are? - (unless (or elint-top-form-logged top) - (let* ((form (elint-top-form-form elint-top-form)) - (top (car form))) - (elint-output (cond - ((memq top '(defun defsubst)) - (format "\nIn function %s:" (cadr form))) - ((eq top 'defmacro) - (format "\nIn macro %s:" (cadr form))) - ((memq top '(defvar defconst)) - (format "\nIn variable %s:" (cadr form))) - (t "\nIn top level expression:")))) - (setq elint-top-form-logged t)) - (elint-output errstr)))) - -(defun elint-clear-log (&optional header) - "Clear the lint log buffer. -Insert HEADER followed by a blank line if non-nil." - (let ((dir default-directory)) - (with-current-buffer (elint-get-log-buffer) - (setq default-directory dir) - (let ((inhibit-read-only t)) - (erase-buffer) - (if header (insert header "\n")))))) - -(defun elint-display-log () - "Display the lint log buffer." - (let ((pop-up-windows t)) - (display-buffer (elint-get-log-buffer)) - (sit-for 0))) - -(defun elint-set-mode-line (&optional on) - "Set the `mode-line-process' of the Elint log buffer." - (with-current-buffer (elint-get-log-buffer) - (and (eq major-mode 'compilation-mode) - (setq mode-line-process - (list (if (or on (bound-and-true-p elint-running)) - (propertize ":run" 'face 'compilation-warning) - (propertize ":finished" 'face 'compilation-info))))))) - -(defun elint-get-log-buffer () - "Return a log buffer for elint." - (or (get-buffer elint-log-buffer) - (with-current-buffer (get-buffer-create elint-log-buffer) - (or (eq major-mode 'compilation-mode) - (compilation-mode)) - (setq buffer-undo-list t) - (current-buffer)))) - -;;; -;;; Initializing code -;;; - -(defun elint-put-function-args (func args) - "Mark function FUNC as having argument list ARGS." - (and (symbolp func) - args - (not (eq args 'unknown)) - (put func 'elint-args args))) - -;;;###autoload -(defun elint-initialize (&optional reinit) - "Initialize elint. -If elint is already initialized, this does nothing, unless -optional prefix argument REINIT is non-nil." - (interactive "P") - (if (and elint-builtin-variables (not reinit)) - (message "Elint is already initialized") - (message "Initializing elint...") - (setq elint-builtin-variables (elint-scan-doc-file) - elint-autoloaded-variables (elint-find-autoloaded-variables)) - (mapc (lambda (x) (elint-put-function-args (car x) (cdr x))) - (elint-find-builtin-args)) - (if elint-unknown-builtin-args - (mapc (lambda (x) (elint-put-function-args (car x) (cdr x))) - elint-unknown-builtin-args)) - (when elint-scan-preloaded - (dolist (lib preloaded-file-list) - ;; Skip files that contain nothing of use to us. - (unless (string-match elint-preloaded-skip-re lib) - (setq elint-preloaded-env - (elint-add-required-env elint-preloaded-env nil lib))))) - (message "Initializing elint...done"))) - - -;; This includes all the built-in and dumped things with documentation. -(defun elint-scan-doc-file () - "Scan the DOC file for function and variables. -Marks the function with their arguments, and returns a list of variables." - ;; Cribbed from help-fns.el. - (let ((docbuf " *DOC*") - vars sym args) - (save-excursion - (if (get-buffer docbuf) - (progn - (set-buffer docbuf) - (goto-char (point-min))) - (set-buffer (get-buffer-create docbuf)) - (insert-file-contents-literally - (expand-file-name internal-doc-file-name doc-directory))) - (while (re-search-forward "\^_\\([VF]\\)" nil t) - (when (setq sym (intern-soft (buffer-substring (point) - (line-end-position)))) - (if (string-equal (match-string 1) "V") - ;; Excludes platform-specific stuff not relevant to the - ;; running platform. - (if (boundp sym) (setq vars (cons sym vars))) - ;; Function. - (when (fboundp sym) - (when (re-search-forward "\\(^(fn.*)\\)?\^_" nil t) - (backward-char 1) - ;; FIXME distinguish no args from not found. - (and (setq args (match-string 1)) - (setq args - (ignore-errors - (read - (replace-regexp-in-string "^(fn ?" "(" args)))) - (elint-put-function-args sym args)))))))) - vars)) - -(defun elint-find-autoloaded-variables () - "Return a list of all autoloaded variables." - (let (var vars) - (with-temp-buffer - (insert-file-contents (locate-library "loaddefs.el")) - (while (re-search-forward "^(defvar \\([[:alnum:]_-]+\\)" nil t) - (and (setq var (intern-soft (match-string 1))) - (boundp var) - (setq vars (cons var vars))))) - vars)) - -(defun elint-find-builtins () - "Return a list of all built-in functions." - (let (subrs) - (mapatoms (lambda (s) (and (subrp (symbol-function s)) - (push s subrs)))) - subrs)) - -(defun elint-find-builtin-args (&optional list) - "Return a list of the built-in functions and their arguments. -If LIST is nil, call `elint-find-builtins' to get a list of all built-in -functions, otherwise use LIST. - -Each function is represented by a cons cell: -\(function-symbol . args) -If no documentation could be found args will be `unknown'." - (mapcar (lambda (f) - (let ((doc (documentation f t))) - (or (and doc - (string-match "\n\n(fn\\(.*)\\)\\'" doc) - (ignore-errors - ;; "BODY...)" -> "&rest BODY)". - (read (replace-regexp-in-string - "\\([^ ]+\\)\\.\\.\\.)\\'" "&rest \\1)" - (format "(%s %s" f (match-string 1 doc)) t)))) - (cons f 'unknown)))) - (or list (elint-find-builtins)))) - -(provide 'elint) - -;;; elint.el ends here diff --git a/lisp/progmodes/elisp-mode.el b/lisp/progmodes/elisp-mode.el index d4a8a13c3d4..b91156d6224 100644 --- a/lisp/progmodes/elisp-mode.el +++ b/lisp/progmodes/elisp-mode.el @@ -111,15 +111,6 @@ All commands in `lisp-mode-shared-map' are inherited by this map." :help "Go to the start of the current function definition"] ["Up List" up-list :help "Go one level up and forward"]) - ("Linting" - ["Lint Defun" elint-defun - :help "Lint the function at point"] - ["Lint Buffer" elint-current-buffer - :help "Lint the current buffer"] - ["Lint File..." elint-file - :help "Lint a file"] - ["Lint Directory..." elint-directory - :help "Lint a directory"]) ("Profiling" ;; Maybe this should be in a separate submenu from the ELP stuff? ["Start Native Profiler..." profiler-start -- 2.39.5