376 lines
12 KiB
EmacsLisp
376 lines
12 KiB
EmacsLisp
![]() |
;;; js2r-vars.el --- Variable declaration manipulation 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 'multiple-cursors-core)
|
||
|
(require 'dash)
|
||
|
|
||
|
(require 'js2r-helpers)
|
||
|
|
||
|
;; Helpers
|
||
|
|
||
|
(defun js2r--name-node-at-point (&optional pos)
|
||
|
(setq pos (or pos (point)))
|
||
|
(let ((current-node (js2-node-at-point pos)))
|
||
|
(unless (js2-name-node-p current-node)
|
||
|
(setq current-node (js2-node-at-point (- (point) 1))))
|
||
|
(if (not (and current-node (js2-name-node-p current-node)))
|
||
|
(error "Point is not on an identifier.")
|
||
|
current-node)))
|
||
|
|
||
|
(defun js2r--local-name-node-at-point (&optional pos)
|
||
|
(setq pos (or pos (point)))
|
||
|
(let ((current-node (js2r--name-node-at-point pos)))
|
||
|
(unless (js2r--local-name-node-p current-node)
|
||
|
(error "Point is not on a local identifier"))
|
||
|
current-node))
|
||
|
|
||
|
(defun js2r--local-name-node-p (node)
|
||
|
(let ((parent (js2-node-parent node)))
|
||
|
(and parent (js2-name-node-p node)
|
||
|
;; is not foo in { foo: 1 }
|
||
|
(not (and (js2-object-prop-node-p parent)
|
||
|
(eq node (js2-object-prop-node-left parent))
|
||
|
;; could be { foo } though, in which case the node
|
||
|
;; is parent's both left and right.
|
||
|
(not (eq node (js2-object-prop-node-right parent)))))
|
||
|
;; is not foo in x.foo
|
||
|
(not (and (js2-prop-get-node-p parent)
|
||
|
(eq node (js2-prop-get-node-right parent))))
|
||
|
;; is not foo in import { foo as bar } from ...
|
||
|
;; could be bar, though.
|
||
|
(not (and (js2-export-binding-node-p parent)
|
||
|
(eq node (js2-export-binding-node-extern-name parent))
|
||
|
(not (eq node (js2-export-binding-node-local-name parent))))))))
|
||
|
|
||
|
(defun js2r--name-node-defining-scope (name-node)
|
||
|
(unless (js2r--local-name-node-p name-node)
|
||
|
(error "Node is not on a local identifier"))
|
||
|
(js2-get-defining-scope
|
||
|
(js2-node-get-enclosing-scope name-node)
|
||
|
(js2-name-node-name name-node)))
|
||
|
|
||
|
(defun js2r--local-usages-of-name-node (name-node)
|
||
|
(unless (js2r--local-name-node-p name-node)
|
||
|
(error "Node is not on a local identifier"))
|
||
|
(let* ((name (js2-name-node-name name-node))
|
||
|
(scope (js2r--name-node-defining-scope name-node))
|
||
|
(result nil))
|
||
|
(js2-visit-ast
|
||
|
scope
|
||
|
(lambda (node end-p)
|
||
|
(when (and (not end-p)
|
||
|
(js2r--local-name-node-p node)
|
||
|
(string= name (js2-name-node-name node))
|
||
|
(eq scope (js2r--name-node-defining-scope node)))
|
||
|
(add-to-list 'result node))
|
||
|
t))
|
||
|
result))
|
||
|
|
||
|
(defun js2r--local-var-positions (name-node)
|
||
|
(-map 'js2-node-abs-pos (js2r--local-usages-of-name-node name-node)))
|
||
|
|
||
|
(defun js2r--var-defining-node (var-node)
|
||
|
(unless (js2r--local-name-node-p var-node)
|
||
|
(error "Node is not on a local identifier"))
|
||
|
(let* ((name (js2-name-node-name var-node))
|
||
|
(scope (js2r--name-node-defining-scope var-node)))
|
||
|
(js2-symbol-ast-node
|
||
|
(js2-scope-get-symbol scope name))))
|
||
|
|
||
|
|
||
|
;; Add to jslint globals annotation
|
||
|
|
||
|
(defun current-line-contents ()
|
||
|
"Find the contents of the current line, minus indentation."
|
||
|
(buffer-substring (save-excursion (back-to-indentation) (point))
|
||
|
(save-excursion (end-of-line) (point))))
|
||
|
|
||
|
(require 'thingatpt)
|
||
|
|
||
|
(defun js2r-add-to-globals-annotation ()
|
||
|
(interactive)
|
||
|
(let ((var (thing-at-point 'symbol)))
|
||
|
(save-excursion
|
||
|
(beginning-of-buffer)
|
||
|
(when (not (string-match "^/\\* *global " (current-line-contents)))
|
||
|
(newline)
|
||
|
(forward-line -1)
|
||
|
(insert "/* global */")
|
||
|
(newline)
|
||
|
(forward-line -1))
|
||
|
(while (not (string-match "*/" (current-line-contents)))
|
||
|
(forward-line))
|
||
|
(end-of-line)
|
||
|
(delete-char -2)
|
||
|
(unless (looking-back "global ")
|
||
|
(while (looking-back " ")
|
||
|
(delete-char -1))
|
||
|
(insert ", "))
|
||
|
(insert (concat var " */")))))
|
||
|
|
||
|
|
||
|
;; Rename variable
|
||
|
|
||
|
(defun js2r-rename-var ()
|
||
|
"Renames the variable on point and all occurrences in its lexical scope."
|
||
|
(interactive)
|
||
|
(js2r--guard)
|
||
|
(js2r--wait-for-parse
|
||
|
(let* ((current-node (js2r--local-name-node-at-point))
|
||
|
(len (js2-node-len current-node))
|
||
|
(current-start (js2-node-abs-pos current-node))
|
||
|
(current-end (+ current-start len)))
|
||
|
(save-excursion
|
||
|
(mapc (lambda (beg)
|
||
|
(when (not (= beg current-start))
|
||
|
(goto-char beg)
|
||
|
(set-mark (+ beg len))
|
||
|
(mc/create-fake-cursor-at-point)))
|
||
|
(js2r--local-var-positions current-node)))
|
||
|
(push-mark current-end)
|
||
|
(goto-char current-start)
|
||
|
(activate-mark))
|
||
|
(mc/maybe-multiple-cursors-mode)))
|
||
|
|
||
|
(add-to-list 'mc--default-cmds-to-run-once 'js2r-rename-var)
|
||
|
|
||
|
;; Change local variable to use this. instead
|
||
|
|
||
|
(defun js2r-var-to-this ()
|
||
|
"Changes the variable on point to use this.var instead."
|
||
|
(interactive)
|
||
|
(js2r--guard)
|
||
|
(js2r--wait-for-parse
|
||
|
(save-excursion
|
||
|
(let ((node (js2-node-at-point)))
|
||
|
(when (js2-var-decl-node-p node)
|
||
|
(let ((kids (js2-var-decl-node-kids node)))
|
||
|
(when (cdr kids)
|
||
|
(error "Currently does not support converting multivar statements."))
|
||
|
(goto-char (js2-node-abs-pos (car kids))))))
|
||
|
(--each (js2r--local-var-positions (js2r--local-name-node-at-point))
|
||
|
(goto-char it)
|
||
|
(cond ((looking-back "var ") (delete-char -4))
|
||
|
((looking-back "let ") (delete-char -4))
|
||
|
((looking-back "const ") (delete-char -6)))
|
||
|
(insert "this.")))))
|
||
|
|
||
|
;; Inline var
|
||
|
|
||
|
(defun js2r-inline-var ()
|
||
|
(interactive)
|
||
|
(js2r--guard)
|
||
|
(js2r--wait-for-parse
|
||
|
(save-excursion
|
||
|
(let* ((current-node (js2r--local-name-node-at-point))
|
||
|
(definer (js2r--var-defining-node current-node))
|
||
|
(definer-start (js2-node-abs-pos definer))
|
||
|
(var-init (js2-node-parent definer))
|
||
|
(initializer (js2-var-init-node-initializer
|
||
|
var-init)))
|
||
|
(unless initializer
|
||
|
(error "Var is not initialized when defined."))
|
||
|
(let* ((var-len (js2-node-len current-node))
|
||
|
(init-beg (js2-node-abs-pos initializer))
|
||
|
(init-end (+ init-beg (js2-node-len initializer)))
|
||
|
(var-init-beg (copy-marker (js2-node-abs-pos var-init)))
|
||
|
(var-init-end (copy-marker (+ var-init-beg (js2-node-len var-init))))
|
||
|
(contents (buffer-substring init-beg init-end)))
|
||
|
(mapc (lambda (beg)
|
||
|
(when (not (= beg definer-start))
|
||
|
(goto-char beg)
|
||
|
(delete-char var-len)
|
||
|
(insert contents)))
|
||
|
(js2r--local-var-positions current-node))
|
||
|
(js2r--delete-var-init var-init-beg var-init-end))))))
|
||
|
|
||
|
|
||
|
(defun js2r--was-single-var ()
|
||
|
(save-excursion
|
||
|
(goto-char (point-at-bol))
|
||
|
(or (looking-at "^[[:space:]]*\\(var\\|const\\|let\\)[[:space:]]?;?$")
|
||
|
(looking-at "^[[:space:]]*,[[:space:]]*$"))))
|
||
|
|
||
|
(defun js2r--was-starting-var ()
|
||
|
(or (looking-back "var ")
|
||
|
(looking-back "const ")
|
||
|
(looking-back "let ")))
|
||
|
|
||
|
(defun js2r--was-ending-var ()
|
||
|
(looking-at ";"))
|
||
|
|
||
|
(defun js2r--delete-var-init (beg end)
|
||
|
(goto-char beg)
|
||
|
(delete-char (- end beg))
|
||
|
(cond
|
||
|
((js2r--was-single-var)
|
||
|
(delete-region (point-at-bol) (point-at-eol))
|
||
|
(delete-blank-lines))
|
||
|
|
||
|
((js2r--was-starting-var)
|
||
|
(delete-char 1)
|
||
|
(if (looking-at " ")
|
||
|
(delete-char 1)
|
||
|
(join-line -1)))
|
||
|
|
||
|
((js2r--was-ending-var)
|
||
|
(if (looking-back ", ")
|
||
|
(delete-char -1)
|
||
|
(join-line)
|
||
|
(delete-char 1))
|
||
|
(delete-char -1))
|
||
|
|
||
|
(t (delete-char 2))))
|
||
|
|
||
|
;; two cases
|
||
|
;; - it's the only var -> remove the line
|
||
|
;; - there are several vars -> remove the node then clean up commas
|
||
|
|
||
|
|
||
|
;; Extract variable
|
||
|
|
||
|
(defun js2r--start-of-parent-stmt ()
|
||
|
(js2-node-abs-pos (js2r--closest-stmt-node)))
|
||
|
|
||
|
(defun js2r--object-literal-key-behind (pos)
|
||
|
(save-excursion
|
||
|
(goto-char pos)
|
||
|
(when (looking-back "\\sw: ?")
|
||
|
(backward-char 2)
|
||
|
(js2-name-node-name (js2r--name-node-at-point)))))
|
||
|
|
||
|
(defun js2r--line-above-is-blank ()
|
||
|
(save-excursion
|
||
|
(forward-line -1)
|
||
|
(string= "" (current-line-contents))))
|
||
|
|
||
|
;;;###autoload
|
||
|
(defun js2r-extract-var ()
|
||
|
(interactive)
|
||
|
(js2r--guard)
|
||
|
(js2r--wait-for-parse
|
||
|
(let ((keyword (if js2r-prefer-let-over-var "let" "var")))
|
||
|
(if (use-region-p)
|
||
|
(js2r--extract-var-between (region-beginning) (region-end) keyword)
|
||
|
(let ((node (js2r--closest-extractable-node)))
|
||
|
(js2r--extract-var-between (js2-node-abs-pos node)
|
||
|
(js2-node-abs-end node) keyword))))))
|
||
|
|
||
|
;;;###autoload
|
||
|
(defun js2r-extract-let ()
|
||
|
(interactive)
|
||
|
(js2r--guard)
|
||
|
(js2r--wait-for-parse
|
||
|
(if (use-region-p)
|
||
|
(js2r--extract-var-between (region-beginning) (region-end) "let")
|
||
|
(let ((node (js2r--closest-extractable-node)))
|
||
|
(js2r--extract-var-between (js2-node-abs-pos node)
|
||
|
(js2-node-abs-end node) "let")))))
|
||
|
|
||
|
;;;###autoload
|
||
|
(defun js2r-extract-const ()
|
||
|
(interactive)
|
||
|
(js2r--guard)
|
||
|
(js2r--wait-for-parse
|
||
|
(if (use-region-p)
|
||
|
(js2r--extract-var-between (region-beginning) (region-end) "const")
|
||
|
(let ((node (js2r--closest-extractable-node)))
|
||
|
(js2r--extract-var-between (js2-node-abs-pos node)
|
||
|
(js2-node-abs-end node) "const")))))
|
||
|
|
||
|
(add-to-list 'mc--default-cmds-to-run-once 'js2r-extract-var)
|
||
|
(add-to-list 'mc--default-cmds-to-run-once 'js2r-extract-let)
|
||
|
(add-to-list 'mc--default-cmds-to-run-once 'js2r-extract-const)
|
||
|
|
||
|
(defun js2r--extract-var-between (beg end keyword)
|
||
|
(interactive "r")
|
||
|
(unless (js2r--single-complete-expression-between-p beg end)
|
||
|
(error "Can only extract single, complete expressions to var"))
|
||
|
|
||
|
(let ((deactivate-mark nil)
|
||
|
(expression (buffer-substring beg end))
|
||
|
(orig-var-end (make-marker))
|
||
|
(new-var-end (make-marker))
|
||
|
(name (or (js2r--object-literal-key-behind beg) "name")))
|
||
|
|
||
|
(delete-region beg end)
|
||
|
(insert name)
|
||
|
(set-marker orig-var-end (point))
|
||
|
|
||
|
(goto-char (js2r--start-of-parent-stmt))
|
||
|
(js2r--insert-var name keyword)
|
||
|
(set-marker new-var-end (point))
|
||
|
(insert " = " expression ";")
|
||
|
(when (or (js2r--line-above-is-blank)
|
||
|
(string-match-p "^function " expression))
|
||
|
(newline))
|
||
|
(newline)
|
||
|
(indent-region new-var-end orig-var-end)
|
||
|
(save-excursion
|
||
|
(goto-char new-var-end)
|
||
|
(set-mark (- (point) (length name)))
|
||
|
(mc/create-fake-cursor-at-point))
|
||
|
(goto-char orig-var-end)
|
||
|
(set-mark (- (point) (length name)))
|
||
|
(set-marker orig-var-end nil)
|
||
|
(set-marker new-var-end nil))
|
||
|
(mc/maybe-multiple-cursors-mode))
|
||
|
|
||
|
(defun js2r--insert-var (name keyword)
|
||
|
"Insert a var definition for NAME."
|
||
|
(insert (format "%s %s" keyword name)))
|
||
|
|
||
|
(defun js2r-split-var-declaration ()
|
||
|
"Split a variable declaration into separate variable
|
||
|
declarations for each declared variable."
|
||
|
(interactive)
|
||
|
(js2r--guard)
|
||
|
(js2r--wait-for-parse
|
||
|
(save-excursion
|
||
|
(let* ((declaration (or (js2r--closest #'js2-var-decl-node-p) (error "No var declaration at point.")))
|
||
|
(kids (js2-var-decl-node-kids declaration))
|
||
|
(stmt (js2-node-parent-stmt declaration))
|
||
|
(tt (js2-var-decl-node-decl-type declaration))
|
||
|
(keyword (cond
|
||
|
((= tt js2-VAR) "var")
|
||
|
((= tt js2-LET) "let")
|
||
|
((= tt js2-CONST) "const"))))
|
||
|
(goto-char (js2-node-abs-end stmt))
|
||
|
(mapc (lambda (kid)
|
||
|
(js2r--insert-var (js2-node-string kid) keyword)
|
||
|
(insert ";")
|
||
|
(newline)
|
||
|
(if (save-excursion
|
||
|
(goto-char (js2-node-abs-end kid))
|
||
|
(looking-at ", *\n *\n"))
|
||
|
(newline)))
|
||
|
kids)
|
||
|
(delete-char -1) ;; delete final newline
|
||
|
(let ((end (point)))
|
||
|
(js2r--goto-and-delete-node stmt)
|
||
|
(indent-region (point) end))))))
|
||
|
|
||
|
(provide 'js2r-vars)
|
||
|
;;; js2-vars.el ends here
|