+(defun js2-jump-to-definition (&optional arg)
+ "Jump to the definition of an object's property, variable or function."
+ (interactive "P")
+ (if (eval-when-compile (fboundp 'xref-push-marker-stack))
+ (xref-push-marker-stack)
+ (ring-insert find-tag-marker-ring (point-marker)))
+ (js2-reparse)
+ (let* ((node (js2-node-at-point))
+ (parent (js2-node-parent node))
+ (names (if (js2-prop-get-node-p parent)
+ (reverse (let ((temp (js2-compute-nested-prop-get parent)))
+ (cl-loop for n in temp
+ with result = '()
+ do (push n result)
+ until (equal node n)
+ finally return result)))))
+ node-init)
+ (unless (and (js2-name-node-p node)
+ (not (js2-var-init-node-p parent))
+ (not (js2-function-node-p parent)))
+ (error "Node is not a supported jump node"))
+ (push (or (and names (pop names))
+ (unless (and (js2-object-prop-node-p parent)
+ (eq node (js2-object-prop-node-left parent)))
+ node)) names)
+ (setq node-init (js2-search-scope node names))
+
+ ;; todo: display list of results in buffer
+ ;; todo: group found references by buffer
+ (unless node-init
+ (switch-to-buffer
+ (catch 'found
+ (unless arg
+ (mapc (lambda (b)
+ (with-current-buffer b
+ (when (derived-mode-p 'js2-mode)
+ (setq node-init (js2-search-scope js2-mode-ast names))
+ (if node-init
+ (throw 'found b)))))
+ (buffer-list)))
+ nil)))
+ (setq node-init (if (listp node-init) (car node-init) node-init))
+ (unless node-init
+ (pop-tag-mark)
+ (error "No jump location found"))
+ (goto-char (js2-node-abs-pos node-init))))
+
+(defun js2-search-object (node name-node)
+ "Check if object NODE contains element with NAME-NODE."
+ (cl-assert (js2-object-node-p node))
+ ;; Only support name-node and nodes for the time being
+ (cl-loop for elem in (js2-object-node-elems node)
+ for left = (js2-object-prop-node-left elem)
+ if (or (and (js2-name-node-p left)
+ (equal (js2-name-node-name name-node)
+ (js2-name-node-name left)))
+ (and (js2-string-node-p left)
+ (string= (js2-name-node-name name-node)
+ (js2-string-node-value left))))
+ return elem))
+
+(defun js2-search-object-for-prop (object prop-names)
+ "Return node in OBJECT that matches PROP-NAMES or nil.
+PROP-NAMES is a list of values representing a path to a value in OBJECT.
+i.e. ('name' 'value') = {name : { value: 3}}"
+ (let (node
+ (temp-object object)
+ (temp t) ;temporay node
+ (names prop-names))
+ (while (and temp names (js2-object-node-p temp-object))
+ (setq temp (js2-search-object temp-object (pop names)))
+ (and (setq node temp)
+ (setq temp-object (js2-object-prop-node-right temp))))
+ (unless names node)))
+
+(defun js2-search-scope (node names)
+ "Searches NODE scope for jump location matching NAMES.
+NAMES is a list of property values to search for. For functions
+and variables NAMES will contain one element."
+ (let (node-init
+ (val (js2-name-node-name (car names))))
+ (setq node-init (js2-get-symbol-declaration node val))
+
+ (when (> (length names) 1)
+
+ ;; Check var declarations
+ (when (and node-init (string= val (js2-name-node-name node-init)))
+ (let ((parent (js2-node-parent node-init))
+ (temp-names names))
+ (pop temp-names) ;; First element is var name
+ (setq node-init (when (js2-var-init-node-p parent)
+ (js2-search-object-for-prop
+ (js2-var-init-node-initializer parent)
+ temp-names)))))
+
+ ;; Check all assign nodes
+ (js2-visit-ast
+ js2-mode-ast
+ (lambda (node endp)
+ (unless endp
+ (if (js2-assign-node-p node)
+ (let ((left (js2-assign-node-left node))
+ (right (js2-assign-node-right node))
+ (temp-names names))
+ (when (js2-prop-get-node-p left)
+ (let* ((prop-list (js2-compute-nested-prop-get left))
+ (found (cl-loop for prop in prop-list
+ until (not (string= (js2-name-node-name
+ (pop temp-names))
+ (js2-name-node-name prop)))
+ if (not temp-names) return prop))
+ (found-node (or found
+ (when (js2-object-node-p right)
+ (js2-search-object-for-prop right
+ temp-names)))))
+ (if found-node (push found-node node-init))))))
+ t))))
+ node-init))
+
+(defun js2-get-symbol-declaration (node name)
+ "Find scope for NAME from NODE."
+ (let ((scope (js2-get-defining-scope
+ (or (js2-node-get-enclosing-scope node)
+ node) name)))
+ (if scope (js2-symbol-ast-node (js2-scope-get-symbol scope name)))))
+