summaryrefslogtreecommitdiff
path: root/elpa/js2-refactor-20210306.2003/js2r-helpers.el
blob: 8f600fec33611b515b17132499132a7deb936b0d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
;;; js2r-helpers.el --- Private helper functions for js2-refactor    -*- lexical-binding: t; -*-

;; Copyright (C) 2012-2014 Magnar Sveen
;; Copyright (C) 2015-2016 Magnar Sveen and Nicolas Petton

;; Author: Magnar Sveen <magnars@gmail.com>,
;;         Nicolas Petton <nicolas@petton.fr>
;; Keywords: conveniences

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

;;; Code:

(require 'dash)
(require 's)
(require 'js2-mode)

(defmacro js2r--wait-for-parse (&rest body)
  "Evaluate BODY once the current buffer has been parsed."
  (declare (debug def-body))
  `(js2-mode-wait-for-parse (lambda () ,@body)))

(defun js2r--wrap-text (&rest text)
  "Wrap TEXT with the prefered quotes.  The prefered quotes is set with `js2r-prefered-quote-type'."
  (let ((prefered-quotes "\""))
    (when (= 2 js2r-prefered-quote-type)
      (setq prefered-quotes "'"))
    (concat prefered-quotes (apply 'concat text) prefered-quotes)))

(defun js2r--fix-special-modifier-combinations (key)
  (case key
    ("C-s-i" "s-TAB")
    ("C-s-m" "s-RET")
    (otherwise key)))

(defun js2r--key-pairs-with-modifier (modifier keys)
  (->> (string-to-list keys)
    (--map (js2r--fix-special-modifier-combinations
            (concat modifier (char-to-string it))))
    (s-join " ")
    (read-kbd-macro)))

(defun js2r--key-pairs-with-prefix (prefix keys)
  (read-kbd-macro (concat prefix " " keys)))

(defun js2r--guard ()
  (when js2-parsed-errors
    (error "Can't refactor while buffer has parse errors")))

(defun js2r--current-quotes-char ()
  "The char that is the current quote delimiter"
  (nth 3 (syntax-ppss)))

(defalias 'js2r--point-inside-string-p 'js2r--current-quotes-char)

(defun js2r--closest-node-where (p node)
  (if (or (null node)
          (apply p node nil))
      node
    (js2r--closest-node-where p (js2-node-parent node))))

(defun js2r--closest (p)
  (save-excursion
    (cond
     ((bolp) (back-to-indentation))
     ((looking-at ";") (forward-char -1))
     ((looking-back ";") (forward-char -2))
     ((looking-back "}") (forward-char -1)))
    (js2r--closest-node-where p (js2-node-at-point))))

(defun js2r--goto-and-delete-node (node)
  (goto-char (js2-node-abs-pos node))
  (delete-char (js2-node-len node)))


(defun js2r--path-to-root (node)
  (when node
    (cons node (js2r--path-to-root (js2-node-parent node)))))

(defun js2r--first-common-ancestor (node1 node2)
  (if (eq node1 node2)
      node1
    (let ((path1 (reverse (js2r--path-to-root node1)))
          (path2 (reverse (js2r--path-to-root node2)))
          (last-common nil))
      (while (eq (car path1) (car path2))
        (setq last-common (car path1))
        (setq path1 (cdr path1))
        (setq path2 (cdr path2)))
      last-common)))

(defun js2r--first-common-ancestor-in-region (beg end)
  (js2r--first-common-ancestor (js2-node-at-point beg)
                               (js2-node-at-point end)))

;; abstract away node type on some common property getters
(defun js2r--node-target (node)
  (cond
   ((js2-call-node-p node) (js2-call-node-target node))
   ((js2-new-node-p node) (js2-new-node-target node))
   (:else nil)))

(defun js2r--node-args (node)
  (cond
   ((js2-call-node-p node) (js2-call-node-args node))
   ((js2-new-node-p node) (js2-new-node-args node))
   (:else nil)))

(defun js2r--node-lp (node)
  (cond
   ((js2-call-node-p node) (js2-call-node-lp node))
   ((js2-new-node-p node) (js2-new-node-lp node))
   (:else nil)))

(defun js2r--node-rp (node)
  (cond
   ((js2-call-node-p node) (js2-call-node-rp node))
   ((js2-new-node-p node) (js2-new-node-rp node))
   (:else nil)))

(defun js2r--node-kids (node)
  (cond
   ((js2-function-node-p node) (js2-block-node-kids (js2-function-node-body node)))
   ((js2-if-node-p node) (js2-scope-kids (js2-if-node-then-part node)))
   ((js2-for-node-p node) (js2-block-node-kids (js2-for-node-body node)))
   ((js2-while-node-p node) (js2-block-node-kids (js2-while-node-body node)))))

;; finding expressions and arguments

(defun js2r--closest-extractable-node ()
  "Return the most appropriate node the be extracted into a variable or paramter.
Lookup the closest expression node from the point, or the closest literal node instead.
If no node is found, signal an error."
  (or (or (js2r--closest #'js2r--expression-p)
          (js2r--closest #'js2r--literal-node-p))
      (error "Cannot perform refactoring: Nothing to extract at point")))

(defun js2r--closest-stmt-node ()
  "Return the closest standalone statement node.
Special care is taken for if branch nodes: if a statement node is
part of an if branch node (like 'else if' nodes), return the
parent node."
  (let* ((node (js2-node-parent-stmt (js2-node-at-point)))
        (parent (js2-node-parent node)))
    (if (and (js2-if-node-p node)
             (js2-if-node-p parent))
        parent
      node)))

(defun js2r--argument-p (node)
  (let ((parent (js2-node-parent node)))
    (and (js2-call-node-p parent)
         (member node (js2-call-node-args parent)))))

(defun js2r--expression-p (node)
  (or (js2-call-node-p node)
      (js2r--argument-p node)
      (and (js2-prop-get-node-p node)
           (not (js2-call-node-p (js2-node-parent node))))))

(defun js2r--literal-node-p (node)
  (or (js2-object-node-p node)
      (js2-string-node-p node)
      (js2-number-node-p node)
      (js2r--boolean-node-p node)))

(defun js2r--boolean-node-p (node)
  (let* ((beg (js2-node-abs-pos node))
         (end (js2-node-abs-end node))
         (content (buffer-substring beg end)))
    (and (js2-keyword-node-p node)
         (member content '("true" "false")))))

(defun js2r--single-complete-expression-between-p (beg end)
  (let ((ancestor (js2r--first-common-ancestor-in-region beg (- end 1))))
    (and (= beg (js2-node-abs-pos ancestor))
         (= end (js2-node-abs-end ancestor)))))


;; executing a list of changes
;; ensures changes are executed from last to first

(defun js2r--by-end-descending (change1 change2)
  (> (plist-get change1 :end)
     (plist-get change2 :end)))

(defun js2r--any-overlapping-changes (sorted-changes)
  (--any?
   (let ((one (car it))
         (two (cadr it)))
     (< (plist-get one :beg)
        (plist-get two :end)))
   (-partition-in-steps 2 1 sorted-changes)))

(defun js2r--execute-changes (changes)
  (when changes
    (let ((sorted-changes (sort changes 'js2r--by-end-descending)))
      (when (js2r--any-overlapping-changes sorted-changes)
        (error "These changes overlap, cannot execute properly."))
      (let ((abs-end (set-marker (make-marker) (1+ (plist-get (car sorted-changes) :end))))
            (abs-beg (plist-get (car (last sorted-changes)) :beg)))
        (--each sorted-changes
          (goto-char (plist-get it :beg))
          (delete-char (- (plist-get it :end) (plist-get it :beg)))
          (insert (plist-get it :contents)))
        (indent-region abs-beg abs-end)
        (set-marker abs-end nil)))))

(provide 'js2r-helpers)
;;; js2-helpers.el ends here