From 8175e9a4dcd80c3bca0ae7dc96b99d129323cc06 Mon Sep 17 00:00:00 2001 From: Ruijie Yu Date: Fri, 7 Apr 2023 16:03:10 +0800 Subject: [PATCH 1/2] Fixed test file, and added regression test cases Fixes bug#653. * lispy-test.el (lispy-simulate-key) (lispy-simulate-keys) (lispy-simulate-expect): Added facilities to assert results after a given set of keypresses. (lispy-read-unsafe-chars): added regression tests for bug#648. --- lispy-test.el | 90 +++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 88 insertions(+), 2 deletions(-) diff --git a/lispy-test.el b/lispy-test.el index 8de14f0f..c8dbdd9f 100644 --- a/lispy-test.el +++ b/lispy-test.el @@ -95,6 +95,92 @@ (with-syntax-table table (split-string str "\\b" t))))) +(defun lispy-simulate-key (key) + "Simulate key press KEY. +This is used rather than `execute-kbd-macro' because apparently +that function somehow fails to run within `ert-deftest'." + (should (numberp key)) + (let ((cmd (keymap-lookup nil (key-description (vector key))))) + (setq last-command-event key) + (call-interactively cmd) + (setq last-command cmd))) + +(defun lispy-simulate-keys (keys) + "Simulate a sequence of KEYS. +See `lispy-simulate-key'." + (seq-do #'lispy-simulate-key keys)) + +(cl-defun lispy-simulate-expect + (keys &key buffer point (mode #'lisp-mode)) + "Simulate key sequence KEYS and check the result. +If KEYS is a sequence of sequence, simulate each element of KEYS +instead. + +MODE is the major mode in effect. + +BUFFER, if non-nil, is the buffer string to match after the keys +are simulated. + +POINT, if non-nil, is the point to match after the keys are +pressed." + (declare (indent 1)) + (if (seqp (seq-first keys)) + (seq-do (lambda (keys) + (lispy-simulate-expect keys + :buffer buffer + :point point + :mode mode)) + keys) + (with-temp-buffer + (funcall mode) + (lispy-mode) + (lispy-simulate-keys keys) + (when buffer + (should (thread-last (buffer-substring-no-properties + (point-min) (point-max)) + (string= buffer)))) + (let ((point (cl-case point + (max (point-max)) + (min (point-min)) + (t point)))) + (when point + (should (= (point) point))))))) + +(ert-deftest lispy-read-unsafe-chars () + "See #648." + ;; Expect: (de|) + ;; Recipe: ( d e + (lispy-simulate-expect '(?\( ?d ?e) + :buffer "(de)" + :point 4) + ;; Expect: (.)| + ;; Recipes: + ;; 1: ( . ) i + ;; 2. ( . SPC C-b C-t ) i + ;; 3. ( . SPC ) i + ;; 4. ( . SPC C-b C-t SPC ) i + (lispy-simulate-expect + '((?\( ?. ?\) ?i) ; format "(.)" + (?\( ?. ? ?\C-b ?\C-t ?\) ?i) ; format "( .)" + (?\( ?. ? ?\) ?i) ; format "(. )" + (?\( ?. ? ?\C-b ?\C-t ? ?\) ?i)) ; format "( . )" + :buffer "(.)" + :point 'max) + ;; Expect: (f .)| + ;; Recipes: + ;; 1. ( f SPC . ) i + ;; 2. ( f SPC . SPC ) i + (lispy-simulate-expect + '((?\( ?f ? ?. ?\) ?i) ; format "(f .)" + (?\( ?f ? ?. ? ?\) ?i)) ; format "(f . )" + :buffer "(f .)" + :point 'max) + ;; Expect: (. f)| + ;; Recipe: ( . SPC f ) i + (lispy-simulate-expect '(?\( ?. ? ?f ?\) ?i) + :buffer "(. f)" + :point 'max)) + (ert-deftest lispy-decode-keysequence () (should (equal (lispy-decode-keysequence "23ab50c") '(23 "a" "b" 50 "c"))) @@ -2388,7 +2474,7 @@ Insert KEY if there's no command." (should (string= (lispy-with "|;;* Intro" "a") ";;* Intro\n;;* |"))) -(ert-deftest lispy-outline-add () +(ert-deftest lispy-outline-add-2 () ; FIXME: duplicate name (should (string= (lispy-with "(quote ~foo|)" "~") "(quote ~~foo|)")) (should (string= (lispy-with "(quote ~~foo|)" "~") @@ -2594,7 +2680,7 @@ Insert KEY if there's no command." (execute-kbd-macro (kbd "aa"))) "(progn (setq type 'norwegian-blue)\n (~setq| plumage-type 'lovely))")))) -(ert-deftest lispy-ace-subword () +(ert-deftest lispy-ace-subword-2 () ; FIXME: duplicate name (should (string= (lispy-with "|(progn (setq type 'norwegian-blue)\n (setq plumage-type 'lovely))" (execute-kbd-macro (kbd "-g"))) "(progn (setq type 'norwegian-blue)\n (setq |plumage~-type 'lovely))")) From 311cfd085e352d6d15d594678c3e30f48046810a Mon Sep 17 00:00:00 2001 From: Ruijie Yu Date: Tue, 4 Apr 2023 17:54:01 +0800 Subject: [PATCH 2/2] Protect against symbols with unsafe chars Fixes bug#648. * lispy.el lispy--symbol-safe-chars: define list of safe chars. (lispy--read): protect against unsafe chars in a symbol. (lispy--replace-regexp-in-code): add predicate to only replace on match data. --- lispy.el | 48 +++++++++++++++++++++++++++++++++--------------- 1 file changed, 33 insertions(+), 15 deletions(-) diff --git a/lispy.el b/lispy.el index 0c5c9720..09e571da 100644 --- a/lispy.el +++ b/lispy.el @@ -7200,13 +7200,21 @@ For example, a `setq' statement is amended with variable name that it uses." (insert char) (lispy--indent-region (point) pt)))) -(defun lispy--replace-regexp-in-code (regexp to-string) +(defun lispy--replace-regexp-in-code + (regexp to-string &optional pred) "Replace text matching REGEXP with TO-STRING in whole buffer. -Ignore the matches in strings and comments." - (goto-char (point-min)) - (while (re-search-forward regexp nil t) - (unless (lispy--in-string-or-comment-p) - (replace-match to-string)))) +Ignore the matches in strings and comments. + +PRED should be a 0-arg predicate with access to the regexp match +data. PRED defaults to `always', and should return non-nil when +a specific match data should be replaced." + (save-excursion + (goto-char (point-min)) + (save-match-data + (while (re-search-forward regexp nil t) + (unless (lispy--in-string-or-comment-p) + (when (funcall (or pred #'always)) + (replace-match to-string))))))) ;;* Utilities: source transformation (defvar lispy--braces-table @@ -7286,6 +7294,18 @@ See https://clojure.org/guides/weird_characters#_character_literal.") (match-string subexp))) t t nil subexp))))) +(defconst lispy--symbol-safe-chars + (seq-concatenate 'list "+-*/:=" + (number-sequence ?a ?z) + (number-sequence ?A ?Z)) + "List of known \"safe\" characters. +Safe characters are those which are suitable for a symbol and +which have no special reader syntax. + +Missing a few safe characters would not be a serious problem. It +would only produce a slightly larger internal representation when +lispy tries to parse a given sexp.") + ;; TODO: Make the read test pass on string with semi-colon (defun lispy--read (str) "Read STR including comments and newlines." @@ -7524,15 +7544,13 @@ See https://clojure.org/guides/weird_characters#_character_literal.") (unless (lispy--in-string-or-comment-p) (replace-match (format "(ly-raw racket-option %s)" (match-string 1))))) - ;; Clojure # in a symbol - (goto-char (point-min)) - (while (re-search-forward "\\_<\\(?:\\sw\\|\\s_\\)+\\_>" nil t) - (unless (lispy--in-string-p) - (when (cl-position ?# (match-string 0)) - (let* ((bnd (lispy--bounds-dwim)) - (str (lispy--string-dwim bnd))) - (delete-region (car bnd) (cdr bnd)) - (insert (format "(ly-raw symbol %S)" str)))))) + ;; Protect symbols containing unsafe characters + (lispy--replace-regexp-in-code + "\\_<\\(?:\\sw\\|\\s_\\)+\\_>" + "(ly-raw symbol \"\\&\")" + (lambda () + (seq-difference + (match-string 0) lispy--symbol-safe-chars))) ;; Clojure (. object method) (goto-char (point-min)) (while (re-search-forward "(\\.[\t\n ]" nil t)