branch: master commit 5ffca32900b224805cb6e679798d53d999b1dcc0 Merge: 8ad76a7 5c9d8b8 Author: Dmitry Gutov <dgu...@yandex.ru> Commit: Dmitry Gutov <dgu...@yandex.ru>
Merge commit '5c9d8b82dddec2fab370ec8798569c7fc5698093' from js2-mode --- packages/js2-mode/Makefile | 2 +- packages/js2-mode/NEWS.md | 18 +++ packages/js2-mode/js2-mode.el | 223 +++++++++++++++++++++++++-------- packages/js2-mode/js2-old-indent.el | 20 ++-- packages/js2-mode/tests/indent.el | 1 + packages/js2-mode/tests/navigation.el | 60 +++++++++ packages/js2-mode/tests/parser.el | 6 +- 7 files changed, 258 insertions(+), 72 deletions(-) diff --git a/packages/js2-mode/Makefile b/packages/js2-mode/Makefile index f86786f..08b1e48 100644 --- a/packages/js2-mode/Makefile +++ b/packages/js2-mode/Makefile @@ -22,5 +22,5 @@ js2-imenu-extras.elc: js2-mode.elc ${EMACS} $(BATCHFLAGS) -l ./js2-mode.elc -f batch-byte-compile $*.el test: - ${EMACS} $(BATCHFLAGS) -l js2-mode.el -l tests/parser.el\ + ${EMACS} $(BATCHFLAGS) -L . -l js2-mode.el -l js2-old-indent.el -l tests/parser.el\ -l tests/indent.el -l tests/externs.el -f ert-run-tests-batch-and-exit diff --git a/packages/js2-mode/NEWS.md b/packages/js2-mode/NEWS.md index 300bc84..2984e91 100644 --- a/packages/js2-mode/NEWS.md +++ b/packages/js2-mode/NEWS.md @@ -1,5 +1,23 @@ # History of user-visible changes +## 20150909 + +* `js2-mode` now derives from `js-mode`. That means the former + function will run `js-mode-hook`, as well as `js2-mode-hook`. The + key bindings will default to `js-mode-map` where they're not set in + `js2-mode-map`. And in Emacs 25 or later (including the snapshot + builds), `js2-mode` uses the indentation code from `js-mode`. Where + feasible, the user options (and functions) now have aliases, but if + you're using Emacs 25 and you see an indentation-related setting + that stopped working, try looking for a corresponding one in the + `js` group: `M-x customize-group RET js RET`. + +* New command: `js2-jump-to-definition`. It's bound to `M-.` by + default, via remapping `js-find-symbol`. To get back to the default + `M-.` binding (e.g. `find-tag`), put this in your init file: + + (eval-after-load 'js (define-key js-mode-map (kbd "M-.") nil)) + ## 20150713 * More comprehensive strict mode warnings and syntax errors. diff --git a/packages/js2-mode/js2-mode.el b/packages/js2-mode/js2-mode.el index 6ea2461..97f3269 100644 --- a/packages/js2-mode/js2-mode.el +++ b/packages/js2-mode/js2-mode.el @@ -7,7 +7,7 @@ ;; Dmitry Gutov <dgu...@yandex.ru> ;; URL: https://github.com/mooz/js2-mode/ ;; http://code.google.com/p/js2-mode/ -;; Version: 20150713 +;; Version: 20150909 ;; Keywords: languages, javascript ;; Package-Requires: ((emacs "24.1") (cl-lib "0.5")) @@ -88,13 +88,15 @@ (require 'cl-lib) (require 'imenu) (require 'js) +(require 'etags) (eval-and-compile (if (version< emacs-version "25.0") (require 'js2-old-indent) (defvaralias 'js2-basic-offset 'js-indent-level nil) (defalias 'js2-proper-indentation 'js--proper-indentation) - (defalias 'js2-indent-line 'js-indent-line))) + (defalias 'js2-indent-line 'js-indent-line) + (defalias 'js2-re-search-forward 'js--re-search-forward))) ;;; Externs (variables presumed to be defined by the host system) @@ -1114,6 +1116,7 @@ information." (define-key map (kbd "C-c C-o") #'js2-mode-toggle-element) (define-key map (kbd "C-c C-w") #'js2-mode-toggle-warnings-and-errors) (define-key map [down-mouse-3] #'js2-down-mouse-3) + (define-key map [remap js-find-symbol] #'js2-jump-to-definition) (define-key map [menu-bar javascript] (cons "JavaScript" (make-sparse-keymap "JavaScript"))) @@ -6818,7 +6821,7 @@ of a simple name. Called before EXPR has a parent node." (defconst js2-jsdoc-param-tag-regexp (concat "^\\s-*\\*+\\s-*\\(@" - "\\(?:param\\|argument\\)" + "\\(?:param\\|arg\\(?:ument\\)?\\|prop\\(?:erty\\)?\\)" "\\)" "\\s-*\\({[^}]+}\\)?" ; optional type "\\s-*\\[?\\([[:alnum:]_$\.]+\\)?\\]?" ; name @@ -6860,7 +6863,6 @@ of a simple name. Called before EXPR has a parent node." "memberOf" "name" "namespace" - "property" "since" "suppress" "this" @@ -7966,46 +7968,42 @@ Scanner should be initialized." (js2-node-add-children fn-node pn) pn)) -(defun js2-define-destruct-symbols-internal - (node decl-type face &optional ignore-not-in-block name-nodes) - "Internal version of `js2-define-destruct-symbols'. The only -difference is that NAME-NODES is passed down recursively." - (cond - ((js2-name-node-p node) - (let (leftpos) - (js2-define-symbol decl-type (js2-name-node-name node) - node ignore-not-in-block) - (when face - (js2-set-face (setq leftpos (js2-node-abs-pos node)) - (+ leftpos (js2-node-len node)) - face 'record)) - (setq name-nodes (append name-nodes (list node))))) - ((js2-object-node-p node) - (dolist (elem (js2-object-node-elems node)) - (setq name-nodes - (append name-nodes - (js2-define-destruct-symbols-internal - ;; In abbreviated destructuring {a, b}, right == left. - (js2-object-prop-node-right elem) - decl-type face ignore-not-in-block name-nodes))))) - ((js2-array-node-p node) - (dolist (elem (js2-array-node-elems node)) - (when elem - (setq name-nodes - (append name-nodes - (js2-define-destruct-symbols-internal - elem decl-type face ignore-not-in-block name-nodes)))))) - (t (js2-report-error "msg.no.parm" nil (js2-node-abs-pos node) - (js2-node-len node)))) - name-nodes) - (defun js2-define-destruct-symbols (node decl-type face &optional ignore-not-in-block) "Declare and fontify destructuring parameters inside NODE. NODE is either `js2-array-node', `js2-object-node', or `js2-name-node'. Return a list of `js2-name-node' nodes representing the symbols declared; probably to check them for errors." - (js2-define-destruct-symbols-internal node decl-type face ignore-not-in-block)) + (let (name-nodes) + (cond + ((js2-name-node-p node) + (let (leftpos) + (js2-define-symbol decl-type (js2-name-node-name node) + node ignore-not-in-block) + (when face + (js2-set-face (setq leftpos (js2-node-abs-pos node)) + (+ leftpos (js2-node-len node)) + face 'record)) + (list node))) + ((js2-object-node-p node) + (dolist (elem (js2-object-node-elems node)) + (when (js2-object-prop-node-p elem) + (push (js2-define-destruct-symbols + ;; In abbreviated destructuring {a, b}, right == left. + (js2-object-prop-node-right elem) + decl-type face ignore-not-in-block) + name-nodes))) + (apply #'append (nreverse name-nodes))) + ((js2-array-node-p node) + (dolist (elem (js2-array-node-elems node)) + (when elem + (push (js2-define-destruct-symbols + elem decl-type face ignore-not-in-block) + name-nodes))) + (apply #'append (nreverse name-nodes))) + (t (js2-report-error "msg.no.parm" nil (js2-node-abs-pos node) + (js2-node-len node)) + nil)))) (defvar js2-illegal-strict-identifiers '("eval" "arguments") @@ -8041,7 +8039,7 @@ represented by FN-NODE at POS." (eq (js2-current-token-type) js2-NAME))) params param param-name-nodes new-param-name-nodes - default-found rest-param-at) + rest-param-at) (when paren-free-arrow (js2-unget-token)) (cl-loop for tt = (js2-peek-token) @@ -8051,8 +8049,6 @@ represented by FN-NODE at POS." ((and (not paren-free-arrow) (or (= tt js2-LB) (= tt js2-LC))) (js2-get-token) - (when default-found - (js2-report-error "msg.no.default.after.default.param")) (setq param (js2-parse-destruct-primary-expr) new-param-name-nodes (js2-define-destruct-symbols param js2-LP 'js2-function-param)) @@ -8074,14 +8070,8 @@ represented by FN-NODE at POS." (js2-check-strict-function-params param-name-nodes (list param)) (setq param-name-nodes (append param-name-nodes (list param))) ;; default parameter value - (when (or (and default-found - (not rest-param-at) - (js2-must-match js2-ASSIGN - "msg.no.default.after.default.param" - (js2-node-pos param) - (js2-node-len param))) - (and (>= js2-language-version 200) - (js2-match-token js2-ASSIGN))) + (when (and (>= js2-language-version 200) + (js2-match-token js2-ASSIGN)) (cl-assert (not paren-free-arrow)) (let* ((pos (js2-node-pos param)) (tt (js2-current-token-type)) @@ -8091,8 +8081,7 @@ represented by FN-NODE at POS." (len (- (js2-node-end right) pos))) (setq param (make-js2-assign-node :type tt :pos pos :len len :op-pos op-pos - :left left :right right) - default-found t) + :left left :right right)) (js2-node-add-children param left right))) (push param params))) (when (and rest-param-at (> (length params) (1+ rest-param-at))) @@ -11129,9 +11118,11 @@ such as `js-mode', while retaining the asynchronous error/warning highlighting features of `js2-mode'." :group 'js2-mode :lighter " js-lint" - (if js2-minor-mode - (js2-minor-mode-enter) - (js2-minor-mode-exit))) + (if (derived-mode-p 'js2-mode) + (setq js2-minor-mode nil) + (if js2-minor-mode + (js2-minor-mode-enter) + (js2-minor-mode-exit)))) (defun js2-minor-mode-enter () "Initialization for `js2-minor-mode'." @@ -11278,7 +11269,6 @@ Selecting an error will jump it to the corresponding source-buffer error. ;;;###autoload (define-derived-mode js2-mode js-mode "Javascript-IDE" "Major mode for editing JavaScript code." - ;; Used by comment-region; don't change it. (set (make-local-variable 'max-lisp-eval-depth) (max max-lisp-eval-depth 3000)) (set (make-local-variable 'indent-line-function) #'js2-indent-line) @@ -12309,6 +12299,129 @@ it marks the next defun after the ones already marked." (unless (js2-ast-root-p fn) (narrow-to-region beg (+ beg (js2-node-len fn)))))) +(defun js2-jump-to-definition (&optional arg) + "Jump to the definition of an object's property, variable or function." + (interactive "P") + (ring-insert find-tag-marker-ring (point-marker)) + (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))))) + (provide 'js2-mode) ;;; js2-mode.el ends here diff --git a/packages/js2-mode/js2-old-indent.el b/packages/js2-mode/js2-old-indent.el index 9b1c929..efc9053 100644 --- a/packages/js2-mode/js2-old-indent.el +++ b/packages/js2-mode/js2-old-indent.el @@ -396,7 +396,7 @@ indentation is aligned to that column." (save-excursion (back-to-indentation) (when (nth 4 parse-status) - (cl-return (js2-lineup-comment parse-status))) + (cl-return-from js2-proper-indentation (js2--comment-indent parse-status))) (let* ((at-closing-bracket (looking-at "[]})]")) (same-indent-p (or at-closing-bracket (looking-at "\\_<case\\_>[^:]") @@ -458,17 +458,13 @@ indentation is aligned to that column." (t 0))))) -(defun js2-lineup-comment (parse-status) - "Indent a multi-line block comment continuation line." - (let* ((beg (nth 8 parse-status)) - (first-line (js2-same-line beg)) - (offset (save-excursion - (goto-char beg) - (if (looking-at "/\\*") - (+ 1 (current-column)) - 0)))) - (unless first-line - (indent-line-to offset)))) +(defun js2--comment-indent (parse-status) + "Indentation inside a multi-line block comment continuation line." + (save-excursion + (goto-char (nth 8 parse-status)) + (if (looking-at "/\\*") + (+ 1 (current-column)) + 0))) (defun js2-indent-line (&optional bounce-backwards) "Indent the current line as JavaScript source text." diff --git a/packages/js2-mode/tests/indent.el b/packages/js2-mode/tests/indent.el index df69202..27b6c5a 100644 --- a/packages/js2-mode/tests/indent.el +++ b/packages/js2-mode/tests/indent.el @@ -22,6 +22,7 @@ (require 'ert) (require 'js2-mode) (require 'cl-lib) +(require 'js2-old-indent) (defun js2-test-indent (content keep-indent) (let ((s (replace-regexp-in-string "^ *|" "" content))) diff --git a/packages/js2-mode/tests/navigation.el b/packages/js2-mode/tests/navigation.el new file mode 100644 index 0000000..d7a8314 --- /dev/null +++ b/packages/js2-mode/tests/navigation.el @@ -0,0 +1,60 @@ +;;; tests/navigation.el --- Some tests for js2-mode. + +;; Copyright (C) 2009, 2011-2015 Free Software Foundation, Inc. + +;; This file is part of GNU Emacs. + +;; GNU Emacs 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. + +;; GNU Emacs 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 GNU Emacs. If not, see <http://www.gnu.org/licenses/>. + +;;; Code: + +(require 'ert) +(require 'js2-mode) + +(cl-defun js2-navigation-helper (buffer-content &optional expected-point (point-offset 1)) + (with-temp-buffer + (insert buffer-content) + (let ((start-point (or (- (point) point-offset)))) + (js2-mode) + (goto-char start-point) + (ignore-errors (js2-jump-to-definition)) + (print (format "%d %d" (point) start-point)) + (should (= (point) (or expected-point start-point)))))) + +(ert-deftest js2-jump-to-var () + (js2-navigation-helper "var soup = 2; soup" 5)) + +(ert-deftest js2-jump-to-function () + (js2-navigation-helper "function aFunction() {}; aFunction" 1)) + +(ert-deftest js2-jump-to-function-parameters () + (js2-navigation-helper "var p1 = 4; function aFunction(p1, p2) {p1};" 32 4)) + +(ert-deftest js2-jump-to-object-property () + (js2-navigation-helper "var aObject = {prop1: 3, prop2: \"hello\"}; aObject.prop1" 16)) + +(ert-deftest js2-no-jump-to-object-property () + (js2-navigation-helper "var aObject = {prop1: 3, prop2: \"hello\"}; anotherObject.prop1")) + +(ert-deftest js2-jump-to-nested-property () + (js2-navigation-helper "var aObject = {prop1: {prop2: { prop3: 4}}}; aObject.prop1.prop2.prop3" 33)) + +(ert-deftest js2-jump-to-object () + (js2-navigation-helper "var aObject = {prop1: 3, prop2: \"hello\"}; aObject.prop1" 5 13)) + +(ert-deftest js2-jump-to-property () + (js2-navigation-helper "aObject.func = functon(){};aObject.func" 9)) + +(ert-deftest js2-jump-to-property-object-property () + (js2-navigation-helper "aObject.value = {prop:1};aObject.value.prop" 18)) diff --git a/packages/js2-mode/tests/parser.el b/packages/js2-mode/tests/parser.el index bfc5653..dc8c001 100644 --- a/packages/js2-mode/tests/parser.el +++ b/packages/js2-mode/tests/parser.el @@ -226,12 +226,10 @@ the test." "function foo(a = 1, b = a + 1) {\n}") (js2-deftest-parse function-with-no-default-after-default - "function foo(a = 1, b) {\n}" - :syntax-error "b") + "function foo(a = 1, b) {\n}") (js2-deftest-parse function-with-destruct-after-default - "function foo(a = 1, {b, c}) {\n}" - :syntax-error "{") + "function foo(a = 1, {b, c}) {\n}") (js2-deftest-parse function-with-rest-parameter "function foo(a, b, ...rest) {\n}")