Personal emacs config
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

331 lines
11 KiB

  1. ;;; js2r-vars.el --- Variable declaration manipulation functions for js2-refactor -*- lexical-binding: t; -*-
  2. ;; Copyright (C) 2012-2014 Magnar Sveen
  3. ;; Copyright (C) 2015-2016 Magnar Sveen and Nicolas Petton
  4. ;; Author: Magnar Sveen <magnars@gmail.com>,
  5. ;; Nicolas Petton <nicolas@petton.fr>
  6. ;; Keywords: conveniences
  7. ;; This program is free software; you can redistribute it and/or modify
  8. ;; it under the terms of the GNU General Public License as published by
  9. ;; the Free Software Foundation, either version 3 of the License, or
  10. ;; (at your option) any later version.
  11. ;; This program is distributed in the hope that it will be useful,
  12. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. ;; GNU General Public License for more details.
  15. ;; You should have received a copy of the GNU General Public License
  16. ;; along with this program. If not, see <http://www.gnu.org/licenses/>.
  17. ;;; Code:
  18. (require 'multiple-cursors-core)
  19. (require 'dash)
  20. ;; Helpers
  21. (defun js2r--name-node-at-point (&optional pos)
  22. (setq pos (or pos (point)))
  23. (let ((current-node (js2-node-at-point pos)))
  24. (unless (js2-name-node-p current-node)
  25. (setq current-node (js2-node-at-point (- (point) 1))))
  26. (if (not (and current-node (js2-name-node-p current-node)))
  27. (error "Point is not on an identifier.")
  28. current-node)))
  29. (defun js2r--local-name-node-at-point (&optional pos)
  30. (setq pos (or pos (point)))
  31. (let ((current-node (js2r--name-node-at-point pos)))
  32. (unless (js2r--local-name-node-p current-node)
  33. (error "Point is not on a local identifier"))
  34. current-node))
  35. (defun js2r--local-name-node-p (node)
  36. (let ((parent (js2-node-parent node)))
  37. (and parent (js2-name-node-p node)
  38. (not (and (js2-object-prop-node-p parent)
  39. (eq node (js2-object-prop-node-left parent))))
  40. (not (and (js2-prop-get-node-p parent)
  41. (eq node (js2-prop-get-node-right parent)))))))
  42. (defun js2r--name-node-defining-scope (name-node)
  43. (unless (js2r--local-name-node-p name-node)
  44. (error "Node is not on a local identifier"))
  45. (js2-get-defining-scope
  46. (js2-node-get-enclosing-scope name-node)
  47. (js2-name-node-name name-node)))
  48. (defun js2r--local-usages-of-name-node (name-node)
  49. (unless (js2r--local-name-node-p name-node)
  50. (error "Node is not on a local identifier"))
  51. (let* ((name (js2-name-node-name name-node))
  52. (scope (js2r--name-node-defining-scope name-node))
  53. (result nil))
  54. (js2-visit-ast
  55. scope
  56. (lambda (node end-p)
  57. (when (and (not end-p)
  58. (js2r--local-name-node-p node)
  59. (string= name (js2-name-node-name node))
  60. (eq scope (js2r--name-node-defining-scope node)))
  61. (add-to-list 'result node))
  62. t))
  63. result))
  64. (defun js2r--local-var-positions (name-node)
  65. (-map 'js2-node-abs-pos (js2r--local-usages-of-name-node name-node)))
  66. (defun js2r--var-defining-node (var-node)
  67. (unless (js2r--local-name-node-p var-node)
  68. (error "Node is not on a local identifier"))
  69. (let* ((name (js2-name-node-name var-node))
  70. (scope (js2r--name-node-defining-scope var-node)))
  71. (js2-symbol-ast-node
  72. (js2-scope-get-symbol scope name))))
  73. ;; Add to jslint globals annotation
  74. (defun current-line-contents ()
  75. "Find the contents of the current line, minus indentation."
  76. (buffer-substring (save-excursion (back-to-indentation) (point))
  77. (save-excursion (end-of-line) (point))))
  78. (require 'thingatpt)
  79. (defun js2r-add-to-globals-annotation ()
  80. (interactive)
  81. (let ((var (thing-at-point 'symbol)))
  82. (save-excursion
  83. (beginning-of-buffer)
  84. (when (not (string-match "^/\\* *global " (current-line-contents)))
  85. (newline)
  86. (forward-line -1)
  87. (insert "/* global */")
  88. (newline)
  89. (forward-line -1))
  90. (while (not (string-match "*/" (current-line-contents)))
  91. (forward-line))
  92. (end-of-line)
  93. (delete-char -2)
  94. (unless (looking-back "global ")
  95. (while (looking-back " ")
  96. (delete-char -1))
  97. (insert ", "))
  98. (insert (concat var " */")))))
  99. ;; Rename variable
  100. (defun js2r-rename-var ()
  101. "Renames the variable on point and all occurrences in its lexical scope."
  102. (interactive)
  103. (js2r--guard)
  104. (let* ((current-node (js2r--local-name-node-at-point))
  105. (len (js2-node-len current-node))
  106. (current-start (js2-node-abs-pos current-node))
  107. (current-end (+ current-start len)))
  108. (save-excursion
  109. (mapc (lambda (beg)
  110. (when (not (= beg current-start))
  111. (goto-char beg)
  112. (set-mark (+ beg len))
  113. (mc/create-fake-cursor-at-point)))
  114. (js2r--local-var-positions current-node)))
  115. (push-mark current-end)
  116. (goto-char current-start)
  117. (activate-mark))
  118. (mc/maybe-multiple-cursors-mode))
  119. (add-to-list 'mc--default-cmds-to-run-once 'js2r-rename-var)
  120. ;; Change local variable to use this. instead
  121. (defun js2r-var-to-this ()
  122. "Changes the variable on point to use this.var instead."
  123. (interactive)
  124. (js2r--guard)
  125. (save-excursion
  126. (let ((node (js2-node-at-point)))
  127. (when (js2-var-decl-node-p node)
  128. (let ((kids (js2-var-decl-node-kids node)))
  129. (when (cdr kids)
  130. (error "Currently does not support converting multivar statements."))
  131. (goto-char (js2-node-abs-pos (car kids))))))
  132. (--each (js2r--local-var-positions (js2r--local-name-node-at-point))
  133. (goto-char it)
  134. (cond ((looking-back "var ") (delete-char -4))
  135. ((looking-back "let ") (delete-char -4))
  136. ((looking-back "const ") (delete-char -6)))
  137. (insert "this."))))
  138. ;; Inline var
  139. (defun js2r-inline-var ()
  140. (interactive)
  141. (js2r--guard)
  142. (save-excursion
  143. (let* ((current-node (js2r--local-name-node-at-point))
  144. (definer (js2r--var-defining-node current-node))
  145. (definer-start (js2-node-abs-pos definer))
  146. (var-init (js2-node-parent definer))
  147. (initializer (js2-var-init-node-initializer
  148. var-init)))
  149. (unless initializer
  150. (error "Var is not initialized when defined."))
  151. (let* ((var-len (js2-node-len current-node))
  152. (init-beg (js2-node-abs-pos initializer))
  153. (init-end (+ init-beg (js2-node-len initializer)))
  154. (var-init-beg (copy-marker (js2-node-abs-pos var-init)))
  155. (var-init-end (copy-marker (+ var-init-beg (js2-node-len var-init))))
  156. (contents (buffer-substring init-beg init-end)))
  157. (mapc (lambda (beg)
  158. (when (not (= beg definer-start))
  159. (goto-char beg)
  160. (delete-char var-len)
  161. (insert contents)))
  162. (js2r--local-var-positions current-node))
  163. (js2r--delete-var-init var-init-beg var-init-end)))))
  164. (defun js2r--was-single-var ()
  165. (save-excursion
  166. (goto-char (point-at-bol))
  167. (or (looking-at "^[[:space:]]*\\(var\\|const\\|let\\)[[:space:]]?;?$")
  168. (looking-at "^[[:space:]]*,[[:space:]]*$"))))
  169. (defun js2r--was-starting-var ()
  170. (or (looking-back "var ")
  171. (looking-back "const ")
  172. (looking-back "let ")))
  173. (defun js2r--was-ending-var ()
  174. (looking-at ";"))
  175. (defun js2r--delete-var-init (beg end)
  176. (goto-char beg)
  177. (delete-char (- end beg))
  178. (cond
  179. ((js2r--was-single-var)
  180. (delete-region (point-at-bol) (point-at-eol))
  181. (delete-blank-lines))
  182. ((js2r--was-starting-var)
  183. (delete-char 1)
  184. (if (looking-at " ")
  185. (delete-char 1)
  186. (join-line -1)))
  187. ((js2r--was-ending-var)
  188. (if (looking-back ", ")
  189. (delete-char -1)
  190. (join-line)
  191. (delete-char 1))
  192. (delete-char -1))
  193. (t (delete-char 2))))
  194. ;; two cases
  195. ;; - it's the only var -> remove the line
  196. ;; - there are several vars -> remove the node then clean up commas
  197. ;; Extract variable
  198. (defun js2r--start-of-parent-stmt ()
  199. (js2-node-abs-pos (js2r--closest-stmt-node)))
  200. (defun js2r--object-literal-key-behind (pos)
  201. (save-excursion
  202. (goto-char pos)
  203. (when (looking-back "\\sw: ?")
  204. (backward-char 2)
  205. (js2-name-node-name (js2r--name-node-at-point)))))
  206. (defun js2r--line-above-is-blank ()
  207. (save-excursion
  208. (forward-line -1)
  209. (string= "" (current-line-contents))))
  210. ;;;###autoload
  211. (defun js2r-extract-var ()
  212. (interactive)
  213. (js2r--guard)
  214. (if (use-region-p)
  215. (js2r--extract-var-between (region-beginning) (region-end))
  216. (let ((node (js2r--closest-extractable-node)))
  217. (js2r--extract-var-between (js2-node-abs-pos node)
  218. (js2-node-abs-end node)))))
  219. (add-to-list 'mc--default-cmds-to-run-once 'js2r-extract-var)
  220. (defun js2r--extract-var-between (beg end)
  221. (interactive "r")
  222. (unless (js2r--single-complete-expression-between-p beg end)
  223. (error "Can only extract single, complete expressions to var"))
  224. (let ((deactivate-mark nil)
  225. (expression (buffer-substring beg end))
  226. (orig-var-end (make-marker))
  227. (new-var-end (make-marker))
  228. (name (or (js2r--object-literal-key-behind beg) "name")))
  229. (delete-region beg end)
  230. (insert name)
  231. (set-marker orig-var-end (point))
  232. (goto-char (js2r--start-of-parent-stmt))
  233. (js2r--insert-var name)
  234. (set-marker new-var-end (point))
  235. (insert " = " expression ";")
  236. (when (or (js2r--line-above-is-blank)
  237. (string-match-p "^function " expression))
  238. (newline))
  239. (newline)
  240. (indent-region new-var-end orig-var-end)
  241. (save-excursion
  242. (goto-char new-var-end)
  243. (set-mark (- (point) (length name)))
  244. (mc/create-fake-cursor-at-point))
  245. (goto-char orig-var-end)
  246. (set-mark (- (point) (length name)))
  247. (set-marker orig-var-end nil)
  248. (set-marker new-var-end nil))
  249. (mc/maybe-multiple-cursors-mode))
  250. (defun js2r--insert-var (name)
  251. "Insert a var definition for NAME."
  252. (let ((keyword (if js2r-prefer-let-over-var
  253. "let"
  254. "var")))
  255. (insert (format "%s %s" keyword name))))
  256. (defun js2r-split-var-declaration ()
  257. "Split a variable declaration into separate variable
  258. declarations for each declared variable."
  259. (interactive)
  260. (js2r--guard)
  261. (save-excursion
  262. (let* ((declaration (or (js2r--closest #'js2-var-decl-node-p) (error "No var declaration at point.")))
  263. (kids (js2-var-decl-node-kids declaration))
  264. (stmt (js2-node-parent-stmt declaration)))
  265. (goto-char (js2-node-abs-end stmt))
  266. (mapc (lambda (kid)
  267. (js2r--insert-var (js2-node-string kid))
  268. (insert ";")
  269. (newline)
  270. (if (save-excursion
  271. (goto-char (js2-node-abs-end kid))
  272. (looking-at ", *\n *\n"))
  273. (newline)))
  274. kids)
  275. (delete-char -1) ;; delete final newline
  276. (let ((end (point)))
  277. (js2r--goto-and-delete-node stmt)
  278. (indent-region (point) end)))))
  279. (provide 'js2r-vars)
  280. ;;; js2-vars.el ends here