[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[nongnu] elpa/typescript-mode 5350c45aec 104/222: Add function to conver
From: |
ELPA Syncer |
Subject: |
[nongnu] elpa/typescript-mode 5350c45aec 104/222: Add function to convert plain strings to template strings. |
Date: |
Sun, 6 Feb 2022 16:59:22 -0500 (EST) |
branch: elpa/typescript-mode
commit 5350c45aec10bcdffe16ec9273d04298e576f1e1
Author: Louis-Dominique Dubeau <ldd@lddubeau.com>
Commit: Louis-Dominique Dubeau <ldd@lddubeau.com>
Add function to convert plain strings to template strings.
---
typescript-mode-tests.el | 191 ++++++++++++++++++++++++++++++++++++++++++++---
typescript-mode.el | 111 ++++++++++++++++++++++++++-
2 files changed, 288 insertions(+), 14 deletions(-)
diff --git a/typescript-mode-tests.el b/typescript-mode-tests.el
index 795555f27d..d82956a7eb 100644
--- a/typescript-mode-tests.el
+++ b/typescript-mode-tests.el
@@ -8,6 +8,7 @@
(require 'ert)
(require 'typescript-mode)
+(require 'cl)
(defun typescript-test-get-doc ()
(buffer-substring-no-properties (point-min) (point-max)))
@@ -211,8 +212,18 @@ a severity set to WARNING, no rule name."
`(with-temp-buffer
(insert ,content)
(typescript-mode)
- (font-lock-fontify-buffer)
(goto-char (point-min))
+ ;; We need this so that tests that simulate user actions operate on the
right buffer.
+ (switch-to-buffer (current-buffer))
+ ,@body))
+
+(defmacro test-with-fontified-buffer (content &rest body)
+ "Fill a temporary buffer with `CONTENT' and eval `BODY' in it."
+ (declare (debug t)
+ (indent 1))
+ `(test-with-temp-buffer
+ ,content
+ (font-lock-fontify-buffer)
,@body))
(defun get-face-at (loc)
@@ -234,7 +245,7 @@ put in the temporary buffer. `EXPECTED' is the expected
results. It should be a list of (LOCATION . FACE) pairs, where
LOCATION can be either a single location, or list of locations,
that are all expected to have the same face."
- (test-with-temp-buffer
+ (test-with-fontified-buffer
contents
;; Make sure our propertize function has been applied to the whole
;; buffer.
@@ -263,7 +274,7 @@ documentation."
(ert-deftest font-lock/no-documentation-in-non-documentation-comments ()
"Documentation tags that are not in documentation comments
should not be fontified as documentation."
- (test-with-temp-buffer
+ (test-with-fontified-buffer
(concat "/*\n" font-lock-contents "\n*/\n")
(let ((loc 3))
;; Make sure we start with the right face.
@@ -274,7 +285,7 @@ should not be fontified as documentation."
(ert-deftest font-lock/no-documentation-in-strings ()
"Documentation tags that are not in strings should not be
fontified as documentation."
- (test-with-temp-buffer
+ (test-with-fontified-buffer
(concat "const x = \"/**" font-lock-contents "*/\";")
(let ((loc (search-forward "\"")))
;; Make sure we start with the right face.
@@ -374,16 +385,16 @@ declare function declareFunctionDefn(x3: xty3, y3: yty3):
ret3;"
(ert-deftest
font-lock/text-after-trailing-regexp-delim-should-not-be-fontified ()
"Text after trailing regular expression delimiter should not be fontified."
- (test-with-temp-buffer
+ (test-with-fontified-buffer
"=/foo/g something // comment"
(should (eq (get-face-at "g something") nil)))
- (test-with-temp-buffer
+ (test-with-fontified-buffer
"=/foo\\bar/g something // comment"
(should (eq (get-face-at "g something") nil)))
- (test-with-temp-buffer
+ (test-with-fontified-buffer
"=/foo\\\\bar/g something // comment"
(should (eq (get-face-at "g something") nil)))
- (test-with-temp-buffer
+ (test-with-fontified-buffer
"=/foo\\\\/g something // comment"
(should (eq (get-face-at "g something") nil))))
@@ -412,23 +423,181 @@ import... from...."
;; to avoid hitting keywords. Moreover, the end position of the search is
important.
;; Flyspell puts point at the end of the word before calling the
predicate. We must
;; replicate that behavior here.
- (test-with-temp-buffer
+ (test-with-fontified-buffer
"import 'a';\nimport { x } from 'b';\nconst foo = 'c';import { x }\nfrom
'd';"
(should (not (flyspell-predicate-test "'a")))
(should (not (flyspell-predicate-test "'b")))
(should (flyspell-predicate-test "'c"))
(should (not (flyspell-predicate-test "'d"))))
- (test-with-temp-buffer
+ (test-with-fontified-buffer
;; This is valid TypeScript.
"const from = 'a';"
(should (flyspell-predicate-test "'a")))
- (test-with-temp-buffer
+ (test-with-fontified-buffer
;; TypeScript does not allow a function named "import" but object
;; members may be named "import". So this *can* be valid
;; TypeScript.
"x.import('a');"
(should (flyspell-predicate-test "'a")))))
+
+(ert-deftest typescript--move-to-end-of-plain-string ()
+ "Unit tests for `typescript--move-to-end-of-plain-string'."
+ (cl-flet
+ ((should-fail ()
+ (let ((point-before (point)))
+ (should (not (typescript--move-to-end-of-plain-string)))
+ (should (eq (point) point-before))))
+ (should-not-fail (expected)
+ (let ((result
(typescript--move-to-end-of-plain-string)))
+ (should (eq result expected))
+ (should (eq (point) expected)))))
+ ;;
+ ;; The tests below are structured as follows. For each case:
+ ;;
+ ;; 1. Move point to a new location in the buffer.
+ ;;
+ ;; 2. Check whether typescript--move-to-end-of-plain-string returns the
value we expected
+ ;; and changes (point) when successful.
+ ;;
+ ;; Cases often start with a check right away: (point) equal to
+ ;; (point-min) for those cases.
+ ;;
+ (dolist (delimiter '("'" "\""))
+ (test-with-temp-buffer
+ (replace-regexp-in-string "'" delimiter "const a = 'not terminated")
+ (should-fail)
+ (re-search-forward delimiter)
+ (should-fail))
+ (test-with-temp-buffer
+ (replace-regexp-in-string "'" delimiter "const a = 'terminated'")
+ (should-fail)
+ ;; This checks that the function works when invoked on the start
delimiter of
+ ;; a terminated string.
+ (re-search-forward delimiter)
+ (should-not-fail (1- (point-max)))
+ (goto-char (point-min))
+ (re-search-forward "term")
+ (should-not-fail (1- (point-max)))
+ ;; This checks that the function works when invoked on the end
delimiter of
+ ;; a terminated string.
+ (goto-char (1- (point-max)))
+ (should-not-fail (1- (point-max))))
+ (test-with-temp-buffer
+ (replace-regexp-in-string "'" delimiter "const a = 'terminated aaa';\n
+const b = 'not terminated bbb")
+ (should-fail)
+ (re-search-forward "term")
+ (should-not-fail (save-excursion (re-search-forward "aaa")))
+ (re-search-forward "const b")
+ (should-fail)
+ (re-search-forward "not terminated")
+ (should-fail))
+ ;; Case with escaped delimiter.
+ (test-with-temp-buffer
+ (replace-regexp-in-string "'" delimiter "const a = 'terminat\\'ed
aaa';\n
+ const b = 'not terminated bbb")
+ (re-search-forward "term")
+ (should-not-fail (save-excursion (re-search-forward "aaa"))))
+ ;; Delimiters in comments.
+ (test-with-temp-buffer
+ (replace-regexp-in-string "'" delimiter "const a = 'terminated aaa';\n
+// Comment 'or'\n
+const b = 'not terminated bbb")
+ (re-search-forward "term")
+ (should-not-fail (save-excursion (re-search-forward "aaa")))
+ (re-search-forward "Comment ")
+ (should-fail)
+ (forward-char)
+ (should-fail)
+ (re-search-forward "or")
+ (should-fail)))
+ ;; Ignores template strings.
+ (test-with-temp-buffer
+ "const a = `terminated aaa`"
+ (re-search-forward "term")
+ (should-fail))))
+
+(ert-deftest typescript-convert-to-template ()
+ "Unit tests for `typescript-convert-to-template'."
+ (cl-flet
+ ((should-do-nothing (str regexp)
+ (test-with-temp-buffer
+ str
+ (re-search-forward regexp)
+ (typescript-convert-to-template)
+ (should (string-equal (typescript-test-get-doc) str))))
+ (should-modify (str delimiter regexp)
+ (test-with-temp-buffer
+ str
+ (re-search-forward regexp)
+ (typescript-convert-to-template)
+ (should (string-equal (typescript-test-get-doc)
+ (replace-regexp-in-string delimiter
"`" str))))))
+ (dolist (delimiter '("'" "\""))
+ (let ((str (replace-regexp-in-string "'" delimiter "const a = 'not
terminated")))
+ (dolist (move-to '("const" "not"))
+ (should-do-nothing str move-to)))
+ (let ((str (replace-regexp-in-string "'" delimiter "const a =
'terminated'")))
+ (should-do-nothing str "const")
+ (should-modify str delimiter delimiter)
+ (should-modify str delimiter "term")
+ (should-modify str delimiter "terminated"))
+ ;; Delimiters in comments.
+ (let ((str (replace-regexp-in-string "'" delimiter "const a =
'terminated aaa';\n
+// Comment 'or'\n
+const b = 'not terminated bbb")))
+ (should-do-nothing str "Comment ")))
+ ;; Ignores template strings.
+ (let ((str "const a = `terminated aaa`"))
+ (should-do-nothing str "terminated"))))
+
+(ert-deftest typescript-autoconvert-to-template ()
+ "Unit tests for `typescript-autoconvert-to-template'."
+ (cl-flet
+ ((should-do-nothing (str regexp)
+ (test-with-temp-buffer
+ str
+ (re-search-forward regexp)
+ (typescript-autoconvert-to-template)
+ (should (string-equal (typescript-test-get-doc) str))))
+ (should-modify (str delimiter regexp)
+ (test-with-temp-buffer
+ str
+ (re-search-forward regexp)
+ (typescript-autoconvert-to-template)
+ (should (string-equal (typescript-test-get-doc)
+ (replace-regexp-in-string delimiter
"`" str))))))
+ (dolist (delimiter '("'" "\""))
+ (let ((str (replace-regexp-in-string "'" delimiter "const a =
'terminated'")))
+ (should-do-nothing str "= ")
+ (should-do-nothing str "terminated"))
+ (let ((str (replace-regexp-in-string "'" delimiter "const a =
'${foo}'")))
+ (should-do-nothing str "= ")
+ (should-modify str delimiter (concat "foo}" delimiter))))))
+
+(ert-deftest typescript-autoconvert-to-template-is-invoked ()
+ "Test that we call `typescript-autoconvert-to-template' as needed."
+ (cl-flet
+ ((should-do-nothing (str delimiter)
+ (test-with-temp-buffer
+ str
+ (goto-char (point-max))
+ (execute-kbd-macro delimiter)
+ (should (string-equal (typescript-test-get-doc) (concat
str delimiter)))))
+ (should-modify (str delimiter)
+ (test-with-temp-buffer
+ str
+ (goto-char (point-max))
+ (execute-kbd-macro delimiter)
+ (should (string-equal (typescript-test-get-doc)
+ (replace-regexp-in-string delimiter
"`" (concat str delimiter)))))))
+ (dolist (delimiter '("'" "\""))
+ (let ((str (replace-regexp-in-string "'" delimiter "const a = '${foo}")))
+ (should-do-nothing str delimiter)
+ (let ((typescript-autoconvert-to-template-flag t))
+ (should-modify str delimiter))))))
+
(provide 'typescript-mode-tests)
;;; typescript-mode-tests.el ends here
diff --git a/typescript-mode.el b/typescript-mode.el
index 93da60b2c1..6d129e9a3b 100644
--- a/typescript-mode.el
+++ b/typescript-mode.el
@@ -631,13 +631,72 @@ seldom use, either globally or on a per-buffer basis."
:type 'hook
:group 'typescript)
+(defcustom typescript-autoconvert-to-template-flag nil
+ "Non-nil means automatically convert plain strings to templates.
+
+When the flag is non-nil the `typescript-autoconvert-to-template'
+is called whenever a plain string delimiter is typed in the buffer."
+ :type 'boolean
+ :group 'typescript)
+
+;;; Public utilities
+
+(defun typescript-convert-to-template ()
+ "Convert the string at point to a template string."
+ (interactive)
+ (save-restriction
+ (widen)
+ (save-excursion
+ (let* ((syntax (syntax-ppss))
+ (str-terminator (nth 3 syntax))
+ (string-start (or (and str-terminator (nth 8 syntax))
+ ;; We have to consider the case that we're on
the start delimiter of a string.
+ ;; We tentatively take (point) as string-start.
If it turns out we're
+ ;; wrong, then
typescript--move-to-end-of-plain-string will fail anway,
+ ;; and we won't use the bogus value.
+ (progn
+ (forward-char)
+ (point)))))
+ (when (typescript--move-to-end-of-plain-string)
+ (let ((end-start (or (nth 8 (syntax-ppss)) -1)))
+ (undo-boundary)
+ (when (= end-start string-start)
+ (delete-char 1)
+ (insert "`")))
+ (goto-char string-start)
+ (delete-char 1)
+ (insert "`"))))))
+
+(defun typescript-autoconvert-to-template ()
+ "Automatically convert a plain string to a teplate string, if needed.
+
+This function is meant to be automatically invoked when the user
+enters plain string delimiters. It checks whether the character
+before point is the end of a string. If it is, then it checks
+whether the string contains ${...}. If it does, then it converts
+the string from a plain string to a template."
+ (interactive)
+ (save-restriction
+ (widen)
+ (save-excursion
+ (backward-char)
+ (when (and (memq (char-after) '(?' ?\"))
+ (not (eq (char-before) ?\\)))
+ (let* ((string-start (nth 8 (syntax-ppss))))
+ (when (and string-start
+ (save-excursion
+ (re-search-backward "\\${.*?}" string-start t)))
+ (typescript-convert-to-template)))))))
+
;;; KeyMap
(defvar typescript-mode-map
(let ((keymap (make-sparse-keymap)))
- (mapc (lambda (key)
- (define-key keymap key #'typescript-insert-and-indent))
- '("{" "}" "(" ")" ":" ";" ","))
+ (dolist (key '("{" "}" "(" ")" ":" ";" ","))
+ (define-key keymap key #'typescript-insert-and-indent))
+ (dolist (key '("\"" "\'"))
+ (define-key keymap key #'typescript-insert-and-autoconvert-to-template))
+ (define-key keymap (kbd "C-c '") #'typescript-convert-to-template)
keymap)
"Keymap for `typescript-mode'.")
@@ -655,6 +714,12 @@ comment."
(1+ (current-indentation)))))
(indent-according-to-mode))))
+(defun typescript-insert-and-autoconvert-to-template (key)
+ "Run the command bount to KEY, and autoconvert to template if necessary."
+ (interactive (list (this-command-keys)))
+ (call-interactively (lookup-key (current-global-map) key))
+ (when typescript-autoconvert-to-template-flag
+ (typescript-autoconvert-to-template)))
;;; Syntax table and parsing
@@ -1490,6 +1555,46 @@ LIMIT defaults to point."
(when pitem
(goto-char (typescript--pitem-h-begin pitem )))))
+(defun typescript--move-to-end-of-plain-string ()
+ "If the point is in a plain string, move to the end of it.
+
+Otherwise, don't move. A plain string is a string which is not a
+template string. The point is considered to be \"in\" a string if
+it is on the delimiters of the string, or any point inside.
+
+Returns point if the end of the string was found, or nil if the
+end of the string was not found."
+ (let ((end-position
+ (save-excursion
+ (let* ((syntax (syntax-ppss))
+ (str-terminator (nth 3 syntax))
+ ;; The 8th element will also be set if we are in a comment.
So we
+ ;; check str-terminator to protect against that.
+ (string-start (and str-terminator
+ (nth 8 syntax))))
+ (if (and string-start
+ (not (eq str-terminator ?`)))
+ ;; We may already be at the end of the string.
+ (if (and (eq (char-after) str-terminator)
+ (not (eq (char-before) ?\\)))
+ (point)
+ ;; We just search forward and then check if the hit we get
has a
+ ;; string-start equal to ours.
+ (loop while (re-search-forward
+ (concat "\\(?:[^\\]\\|^\\)\\(" (string
str-terminator) "\\)")
+ nil t)
+ if (eq string-start
+ (save-excursion (nth 8 (syntax-ppss
(match-beginning 1)))))
+ return (match-beginning 1)))
+ ;; If we are on the start delimiter then the value of
syntax-ppss will look
+ ;; like we're not in a string at all, but this function
considers the
+ ;; start delimiter to be "in" the string. We take care of this
here.
+ (when (memq (char-after) '(?' ?\"))
+ (forward-char)
+ (typescript--move-to-end-of-plain-string)))))))
+ (when end-position
+ (goto-char end-position))))
+
;;; Font Lock
(defun typescript--make-framework-matcher (framework &rest regexps)
"Helper function for building `typescript--font-lock-keywords'.
- [nongnu] elpa/typescript-mode d29f31c791 045/222: Fix indentation of methods that are generators., (continued)
- [nongnu] elpa/typescript-mode d29f31c791 045/222: Fix indentation of methods that are generators., ELPA Syncer, 2022/02/06
- [nongnu] elpa/typescript-mode e780858a86 050/222: Don't interpret spread syntax as a continuation., ELPA Syncer, 2022/02/06
- [nongnu] elpa/typescript-mode f3140123d6 067/222: Remove a leftover comment., ELPA Syncer, 2022/02/06
- [nongnu] elpa/typescript-mode aae70b59e6 023/222: Merge pull request #16 from wingyplus/add-as-and-from-keywords, ELPA Syncer, 2022/02/06
- [nongnu] elpa/typescript-mode 3766e37f24 026/222: Add README, ELPA Syncer, 2022/02/06
- [nongnu] elpa/typescript-mode f6907e8ae3 031/222: Fix error in compilation-mode alist registration., ELPA Syncer, 2022/02/06
- [nongnu] elpa/typescript-mode 316b018d27 040/222: Add CI-config and test-runner., ELPA Syncer, 2022/02/06
- [nongnu] elpa/typescript-mode ab6d105807 076/222: Add regression tests for function name highlighting., ELPA Syncer, 2022/02/06
- [nongnu] elpa/typescript-mode 2c43dd034c 068/222: Merge pull request #46 from ananthakumaran/fix/union-type-in-return-annotation, ELPA Syncer, 2022/02/06
- [nongnu] elpa/typescript-mode 3361adad67 093/222: Prevent messing up comment fontification when filling paragraphs., ELPA Syncer, 2022/02/06
- [nongnu] elpa/typescript-mode 5350c45aec 104/222: Add function to convert plain strings to template strings.,
ELPA Syncer <=
- [nongnu] elpa/typescript-mode 7a5c74d88e 109/222: Merge pull request #72 from Wilfred/remove_cpp, ELPA Syncer, 2022/02/06
- [nongnu] elpa/typescript-mode c2d72b7d81 116/222: Fix unwanted indentation after non-keyword "each", ELPA Syncer, 2022/02/06
- [nongnu] elpa/typescript-mode 0d32c85bfb 110/222: Drop typescript-parent-mode., ELPA Syncer, 2022/02/06
- [nongnu] elpa/typescript-mode 5b6d60a2b2 118/222: Fix indentation after keyword-lookalike methods., ELPA Syncer, 2022/02/06
- [nongnu] elpa/typescript-mode 6a6c63ad5e 126/222: Extract test utilities, ELPA Syncer, 2022/02/06
- [nongnu] elpa/typescript-mode 72ec0f93aa 125/222: Merge pull request #90 from jack-williams/add-never, ELPA Syncer, 2022/02/06
- [nongnu] elpa/typescript-mode 69607202bc 139/222: Make GPL license discoverable by Github., ELPA Syncer, 2022/02/06
- [nongnu] elpa/typescript-mode f20103a448 154/222: Add `delete-selection` hints, ELPA Syncer, 2022/02/06
- [nongnu] elpa/typescript-mode fe63c579b5 158/222: Merge pull request #120 from emacs-typescript/tsx-tests, ELPA Syncer, 2022/02/06
- [nongnu] elpa/typescript-mode bd15f212a7 165/222: Implement support type-highlighting in some basic declarations, ELPA Syncer, 2022/02/06