Skip to content

Mark put-clojure-indent safe for use in LocalVariables, .dir-locals.el, etc. #598

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ compile: elpa
$(CASK) build

clean:
rm -f $(OBJS)
rm -f $(OBJS) clojure-mode-autoloads.el

test: $(PKGDIR)
$(CASK) exec buttercup
Expand All @@ -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 $<
78 changes: 63 additions & 15 deletions clojure-mode.el
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@
(require 'cl-lib)
(require 'imenu)
(require 'newcomment)
(require 'thingatpt)
(require 'align)
(require 'subr-x)
(require 'lisp-mnt)
Expand Down Expand Up @@ -479,12 +480,22 @@ 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)
(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
Expand Down Expand Up @@ -1122,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)
Expand Down Expand Up @@ -1554,6 +1565,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
Expand Down Expand Up @@ -1787,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."
Expand Down Expand Up @@ -1899,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'."
Expand Down Expand Up @@ -2494,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)
Expand Down
74 changes: 74 additions & 0 deletions test/clojure-mode-safe-eval-test.el
Original file line number Diff line number Diff line change
@@ -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 <[email protected]>
;; Copyright (C) 2021 Rob Browning <[email protected]>

;; 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 <http://www.gnu.org/licenses/>.

;;; 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