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.

518 lines
19 KiB

  1. ;;; js2r-functions.el --- Function 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 'dash)
  19. (require 'yasnippet)
  20. (defun js2r-localize-parameter ()
  21. "Turn parameter into local var in local function."
  22. (interactive)
  23. (js2r--guard)
  24. (if (js2-name-node-p (js2-node-at-point))
  25. (js2r--localize-parameter-pull)
  26. (js2r--localize-parameter-push)))
  27. (defun js2r--localize-parameter-push ()
  28. (let* ((node (js2-node-at-point))
  29. (arg-node (or (js2r--closest-node-where 'js2r--parent-is-call-node node)
  30. (error "Place cursor on argument to localize")))
  31. (call-node (js2-node-parent arg-node))
  32. (value (js2-node-string arg-node))
  33. (target (js2-call-node-target call-node))
  34. (fn (if (js2-name-node-p target)
  35. (js2r--local-fn-from-name-node target)
  36. (error "Can only localize parameter for local functions")))
  37. (usages (js2r--function-usages fn))
  38. (index (car (--keep (when (eq arg-node it) it-index)
  39. (js2-call-node-args call-node))))
  40. (name (js2-name-node-name (nth index (js2-function-node-params fn)))))
  41. (js2r--localize-parameter fn usages index name value)))
  42. (defun js2r--localize-parameter-pull ()
  43. (let* ((name-node (js2-node-at-point))
  44. (name (if (js2-name-node-p name-node)
  45. (js2-name-node-name name-node)
  46. (error "Place cursor on parameter to localize")))
  47. (fn (or (js2r--closest-node-where #'js2r--is-local-function name-node)
  48. (error "Can only localize parameter in local functions")))
  49. (index (or (js2r--param-index-for name fn)
  50. (error "%S isn't a parameter to this function" name)))
  51. (usages (js2r--function-usages fn))
  52. (examples (-distinct (--map (js2r--argument index it) usages)))
  53. (value (js2r--choose-one "Value: " examples)))
  54. (js2r--localize-parameter fn usages index name value)))
  55. (defun js2r--localize-parameter (fn usages index name value)
  56. (save-excursion
  57. (js2r--goto-fn-body-beg fn)
  58. (save-excursion
  59. (--each usages (js2r--remove-argument-at-index index it)))
  60. (newline-and-indent)
  61. (insert "var " name " = " value ";")
  62. (js2r--remove-parameter-at-index index fn)))
  63. (defun js2r--parent-is-call-node (node)
  64. (js2-call-node-p (js2-node-parent node)))
  65. (defun js2r--local-fn-from-name-node (name-node)
  66. (->> name-node
  67. (js2r--local-usages-of-name-node)
  68. (-map #'js2-node-parent)
  69. (-first #'js2-function-node-p)))
  70. (defun js2r--param-index-for (name fn)
  71. (car (--keep (when (equal name (js2-name-node-name it)) it-index)
  72. (js2-function-node-params fn))))
  73. (defun js2r--argument (index call-node)
  74. (js2-node-string (nth index (js2-call-node-args call-node))))
  75. (defun js2r--remove-parameter-at-index (index fn)
  76. (js2r--delete-node-in-params (nth index (js2-function-node-params fn))))
  77. (defun js2r--remove-argument-at-index (index call-node)
  78. (js2r--delete-node-in-params (nth index (js2-call-node-args call-node))))
  79. (defun js2r--delete-node-in-params (node)
  80. (goto-char (js2-node-abs-pos node))
  81. (delete-char (js2-node-len node))
  82. (if (and (looking-back "(")
  83. (looking-at ", "))
  84. (delete-char 2)
  85. (when (looking-back ", ")
  86. (delete-char -2))))
  87. (defun js2r--choose-one (prompt options)
  88. (when options
  89. (if (cdr options)
  90. (completing-read prompt options)
  91. (car options))))
  92. (defun js2r-introduce-parameter ()
  93. "Introduce a parameter in a local function."
  94. (interactive)
  95. (js2r--guard)
  96. (if (use-region-p)
  97. (js2r--introduce-parameter-between (region-beginning) (region-end))
  98. (let ((node (js2r--closest-extractable-node)))
  99. (js2r--introduce-parameter-between (js2-node-abs-pos node)
  100. (js2-node-abs-end node)))))
  101. (defun js2r--introduce-parameter-between (beg end)
  102. (unless (js2r--single-complete-expression-between-p beg end)
  103. (error "Can only introduce single, complete expressions as parameter"))
  104. (let ((fn (js2r--closest-node-where #'js2r--is-local-function (js2-node-at-point))))
  105. (unless fn
  106. (error "Can only introduce parameter in local functions"))
  107. (save-excursion
  108. (let ((name (read-string "Parameter name: "))
  109. (val (buffer-substring beg end))
  110. (usages (js2r--function-usages fn)))
  111. (goto-char beg)
  112. (save-excursion
  113. (-each usages (-partial #'js2r--add-parameter val)))
  114. (delete-char (- end beg))
  115. (insert name)
  116. (js2r--add-parameter name fn)
  117. (query-replace val name nil (js2-node-abs-pos fn) (js2r--fn-body-end fn))))))
  118. (defun js2r--function-usages (fn)
  119. (-map #'js2-node-parent (js2r--function-usages-name-nodes fn)))
  120. (defun js2r--function-usages-name-nodes (fn)
  121. (let ((name-node (or (js2-function-node-name fn)
  122. (js2-var-init-node-target (js2-node-parent fn)))))
  123. (remove name-node (js2r--local-usages-of-name-node name-node))))
  124. (defun js2r--add-parameter (name node)
  125. (save-excursion
  126. (js2r--goto-closing-paren node)
  127. (unless (looking-back "(")
  128. (insert ", "))
  129. (insert name)))
  130. (defun js2r--goto-closing-paren (node)
  131. (goto-char (js2-node-abs-pos node))
  132. (search-forward "(")
  133. (forward-char -1)
  134. (forward-list)
  135. (forward-char -1))
  136. (defun js2r--goto-fn-body-beg (fn)
  137. (goto-char (js2-node-abs-pos fn))
  138. (search-forward "{"))
  139. (defun js2r--fn-body-end (fn)
  140. (save-excursion
  141. (js2r--goto-fn-body-beg fn)
  142. (forward-char -1)
  143. (forward-list)
  144. (point)))
  145. (defun js2r--is-local-function (node)
  146. (or (js2r--is-var-function-expression node)
  147. (js2r--is-function-declaration node)))
  148. (defun js2r--is-method (node)
  149. (and (js2-function-node-p node)
  150. (js2-object-prop-node-p (js2-node-parent node))))
  151. (defun js2r--is-var-function-expression (node)
  152. (and (js2-function-node-p node)
  153. (js2-var-init-node-p (js2-node-parent node))))
  154. (defun js2r--is-assigned-function-expression (node)
  155. (and (js2-function-node-p node)
  156. (js2-assign-node-p (js2-node-parent node))))
  157. (defun js2r--is-function-declaration (node)
  158. (let ((parent (js2-node-parent node)))
  159. (and (js2-function-node-p node)
  160. (not (js2-assign-node-p parent))
  161. (not (js2-var-init-node-p parent))
  162. (not (js2-object-prop-node-p parent)))))
  163. (defun js2r-arguments-to-object ()
  164. "Change from a list of arguments to a parameter object."
  165. (interactive)
  166. (js2r--guard)
  167. (let ((node (js2-node-at-point)))
  168. (unless (and (looking-at "(")
  169. (or (js2-function-node-p node)
  170. (js2-call-node-p node)
  171. (js2-new-node-p node)))
  172. (error "Place point right before the opening paren in the call or function"))
  173. (-when-let* ((target (js2r--node-target node))
  174. (fn (and (js2-name-node-p target)
  175. (js2r--local-fn-from-name-node target))))
  176. (setq node fn))
  177. (if (js2-function-node-p node)
  178. (js2r--arguments-to-object-for-function node)
  179. (js2r--arguments-to-object-for-args-with-unknown-function (js2r--node-args node)))))
  180. (defun js2r--arguments-to-object-for-function (function-node)
  181. (let ((params (js2-function-node-params function-node)))
  182. (when (null params)
  183. (error "No params to convert"))
  184. (save-excursion
  185. (js2r--execute-changes
  186. (-concat
  187. ;; change parameter list to just (params)
  188. (list
  189. (list :beg (+ (js2-node-abs-pos function-node) (js2-function-node-lp function-node))
  190. :end (+ (js2-node-abs-pos function-node) (js2-function-node-rp function-node) 1)
  191. :contents "(params)"))
  192. ;; add params. in front of function local param usages
  193. (let* ((local-param-name-nodes (--mapcat (-> it
  194. (js2-node-abs-pos)
  195. (js2r--local-name-node-at-point)
  196. (js2r--local-usages-of-name-node))
  197. params))
  198. (local-param-name-usages (--remove (js2-function-node-p (js2-node-parent it))
  199. local-param-name-nodes))
  200. (local-param-name-positions (-map #'js2-node-abs-pos local-param-name-usages)))
  201. (--map
  202. (list :beg it :end it :contents "params.")
  203. local-param-name-positions))
  204. ;; update usages of function
  205. (let ((names (-map #'js2-name-node-name params))
  206. (usages (js2r--function-usages function-node)))
  207. (--map
  208. (js2r--changes/arguments-to-object it names)
  209. usages)))))))
  210. (defun js2r--changes/arguments-to-object (node names)
  211. (let ((args (js2r--node-args node)))
  212. (list :beg (+ (js2-node-abs-pos node) (js2r--node-lp node))
  213. :end (+ (js2-node-abs-pos node) (js2r--node-rp node) 1)
  214. :contents (js2r--create-object-with-arguments names args))))
  215. (defun js2r--arguments-to-object-for-args-with-unknown-function (args)
  216. (when (null args)
  217. (error "No arguments to convert"))
  218. (let ((names (--map-indexed
  219. (format "${%d:%s}"
  220. (1+ it-index)
  221. (if (js2-name-node-p it)
  222. (js2-name-node-name it)
  223. "key"))
  224. args)))
  225. (yas-expand-snippet (js2r--create-object-with-arguments names args)
  226. (point)
  227. (save-excursion (forward-list) (point)))))
  228. (defun js2r--create-object-with-arguments (names args)
  229. (let (arg key result)
  230. (--dotimes (length args)
  231. (setq arg (nth it args))
  232. (setq key (nth it names))
  233. (setq result
  234. (concat result
  235. (format " %s: %s,\n"
  236. key
  237. (buffer-substring (js2-node-abs-pos arg)
  238. (js2-node-abs-end arg))))))
  239. (concat "({\n" (substring result 0 -2) "\n})")))
  240. (defun js2r-extract-function (name)
  241. "Extract a function from the closest statement expression from the point."
  242. (interactive "sName of new function: ")
  243. (js2r--extract-fn
  244. name
  245. (lambda ()
  246. (unless (js2r--looking-at-function-declaration)
  247. (goto-char (js2-node-abs-pos (js2r--closest #'js2-expr-stmt-node-p)))))
  248. "%s(%s);"
  249. "function %s(%s) {\n%s\n}\n\n"))
  250. (defun js2r-extract-method (name)
  251. "Extract a method from the closest statement expression from the point."
  252. (interactive "sName of new method: ")
  253. (js2r--extract-fn
  254. name
  255. (lambda ()
  256. (goto-char (js2-node-abs-pos (js2r--closest #'js2-object-prop-node-p))))
  257. "this.%s(%s);"
  258. "%s: function (%s) {\n%s\n},\n\n"))
  259. (defun js2r--extract-fn (name goto-position call-template function-template)
  260. (js2r--guard)
  261. (unless (use-region-p)
  262. (error "Mark the expressions to extract first"))
  263. (save-excursion
  264. (let* ((parent (js2r--first-common-ancestor-in-region (region-beginning) (region-end)))
  265. (block (js2r--closest-node-where #'js2-block-node-p parent))
  266. (fn (js2r--closest-node-where #'js2-function-node-p block))
  267. (exprs (js2r--marked-expressions-in-block block))
  268. (vars (-mapcat #'js2r--name-node-decendants exprs))
  269. (local (--filter (js2r--local-to-fn-p fn it) vars))
  270. (names (-distinct (-map 'js2-name-node-name local)))
  271. (declared-in-exprs (-map #'js2r--var-init-node-target-name (-mapcat #'js2r--var-init-node-decendants exprs)))
  272. (outside-exprs (-difference (js2-block-node-kids block) exprs))
  273. (outside-var-uses (-map #'js2-name-node-name (-mapcat #'js2r--name-node-decendants outside-exprs)))
  274. (declared-in-but-used-outside (-intersection declared-in-exprs outside-var-uses))
  275. (export-var (car declared-in-but-used-outside))
  276. (params (-difference names declared-in-exprs))
  277. (params-string (mapconcat #'identity (reverse params) ", "))
  278. (first (car exprs))
  279. (last (car (last exprs)))
  280. (beg (js2-node-abs-pos (car exprs)))
  281. (end (js2-node-abs-end last))
  282. (contents (buffer-substring beg end)))
  283. (goto-char beg)
  284. (delete-region beg end)
  285. (when (js2-return-node-p last)
  286. (insert "return "))
  287. (when export-var
  288. (setq contents (concat contents "\nreturn " export-var ";"))
  289. (insert "var " export-var " = "))
  290. (insert (format call-template name params-string))
  291. (goto-char (js2-node-abs-pos fn))
  292. (funcall goto-position)
  293. (let ((start (point)))
  294. (insert (format function-template name params-string contents))
  295. (indent-region start (1+ (point)))))))
  296. (defun js2r--var-init-node-target-name (node)
  297. (js2-name-node-name
  298. (js2-var-init-node-target node)))
  299. (defun js2r--function-around-region ()
  300. (or
  301. (js2r--closest-node-where #'js2-function-node-p
  302. (js2r--first-common-ancestor-in-region
  303. (region-beginning)
  304. (region-end)))
  305. (error "This only works when you mark stuff inside a function")))
  306. (defun js2r--marked-expressions-in-block (fn)
  307. (-select #'js2r--node-is-marked (js2-block-node-kids fn)))
  308. (defun js2r--node-is-marked (node)
  309. (and
  310. (<= (region-beginning) (js2-node-abs-end node))
  311. (>= (region-end) (js2-node-abs-pos node))))
  312. (defun js2r--name-node-decendants (node)
  313. (-select #'js2-name-node-p (js2r--decendants node)))
  314. (defun js2r--var-init-node-decendants (node)
  315. (-select #'js2-var-init-node-p (js2r--decendants node)))
  316. (defun js2r--decendants (node)
  317. (let (vars)
  318. (js2-visit-ast node
  319. (lambda (node end-p)
  320. (unless end-p
  321. (setq vars (cons node vars)))))
  322. vars))
  323. (defun js2r--local-to-fn-p (fn name-node)
  324. (let* ((name (js2-name-node-name name-node))
  325. (scope (js2-node-get-enclosing-scope name-node))
  326. (scope (js2-get-defining-scope scope name)))
  327. (eq fn scope)))
  328. (defun js2r-toggle-arrow-function-and-expression ()
  329. "Toggle between function expression to arrow function."
  330. (interactive)
  331. (save-excursion
  332. (js2r--find-closest-function)
  333. (cond ((js2r--arrow-function-p)
  334. (js2r--transform-arrow-function-to-expression))
  335. ((and (js2r--function-start-p) (not (js2r--looking-at-function-declaration)))
  336. (js2r--transform-function-expression-to-arrow))
  337. (t (error "Can only toggle between function expressions and arrow function")))))
  338. ;; Toggle between function name() {} and var name = function ();
  339. (defun js2r-toggle-function-expression-and-declaration ()
  340. (interactive)
  341. (save-excursion
  342. (js2r--find-closest-function)
  343. (cond
  344. ((js2r--looking-at-var-function-expression)
  345. (when (js2r--arrow-function-p) (js2r--transform-arrow-function-to-expression))
  346. (js2r--transform-function-expression-to-declaration))
  347. ((js2r--looking-at-function-declaration)
  348. (js2r--transform-function-declaration-to-expression))
  349. (t (error "Can only toggle between function declarations and free standing function expressions")))))
  350. (defun js2r--arrow-function-p ()
  351. (interactive)
  352. (save-excursion
  353. (ignore-errors
  354. (js2r--find-closest-function)
  355. (and (looking-at "(?[,[:space:][:word:]]*)?[[:space:]]*=>")
  356. (not (js2r--point-inside-string-p))))))
  357. (defun js2r--transform-arrow-function-to-expression ()
  358. (when (js2r--arrow-function-p)
  359. (let (has-parenthesis)
  360. (save-excursion
  361. (js2r--find-closest-function)
  362. (let ((end (make-marker)))
  363. (save-excursion
  364. (search-forward "=>")
  365. (set-marker end (js2-node-abs-end (js2-node-at-point))))
  366. (setq has-parenthesis (looking-at "\\s-*("))
  367. (insert "function ")
  368. (if has-parenthesis
  369. (forward-list)
  370. (insert "("))
  371. (search-forward "=>")
  372. (delete-char -2)
  373. (js2r--ensure-just-one-space)
  374. (unless has-parenthesis
  375. (backward-char 1)
  376. (insert ")"))
  377. (unless (looking-at "\\s-*{")
  378. (js2r--ensure-just-one-space)
  379. (insert "{ return ")
  380. (js2r--ensure-just-one-space)
  381. (goto-char (marker-position end))
  382. (insert "; }")))))))
  383. (defun js2r--transform-function-expression-to-arrow ()
  384. (when (not (js2r--arrow-function-p))
  385. (save-excursion
  386. (js2r--find-closest-function)
  387. (let ((pos (point))
  388. (params
  389. (js2-function-node-params (js2-node-at-point)))
  390. parenthesis-start
  391. parenthesis-end)
  392. (when (js2r--looking-at-function-declaration)
  393. (error "Can not convert function declarations to arrow function"))
  394. (search-forward "(")
  395. (backward-char 1)
  396. (delete-region pos (point))
  397. (setq parenthesis-start (point))
  398. (forward-list)
  399. (setq parenthesis-end (point))
  400. (insert " => ")
  401. (js2r--ensure-just-one-space)
  402. (when (and (= 1 (length params))
  403. (not js2r-always-insert-parens-around-arrow-function-params))
  404. (goto-char parenthesis-end)
  405. (backward-delete-char 1)
  406. (goto-char parenthesis-start)
  407. (delete-char 1))))))
  408. (defun js2r--function-start-p()
  409. (let* ((fn (js2r--closest #'js2-function-node-p)))
  410. (and fn
  411. (= (js2-node-abs-pos fn) (point)))))
  412. (defun js2r--find-closest-function ()
  413. (when (not (js2r--function-start-p))
  414. (let* ((fn (js2r--closest #'js2-function-node-p)))
  415. (goto-char (js2-node-abs-pos fn)))))
  416. (defun js2r--looking-at-method ()
  417. (and (js2r--function-start-p)
  418. (looking-back ": ?")))
  419. (defun js2r--looking-at-function-declaration ()
  420. (and (js2r--function-start-p)
  421. (looking-back "^ *")))
  422. (defun js2r--looking-at-var-function-expression ()
  423. (and (js2r--function-start-p)
  424. (looking-back "^ *var[\s\n]*[a-z_$]+[\s\n]*=[\s\n]*")))
  425. (defun js2r--transform-function-expression-to-declaration ()
  426. (when (js2r--looking-at-var-function-expression)
  427. (delete-char 9)
  428. (forward-list)
  429. (forward-list)
  430. (delete-char 1)
  431. (backward-list)
  432. (backward-list)
  433. (delete-backward-char 3)
  434. (back-to-indentation)
  435. (delete-char 4)
  436. (insert "function ")))
  437. (defun js2r--transform-function-declaration-to-expression ()
  438. (when (js2r--looking-at-function-declaration)
  439. (delete-char 9)
  440. (insert "var ")
  441. (search-forward "(")
  442. (backward-char 1)
  443. (insert " = function ")
  444. (forward-list)
  445. (forward-list)
  446. (insert ";")))
  447. (provide 'js2r-functions)
  448. ;;; js2-functions.el ends here