From 2144d56e658ef8777ba73c03523cf4bc786f0683 Mon Sep 17 00:00:00 2001 From: Rob Browning Date: Fri, 2 Jul 2021 13:17:39 -0500 Subject: [PATCH 1/8] Mark put-clojure-indent safe for use in .dir-locals.el, etc. Add a put-clojure-indent form validator and attach it as a safe-local-eval-function property so that safe invocations won't trigger open-file prompts when used in local variables, or when added to .dir-locals.el like this: ((clojure-mode (eval . (put-clojure-indent 'defrecord '(2 :form :form (1)))))) For now, only support specs specified as lists, not vectors. --- clojure-mode.el | 47 ++++++++++++++++++ test/clojure-mode-safe-eval-test.el | 74 +++++++++++++++++++++++++++++ 2 files changed, 121 insertions(+) create mode 100644 test/clojure-mode-safe-eval-test.el diff --git a/clojure-mode.el b/clojure-mode.el index c5697c99..db310086 100644 --- a/clojure-mode.el +++ b/clojure-mode.el @@ -1554,6 +1554,53 @@ This function also returns nil meaning don't specify the indentation." "Instruct `clojure-indent-function' to indent the body of SYM by INDENT." (put sym 'clojure-indent-function indent)) +(defun clojure--maybe-quoted-symbol-p (x) + "Check that X is either a symbol or a quoted symbol like :foo or 'foo." + (or (symbolp x) + (and (listp x) + (= 2 (length x)) + (eq 'quote (car x)) + (symbolp (cadr x))))) + +(defun clojure--valid-unquoted-indent-spec-p (spec) + "Check that the indentation SPEC is valid. +Validate it with respect to +https://docs.cider.mx/cider/indent_spec.html e.g. (2 :form +:form (1)))." + (or (integerp spec) + (memq spec '(:form :defn)) + (and (listp spec) + (not (null spec)) + (or (integerp (car spec)) + (memq (car spec) '(:form :defn))) + (seq-every-p 'clojure--valid-unquoted-indent-spec-p (cdr spec))))) + +(defun clojure--valid-indent-spec-p (spec) + "Check that the indentation SPEC (quoted if a list) is valid. +Validate it with respect to +https://docs.cider.mx/cider/indent_spec.html e.g. (2 :form +:form (1)))." + (or (integerp spec) + (and (keywordp spec) (memq spec '(:form :defn))) + (and (listp spec) + (= 2 (length spec)) + (eq 'quote (car spec)) + (clojure--valid-unquoted-indent-spec-p (cadr spec))))) + +(defun clojure--valid-put-clojure-indent-call-p (exp) + "Check that EXP is a valid `put-clojure-indent' expression. +For example: (put-clojure-indent 'defrecord '(2 :form :form (1))." + (unless (and (listp exp) + (= 3 (length exp)) + (eq 'put-clojure-indent (nth 0 exp)) + (clojure--maybe-quoted-symbol-p (nth 1 exp)) + (clojure--valid-indent-spec-p (nth 2 exp))) + (error "Unrecognized put-clojure-indent call: %s" exp)) + t) + +(put 'put-clojure-indent 'safe-local-eval-function + 'clojure--valid-put-clojure-indent-call-p) + (defmacro define-clojure-indent (&rest kvs) "Call `put-clojure-indent' on a series, KVS." `(progn diff --git a/test/clojure-mode-safe-eval-test.el b/test/clojure-mode-safe-eval-test.el new file mode 100644 index 00000000..39c94edb --- /dev/null +++ b/test/clojure-mode-safe-eval-test.el @@ -0,0 +1,74 @@ +;;; clojure-mode-safe-eval-test.el --- Clojure Mode: safe eval test suite -*- lexical-binding: t; -*- + +;; Copyright (C) 2014-2021 Bozhidar Batsov +;; Copyright (C) 2021 Rob Browning + +;; This file is not part of GNU Emacs. + +;; This program 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. + +;; This program 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 this program. If not, see . + +;;; Commentary: + +;; The safe eval test suite of Clojure Mode + +;;; Code: +(require 'clojure-mode) +(require 'buttercup) + +(describe "put-clojure-indent safe-local-eval-function property" + (it "should be set to clojure--valid-put-clojure-indent-call-p" + (expect (get 'put-clojure-indent 'safe-local-eval-function) + :to-be 'clojure--valid-put-clojure-indent-call-p))) + +(describe "clojure--valid-put-clojure-indent-call-p" + (it "should approve valid forms" + (expect (clojure--valid-put-clojure-indent-call-p + '(put-clojure-indent 'foo 1))) + (expect (clojure--valid-put-clojure-indent-call-p + '(put-clojure-indent 'foo :defn))) + (expect (clojure--valid-put-clojure-indent-call-p + '(put-clojure-indent 'foo :form))) + (expect (clojure--valid-put-clojure-indent-call-p + '(put-clojure-indent 'foo '(1)))) + (expect (clojure--valid-put-clojure-indent-call-p + '(put-clojure-indent 'foo '(:defn)))) + (expect (clojure--valid-put-clojure-indent-call-p + '(put-clojure-indent 'foo '(:form)))) + (expect (clojure--valid-put-clojure-indent-call-p + '(put-clojure-indent 'foo '(1 1)))) + (expect (clojure--valid-put-clojure-indent-call-p + '(put-clojure-indent 'foo '(2 :form :form (1)))))) + (it "should reject invalid forms" + (expect (clojure--valid-put-clojure-indent-call-p + '(put-clojure-indent 1 1)) + :to-throw 'error) + (expect (clojure--valid-put-clojure-indent-call-p + '(put-clojure-indent 'foo :foo)) + :to-throw 'error) + (expect (clojure--valid-put-clojure-indent-call-p + '(put-clojure-indent 'foo (:defn))) + :to-throw 'error) + (expect (clojure--valid-put-clojure-indent-call-p + '(put-clojure-indent 'foo '(:foo))) + :to-throw 'error) + (expect (clojure--valid-put-clojure-indent-call-p + '(put-clojure-indent 'foo '(1 :foo))) + :to-throw 'error) + (expect (clojure--valid-put-clojure-indent-call-p + '(put-clojure-indent 'foo '(1 "foo"))) + :to-throw 'error))) + +(provide 'clojure-mode-safe-eval-test) + +;;; clojure-mode-safe-eval-test.el ends here From c5985a11d349dc7a0ad4e472d502ce29f432ce7d Mon Sep 17 00:00:00 2001 From: Rob Browning Date: Fri, 23 Jul 2021 01:54:14 -0500 Subject: [PATCH 2/8] Add missing thingatpt require --- clojure-mode.el | 1 + 1 file changed, 1 insertion(+) diff --git a/clojure-mode.el b/clojure-mode.el index db310086..2c322128 100644 --- a/clojure-mode.el +++ b/clojure-mode.el @@ -68,6 +68,7 @@ (require 'cl-lib) (require 'imenu) (require 'newcomment) +(require 'thingatpt) (require 'align) (require 'subr-x) (require 'lisp-mnt) From 736d5693cfc3c2ae0e10273929a833596755a1ef Mon Sep 17 00:00:00 2001 From: Rob Browning Date: Fri, 23 Jul 2021 02:05:11 -0500 Subject: [PATCH 3/8] rm clojure-mode-autoloads.el during "make clean" --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 446c3ea8..ff2f7842 100644 --- a/Makefile +++ b/Makefile @@ -26,7 +26,7 @@ compile: elpa $(CASK) build clean: - rm -f $(OBJS) + rm -f $(OBJS) clojure-mode-autoloads.el test: $(PKGDIR) $(CASK) exec buttercup From f6a9b4868c85d6e7b8b0063190cf4cb88a544ca6 Mon Sep 17 00:00:00 2001 From: Rob Browning Date: Fri, 23 Jul 2021 02:06:41 -0500 Subject: [PATCH 4/8] make %.elc-test: rename $ to $< --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index ff2f7842..27503963 100644 --- a/Makefile +++ b/Makefile @@ -39,4 +39,4 @@ test-bytecomp: $(SRCS:.el=.elc-test) %.elc-test: %.el elpa $(CASK) exec $(EMACS) --no-site-file --no-site-lisp --batch \ - -l test/clojure-mode-bytecomp-warnings.el $ + -l test/clojure-mode-bytecomp-warnings.el $< From 00f43c853f9f09d6946a831a69fa03dea9d2199d Mon Sep 17 00:00:00 2001 From: Rob Browning Date: Fri, 23 Jul 2021 02:08:45 -0500 Subject: [PATCH 5/8] Move clojure--let-regexp before first use Otherwise test-bytecomp fails with: In toplevel form: clojure-mode.el:493:25:Error: reference to free variable `clojure--let-regexp' --- clojure-mode.el | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/clojure-mode.el b/clojure-mode.el index 2c322128..c83f8641 100644 --- a/clojure-mode.el +++ b/clojure-mode.el @@ -486,6 +486,16 @@ val} as of Clojure 1.9.") (declare-function paredit-close-curly "ext:paredit" t t) (declare-function paredit-convolute-sexp "ext:paredit") +(defvar clojure--let-regexp + "\(\\(when-let\\|if-let\\|let\\)\\(\\s-*\\|\\[\\)" + "Regexp matching let like expressions, i.e. \"let\", \"when-let\", \"if-let\". + +The first match-group is the let expression. + +The second match-group is the whitespace or the opening square +bracket if no whitespace between the let expression and the +bracket.") + (defun clojure--replace-let-bindings-and-indent (&rest _) "Replace let bindings and indent." (save-excursion @@ -2542,16 +2552,6 @@ See: https://github.com/clojure-emacs/clj-refactor.el/wiki/cljr-cycle-if" ;;; let related stuff -(defvar clojure--let-regexp - "\(\\(when-let\\|if-let\\|let\\)\\(\\s-*\\|\\[\\)" - "Regexp matching let like expressions, i.e. \"let\", \"when-let\", \"if-let\". - -The first match-group is the let expression. - -The second match-group is the whitespace or the opening square -bracket if no whitespace between the let expression and the -bracket.") - (defun clojure--goto-let () "Go to the beginning of the nearest let form." (when (clojure--in-string-p) From c3431e6b6c1684ddc0e0a1521ed63d5b3f38740e Mon Sep 17 00:00:00 2001 From: Rob Browning Date: Fri, 23 Jul 2021 02:11:02 -0500 Subject: [PATCH 6/8] Move clojure-cached-ns before first use Otherwise test-bytecomp fails like this: In toplevel form: clojure-mode.el:1860:23:Error: assignment to free variable `clojure-cached-ns' --- clojure-mode.el | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/clojure-mode.el b/clojure-mode.el index c83f8641..905d6440 100644 --- a/clojure-mode.el +++ b/clojure-mode.el @@ -1845,6 +1845,9 @@ If PATH is nil, use the path to the file backing the current buffer." (goto-char (point-min)) (clojure-insert-ns-form-at-point)) +(defvar-local clojure-cached-ns nil + "A buffer ns cache used to speed up ns-related operations.") + (defun clojure-update-ns () "Update the namespace of the current buffer. Useful if a file has been renamed." @@ -1957,9 +1960,6 @@ the cached value will be updated automatically." :safe #'booleanp :package-version '(clojure-mode . "5.8.0")) -(defvar-local clojure-cached-ns nil - "A buffer ns cache used to speed up ns-related operations.") - (defun clojure--find-ns-in-direction (direction) "Return the nearest namespace in a specific DIRECTION. DIRECTION is `forward' or `backward'." From 5ed44507a19479b5e1d549e740b6a6079c6b6df7 Mon Sep 17 00:00:00 2001 From: Rob Browning Date: Fri, 23 Jul 2021 02:13:28 -0500 Subject: [PATCH 7/8] Fix make-obsolete call for clojure-no-space-after-tag Otherwise test-bytecomp fails like this: In toplevel form: clojure-mode.el:3015:1:Error: the function `clojure-no-space-after-tag' is not known to be defined. --- clojure-mode.el | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clojure-mode.el b/clojure-mode.el index 905d6440..d81fa657 100644 --- a/clojure-mode.el +++ b/clojure-mode.el @@ -480,7 +480,7 @@ This includes #fully.qualified/my-ns[:kw val] and #::my-ns{:kw val} as of Clojure 1.9.") (make-obsolete-variable 'clojure--collection-tag-regexp nil "5.12.0") -(make-obsolete #'clojure-no-space-after-tag #'clojure-space-for-delimiter-p "5.12.0") +(make-obsolete 'clojure-no-space-after-tag 'clojure-space-for-delimiter-p "5.12.0") (declare-function paredit-open-curly "ext:paredit" t t) (declare-function paredit-close-curly "ext:paredit" t t) From 58836df94993a058a80f40c546449a3eea7f50e9 Mon Sep 17 00:00:00 2001 From: Rob Browning Date: Sat, 24 Jul 2021 14:17:46 -0500 Subject: [PATCH 8/8] clojure-align-separator: shorten docstring to satisfy test-bytecomp In toplevel form: clojure-mode.el:1135:1: Error: custom-declare-variable `clojure-align-separator' docstring wider than 80 characters make: *** [Makefile:41: clojure-mode.elc-test] Error 1 --- clojure-mode.el | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clojure-mode.el b/clojure-mode.el index d81fa657..6f642583 100644 --- a/clojure-mode.el +++ b/clojure-mode.el @@ -1133,7 +1133,7 @@ will align the values like this: (defconst clojure--align-separator-newline-regexp "^ *$") (defcustom clojure-align-separator clojure--align-separator-newline-regexp - "The separator that will be passed to `align-region' when performing vertical alignment." + "Separator passed to `align-region' when performing vertical alignment." :package-version '(clojure-mode . "5.10") :type `(choice (const :tag "Make blank lines prevent vertical alignment from happening." ,clojure--align-separator-newline-regexp)