From: Dmitry Gutov Date: Sun, 29 May 2016 00:06:57 +0000 (+0300) Subject: Merge branch 'dgreensp-object-rest-spread' X-Git-Url: https://code.delx.au/gnu-emacs-elpa/commitdiff_plain/58857dc01d4de5196f39f02afd12151ac4d0d349?hp=2a904e08fe3e009409ccfb8f928fab43f7efaeed Merge branch 'dgreensp-object-rest-spread' --- diff --git a/Makefile b/Makefile index dda51a3a8..7777a67c3 100644 --- a/Makefile +++ b/Makefile @@ -19,4 +19,5 @@ clean: test: ${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 + -l tests/indent.el -l tests/externs.el -l tests/json-path.el \ + -l tests/navigation.el -f ert-run-tests-batch-and-exit diff --git a/README.md b/README.md index d00571682..cbce9c37c 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -About [![Build Status](https://travis-ci.org/mooz/js2-mode.png?branch=master)](https://travis-ci.org/mooz/js2-mode) [![MELPA](https://melpa.org/packages/js2-mode-badge.svg)](https://melpa.org/#/js2-mode) +About [![Build Status](https://travis-ci.org/mooz/js2-mode.svg?branch=master)](https://travis-ci.org/mooz/js2-mode) [![MELPA](https://melpa.org/packages/js2-mode-badge.svg)](https://melpa.org/#/js2-mode) ====== Improved JavaScript editing mode for GNU Emacs ([description here](http://elpa.gnu.org/packages/js2-mode.html)). diff --git a/js2-mode.el b/js2-mode.el index 22442b96f..4d9bbf8da 100644 --- a/js2-mode.el +++ b/js2-mode.el @@ -1076,6 +1076,7 @@ Not currently used." "Face used to highlight undeclared variable identifiers.") (defcustom js2-init-hook nil + ;; FIXME: We don't really need this anymore. "List of functions to be called after `js2-mode' or `js2-minor-mode' has initialized all variables, before parsing the buffer for the first time." @@ -1455,7 +1456,7 @@ the correct number of ARGS must be provided." "Compilation produced %s syntax errors.") (js2-msg "msg.var.redecl" - "TypeError: redeclaration of var %s.") + "Redeclaration of var %s.") (js2-msg "msg.const.redecl" "TypeError: redeclaration of const %s.") @@ -2551,7 +2552,9 @@ so many of its properties will be nil. (js2-print-from-clause from)) (exports-list (js2-print-named-imports exports-list))) - (unless (and default (not (js2-assign-node-p default))) + (unless (or (and default (not (js2-assign-node-p default))) + (and declaration (or (js2-function-node-p declaration) + (js2-class-node-p declaration)))) (insert ";\n")))) (cl-defstruct (js2-while-node @@ -3477,6 +3480,7 @@ The type field inherited from `js2-node' holds the operator." (cons js2-INSTANCEOF "instanceof") (cons js2-DELPROP "delete") (cons js2-AWAIT "await") + (cons js2-VOID "void") (cons js2-COMMA ",") (cons js2-COLON ":") (cons js2-OR "||") @@ -3578,7 +3582,8 @@ property is added if the operator follows the operand." (insert op)) (if (or (= tt js2-TYPEOF) (= tt js2-DELPROP) - (= tt js2-AWAIT)) + (= tt js2-AWAIT) + (= tt js2-VOID)) (insert " ")) (js2-print-ast (js2-unary-node-operand n) 0) (when postfix @@ -3841,9 +3846,32 @@ You can tell the quote type by looking at the first character." (insert ","))) (insert "]")) -(cl-defstruct (js2-class-node +(cl-defstruct (js2-object-node (:include js2-node) (:constructor nil) + (:constructor make-js2-object-node (&key (type js2-OBJECTLIT) + (pos js2-ts-cursor) + len + elems))) + "AST node for an object literal expression. +`elems' is a list of `js2-object-prop-node'." + elems) + +(put 'cl-struct-js2-object-node 'js2-visitor 'js2-visit-object-node) +(put 'cl-struct-js2-object-node 'js2-printer 'js2-print-object-node) + +(defun js2-visit-object-node (n v) + (dolist (e (js2-object-node-elems n)) + (js2-visit-ast e v))) + +(defun js2-print-object-node (n i) + (insert (js2-make-pad i) "{") + (js2-print-list (js2-object-node-elems n)) + (insert "}")) + +(cl-defstruct (js2-class-node + (:include js2-object-node) + (:constructor nil) (:constructor make-js2-class-node (&key (type js2-CLASS) (pos js2-ts-cursor) (form 'CLASS_STATEMENT) @@ -3855,7 +3883,7 @@ optional `js2-expr-node'" form ; CLASS_{STATEMENT|EXPRESSION} name ; class name (a `js2-node-name', or nil if anonymous) extends ; class heritage (a `js2-expr-node', or nil if none) - elems) + ) (put 'cl-struct-js2-class-node 'js2-visitor 'js2-visit-class-node) (put 'cl-struct-js2-class-node 'js2-printer 'js2-print-class-node) @@ -3887,29 +3915,6 @@ optional `js2-expr-node'" (js2-print-ast elem (1+ i)))) (insert "\n" pad "}"))) -(cl-defstruct (js2-object-node - (:include js2-node) - (:constructor nil) - (:constructor make-js2-object-node (&key (type js2-OBJECTLIT) - (pos js2-ts-cursor) - len - elems))) - "AST node for an object literal expression. -`elems' is a list of `js2-object-prop-node'." - elems) - -(put 'cl-struct-js2-object-node 'js2-visitor 'js2-visit-object-node) -(put 'cl-struct-js2-object-node 'js2-printer 'js2-print-object-node) - -(defun js2-visit-object-node (n v) - (dolist (e (js2-object-node-elems n)) - (js2-visit-ast e v))) - -(defun js2-print-object-node (n i) - (insert (js2-make-pad i) "{") - (js2-print-list (js2-object-node-elems n)) - (insert "}")) - (cl-defstruct (js2-computed-prop-name-node (:include js2-node) (:constructor nil) @@ -6902,15 +6907,18 @@ of a simple name. Called before EXPR has a parent node." '("alias" "augments" "borrows" + "callback" "bug" "base" "config" "default" "define" "exception" + "func" "function" "member" "memberOf" + "method" "name" "namespace" "since" @@ -6941,6 +6949,7 @@ of a simple name. Called before EXPR has a parent node." "export" "fileoverview" "final" + "func" "function" "hidden" "ignore" @@ -6949,6 +6958,7 @@ of a simple name. Called before EXPR has a parent node." "inner" "interface" "license" + "method" "noalias" "noshadow" "notypecheck" @@ -7249,11 +7259,12 @@ are ignored." js2-additional-externs))) (defun js2-get-jslint-globals () + (js2-reparse) (cl-loop for node in (js2-ast-root-comments js2-mode-ast) when (and (eq 'block (js2-comment-node-format node)) (save-excursion (goto-char (js2-node-abs-pos node)) - (looking-at "/\\*global "))) + (looking-at "/\\* *global "))) append (js2-get-jslint-globals-in (match-end 0) (js2-node-abs-end node)))) @@ -7503,7 +7514,7 @@ For instance, processing a nested scope requires a parent function node." (let (result fn parent-qname p elem) (dolist (entry js2-imenu-recorder) ;; function node goes first - (cl-destructuring-bind (current-fn &rest (&whole chain head &rest)) entry + (cl-destructuring-bind (current-fn &rest (&whole chain head &rest _)) entry ;; Examine head's defining scope: ;; Pre-processed chain, or top-level/external, keep as-is. (if (or (stringp head) (js2-node-top-level-decl-p head)) @@ -7747,24 +7758,46 @@ string is NAME. Returns nil and keeps current token otherwise." t)) (defun js2-match-async-arrow-function () - (when (and (js2-contextual-kwd-p (js2-current-token) "async") - (/= (js2-peek-token) js2-FUNCTION)) - (js2-record-face 'font-lock-keyword-face) - (js2-get-token) - t)) + (and (js2-contextual-kwd-p (js2-current-token) "async") + (/= (js2-peek-token) js2-FUNCTION))) -(defun js2-match-await () - (when (and (= tt js2-NAME) - (js2-contextual-kwd-p (js2-current-token) "await")) - (js2-record-face 'font-lock-keyword-face) - (let ((beg (js2-current-token-beg)) - (end (js2-current-token-end))) - (js2-get-token) - (unless (and (js2-inside-function) - (js2-function-node-async js2-current-script-or-fn)) - (js2-report-error "msg.bad.await" nil - beg (- end beg)))) - t)) +(defsubst js2-inside-function () + (cl-plusp js2-nesting-of-function)) + +(defsubst js2-inside-async-function () + (and (js2-inside-function) + (js2-function-node-async js2-current-script-or-fn))) + +(defun js2-parse-await-maybe (tt) + "Parse \"await\" as an AwaitExpression, if it is one." + (and (= tt js2-NAME) + (js2-contextual-kwd-p (js2-current-token) "await") + ;; Per the proposal, AwaitExpression consists of "await" + ;; followed by a UnaryExpression. So look ahead for one. + (let ((ts-state (make-js2-ts-state)) + (recorded-identifiers js2-recorded-identifiers) + (parsed-errors js2-parsed-errors) + (current-token (js2-current-token)) + (beg (js2-current-token-beg)) + (end (js2-current-token-end)) + pn) + (js2-get-token) + (setq pn (js2-make-unary js2-AWAIT 'js2-parse-unary-expr)) + (if (= (js2-node-type (js2-unary-node-operand pn)) js2-ERROR) + ;; The parse failed, so pretend like nothing happened and restore + ;; the previous parsing state. + (progn + (js2-ts-seek ts-state) + (setq js2-recorded-identifiers recorded-identifiers + js2-parsed-errors parsed-errors) + ;; And ensure the caller knows about the failure. + nil) + ;; The parse was successful, so process and return the "await". + (js2-record-face 'font-lock-keyword-face current-token) + (unless (js2-inside-async-function) + (js2-report-error "msg.bad.await" nil + beg (- end beg))) + pn)))) (defun js2-get-prop-name-token () (js2-get-token (and (>= js2-language-version 170) 'KEYWORD_IS_NAME))) @@ -7816,9 +7849,6 @@ Returns t on match, nil if no match." (js2-unget-token)) nil)) -(defsubst js2-inside-function () - (cl-plusp js2-nesting-of-function)) - (defun js2-set-requires-activation () (if (js2-function-node-p js2-current-script-or-fn) (setf (js2-function-node-needs-activation js2-current-script-or-fn) t))) @@ -8072,12 +8102,17 @@ declared; probably to check them for errors." (let ((subexpr (cond ((and (js2-infix-node-p elem) (= js2-ASSIGN (js2-infix-node-type elem))) + ;; Destructuring with default argument. (js2-infix-node-left elem)) ((and (js2-infix-node-p elem) (= js2-COLON (js2-infix-node-type elem))) + ;; In regular destructuring {a: aa, b: bb}, + ;; the var is on the right. In abbreviated + ;; destructuring {a, b}, right == left. (js2-infix-node-right elem)) ((and (js2-unary-node-p elem) (= js2-TRIPLEDOT (js2-unary-node-type elem))) + ;; Destructuring with spread. (js2-unary-node-operand elem))))) (when subexpr (push (js2-define-destruct-symbols @@ -8577,7 +8612,7 @@ imports or a namespace import that follows it. (js2-define-symbol js2-LET (js2-name-node-name name-node) name-node t)))))) ((= (js2-peek-token) js2-NAME) - (let ((binding (js2-maybe-parse-export-binding))) + (let ((binding (js2-maybe-parse-export-binding t))) (let ((node-name (js2-export-binding-node-local-name binding))) (js2-define-symbol js2-LET (js2-name-node-name node-name) node-name t)) (setf (js2-import-clause-node-default-binding clause) binding) @@ -8610,50 +8645,47 @@ imports or a namespace import that follows it. "Parse a namespace import expression such as '* as bar'. The current token must be js2-MUL." (let ((beg (js2-current-token-beg))) - (when (js2-must-match js2-NAME "msg.syntax") - (if (equal "as" (js2-current-token-string)) - (when (js2-must-match-prop-name "msg.syntax") - (let ((node (make-js2-namespace-import-node - :pos beg - :len (- (js2-current-token-end) beg) - :name (make-js2-name-node - :pos (js2-current-token-beg) - :len (js2-current-token-end) - :name (js2-current-token-string))))) - (js2-node-add-children node (js2-namespace-import-node-name node)) - node)) - (js2-unget-token) - (js2-report-error "msg.syntax"))))) + (if (js2-match-contextual-kwd "as") + (when (js2-must-match-prop-name "msg.syntax") + (let ((node (make-js2-namespace-import-node + :pos beg + :len (- (js2-current-token-end) beg) + :name (make-js2-name-node + :pos (js2-current-token-beg) + :len (js2-current-token-end) + :name (js2-current-token-string))))) + (js2-node-add-children node (js2-namespace-import-node-name node)) + node)) + (js2-unget-token) + (js2-report-error "msg.syntax")))) (defun js2-parse-from-clause () "Parse the from clause in an import or export statement. E.g. from 'src/lib'" - (when (js2-must-match-name "msg.mod.from.after.import.spec.set") - (let ((beg (js2-current-token-beg))) - (if (equal "from" (js2-current-token-string)) - (cond - ((js2-match-token js2-STRING) - (make-js2-from-clause-node - :pos beg - :len (- (js2-current-token-end) beg) - :module-id (js2-current-token-string) - :metadata-p nil)) - ((js2-match-token js2-THIS) - (when (js2-must-match-name "msg.mod.spec.after.from") - (if (equal "module" (js2-current-token-string)) - (make-js2-from-clause-node - :pos beg - :len (- (js2-current-token-end) beg) - :module-id "this" - :metadata-p t) - (js2-unget-token) - (js2-unget-token) - (js2-report-error "msg.mod.spec.after.from") - nil))) - (t (js2-report-error "msg.mod.spec.after.from") nil)) - (js2-unget-token) - (js2-report-error "msg.mod.from.after.import.spec.set") - nil)))) + (if (js2-match-contextual-kwd "from") + (let ((beg (js2-current-token-beg))) + (cond + ((js2-match-token js2-STRING) + (make-js2-from-clause-node + :pos beg + :len (- (js2-current-token-end) beg) + :module-id (js2-current-token-string) + :metadata-p nil)) + ((js2-match-token js2-THIS) + (when (js2-must-match-name "msg.mod.spec.after.from") + (if (equal "module" (js2-current-token-string)) + (make-js2-from-clause-node + :pos beg + :len (- (js2-current-token-end) beg) + :module-id "this" + :metadata-p t) + (js2-unget-token) + (js2-unget-token) + (js2-report-error "msg.mod.spec.after.from") + nil))) + (t (js2-report-error "msg.mod.spec.after.from") nil))) + (js2-report-error "msg.mod.from.after.import.spec.set") + nil)) (defun js2-parse-export-bindings (&optional import-p) "Parse a list of export binding expressions such as {}, {foo, bar}, and @@ -8661,7 +8693,7 @@ The current token must be js2-MUL." js2-LC. Return a lisp list of js2-export-binding-node" (let ((bindings (list))) (while - (let ((binding (js2-maybe-parse-export-binding))) + (let ((binding (js2-maybe-parse-export-binding import-p))) (when binding (push binding bindings)) (js2-match-token js2-COMMA))) @@ -8670,7 +8702,7 @@ js2-LC. Return a lisp list of js2-export-binding-node" "msg.mod.rc.after.export.spec.list")) (reverse bindings)))) -(defun js2-maybe-parse-export-binding () +(defun js2-maybe-parse-export-binding (&optional import-p) "Attempt to parse a binding expression found inside an import/export statement. This can take the form of either as single js2-NAME token as in 'foo' or as in a rebinding expression 'bar as foo'. If it matches, it will return an instance of @@ -8682,45 +8714,49 @@ consumes no tokens." (is-reserved-name (or (= (js2-current-token-type) js2-RESERVED) (aref js2-kwd-tokens (js2-current-token-type))))) (if extern-name - (let ((as (and (js2-match-token js2-NAME) (js2-current-token-string)))) - (if (and as (equal "as" (js2-current-token-string))) - (let ((name - (or - (and (js2-match-token js2-DEFAULT) "default") - (and (js2-match-token js2-NAME) (js2-current-token-string))))) - (if name - (let ((node (make-js2-export-binding-node - :pos beg - :len (- (js2-current-token-end) beg) - :local-name (make-js2-name-node - :name name - :pos (js2-current-token-beg) - :len (js2-current-token-len)) - :extern-name (make-js2-name-node - :name extern-name - :pos beg - :len extern-name-len)))) - (js2-node-add-children - node - (js2-export-binding-node-local-name node) - (js2-export-binding-node-extern-name node)) - node) - (js2-unget-token) - nil)) - (when as (js2-unget-token)) - (let* ((name-node (make-js2-name-node - :name (js2-current-token-string) - :pos (js2-current-token-beg) - :len (js2-current-token-len))) - (node (make-js2-export-binding-node - :pos (js2-current-token-beg) - :len (js2-current-token-len) - :local-name name-node - :extern-name name-node))) - (when is-reserved-name - (js2-report-error "msg.mod.as.after.reserved.word" extern-name)) - (js2-node-add-children node name-node) - node))) + (if (js2-match-contextual-kwd "as") + (let ((name + (or + (and (js2-match-token js2-DEFAULT) "default") + (and (js2-match-token js2-NAME) (js2-current-token-string))))) + (if name + (let ((node (make-js2-export-binding-node + :pos beg + :len (- (js2-current-token-end) beg) + :local-name (make-js2-name-node + :name name + :pos (js2-current-token-beg) + :len (js2-current-token-len)) + :extern-name (make-js2-name-node + :name extern-name + :pos beg + :len extern-name-len)))) + (js2-node-add-children + node + (js2-export-binding-node-local-name node) + (js2-export-binding-node-extern-name node)) + (if import-p + (js2-set-face (js2-current-token-beg) (js2-current-token-end) + 'font-lock-variable-name-face 'record)) + node) + (js2-unget-token) + nil)) + (let* ((name-node (make-js2-name-node + :name (js2-current-token-string) + :pos (js2-current-token-beg) + :len (js2-current-token-len))) + (node (make-js2-export-binding-node + :pos (js2-current-token-beg) + :len (js2-current-token-len) + :local-name name-node + :extern-name name-node))) + (when is-reserved-name + (js2-report-error "msg.mod.as.after.reserved.word" extern-name)) + (js2-node-add-children node name-node) + (if import-p + (js2-set-face (js2-current-token-beg) (js2-current-token-end) + 'font-lock-variable-name-face 'record)) + node)) nil))) (defun js2-parse-switch () @@ -8848,15 +8884,17 @@ invalid export statements." (when exports-list (dolist (export exports-list) (push export children))) - (when (js2-match-token js2-NAME) - (if (equal "from" (js2-current-token-string)) - (progn - (js2-unget-token) - (setq from-clause (js2-parse-from-clause))) - (js2-unget-token)))) + (when (js2-match-contextual-kwd "from") + (js2-unget-token) + (setq from-clause (js2-parse-from-clause)))) ((js2-match-token js2-DEFAULT) (setq default (cond ((js2-match-token js2-CLASS) (js2-parse-class-stmt)) + ((js2-match-token js2-NAME) + (if (js2-match-async-function) + (js2-parse-async-function-stmt) + (js2-unget-token) + (js2-parse-expr))) ((js2-match-token js2-FUNCTION) (js2-parse-function-stmt)) (t (js2-parse-expr))))) @@ -8864,6 +8902,12 @@ invalid export statements." (setq declaration (js2-parse-variables (js2-current-token-type) (js2-current-token-beg)))) ((js2-match-token js2-CLASS) (setq declaration (js2-parse-class-stmt))) + ((js2-match-token js2-NAME) + (setq declaration + (if (js2-match-async-function) + (js2-parse-async-function-stmt) + (js2-unget-token) + (js2-parse-expr)))) ((js2-match-token js2-FUNCTION) (setq declaration (js2-parse-function-stmt))) (t @@ -8923,7 +8967,7 @@ Last matched token must be js2-FOR." ((= tt js2-SEMI) (js2-unget-token) (setq init (make-js2-empty-expr-node))) - ((or (= tt js2-VAR) (= tt js2-LET)) + ((or (= tt js2-VAR) (= tt js2-LET) (= tt js2-CONST)) (setq init (js2-parse-variables tt (js2-current-token-beg)))) (t (js2-unget-token) @@ -9708,6 +9752,9 @@ If NODE is non-nil, it is the AST node associated with the symbol." ((and (= tt js2-ARROW) (>= js2-language-version 200)) (js2-ts-seek ts-state) + (when async-p + (js2-record-face 'font-lock-keyword-face) + (js2-get-token)) (setq js2-recorded-identifiers recorded-identifiers js2-parsed-errors parsed-errors) (setq pn (js2-parse-function 'FUNCTION_ARROW (js2-current-token-beg) nil async-p))) @@ -9899,18 +9946,6 @@ to parse the operand (for prefix operators)." (js2-node-add-children pn expr) pn)) -(defun js2-make-await () - "Make an await node." - (let* ((pos (js2-current-token-beg)) - (expr (js2-parse-unary-expr)) - (end (js2-node-end expr)) - pn) - (setq pn (make-js2-await-node :pos pos - :len (- end pos) - :operand expr)) - (js2-node-add-children pn expr) - pn)) - (defconst js2-incrementable-node-types (list js2-NAME js2-GETPROP js2-GETELEM js2-GET_REF js2-CALL) "Node types that can be the operand of a ++ or -- operator.") @@ -9952,8 +9987,7 @@ to parse the operand (for prefix operators)." ((= tt js2-DELPROP) (js2-get-token) (js2-make-unary js2-DELPROP 'js2-parse-unary-expr)) - ((js2-match-await) - (js2-make-unary js2-AWAIT 'js2-parse-unary-expr)) + ((js2-parse-await-maybe tt)) ((= tt js2-ERROR) (js2-get-token) (make-js2-error-node)) ; try to continue @@ -10661,6 +10695,7 @@ If ONLY-OF-P is non-nil, only the 'for (foo of bar)' form is allowed." (js2-set-face (js2-node-pos name) (js2-node-end name) 'font-lock-function-name-face 'record) (let ((node (js2-parse-class pos 'CLASS_STATEMENT name))) + (js2-record-imenu-functions node name) (js2-define-symbol js2-FUNCTION (js2-name-node-name name) node) @@ -10764,7 +10799,7 @@ expression)." ;; Found a key/value property (of any sort) ((member tt (list js2-NAME js2-STRING js2-NUMBER js2-LB)) (setq after-comma nil - elem (js2-parse-named-prop tt pos previous-token)) + elem (js2-parse-named-prop tt previous-token)) (if (and (null elem) (not js2-recover-from-parse-errors)) (setq continue nil))) @@ -10823,7 +10858,7 @@ expression)." (js2-must-match js2-RC "msg.no.brace.prop") (nreverse elems))) -(defun js2-parse-named-prop (tt pos previous-token) +(defun js2-parse-named-prop (tt previous-token) "Parse a name, string, or getter/setter object property. When `js2-is-in-destructuring' is t, forms like {a, b, c} will be permitted." (let ((key (js2-parse-prop-name tt)) @@ -10831,8 +10866,10 @@ When `js2-is-in-destructuring' is t, forms like {a, b, c} will be permitted." (property-type (when previous-token (if (= (js2-token-type previous-token) js2-MUL) "*" - (js2-token-string previous-token))))) + (js2-token-string previous-token)))) + pos) (when (member prop '("get" "set" "async")) + (setq pos (js2-token-beg previous-token)) (js2-set-face (js2-token-beg previous-token) (js2-token-end previous-token) 'font-lock-keyword-face 'record)) ; get/set/async @@ -10964,6 +11001,7 @@ TYPE-STRING is a string `get', `set', `*', or nil, indicating a found keyword." (js2-node-set-prop fn 'METHOD_TYPE type) ; for codegen (when (string= type-string "*") (setf (js2-function-node-generator-type fn) 'STAR)) + (unless pos (setq pos (js2-node-pos prop))) (setq end (js2-node-end fn) result (make-js2-method-node :pos pos :len (- end pos) @@ -11008,6 +11046,7 @@ And, if CHECK-ACTIVATION-P is non-nil, use the value of TOKEN." "Print the path to the JSON value under point, and save it in the kill ring. If HARDCODED-ARRAY-INDEX provided, array index in JSON path is replaced with it." (interactive "P") + (js2-reparse) (let (previous-node current-node key-name rlt) @@ -11550,7 +11589,9 @@ Selecting an error will jump it to the corresponding source-buffer error. (run-hooks 'js2-init-hook) - (js2-reparse)) + (let ((js2-idle-timer-delay 0)) + ;; Schedule parsing for after when the mode hooks run. + (js2-mode-reset-timer))) ;; We may eventually want js2-jsx-mode to derive from js-jsx-mode, but that'd be ;; a bit more complicated and it doesn't net us much yet. @@ -11901,10 +11942,7 @@ PARSE-STATUS is as documented in `parse-partial-sexp'." (insert "\n") (indent-to col) (insert "*/")))) - ((and single - (save-excursion - (and (zerop (forward-line 1)) - (looking-at "\\s-*//")))) + (single (indent-to col) (insert "// "))) ;; Don't need to extend the comment after all. @@ -12551,7 +12589,10 @@ it marks the next defun after the ones already marked." (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)) + (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) diff --git a/js2-old-indent.el b/js2-old-indent.el index 82a00b4a2..f336005e4 100644 --- a/js2-old-indent.el +++ b/js2-old-indent.el @@ -131,13 +131,13 @@ switch statement body are indented one additional level." followed by an opening brace.") (defconst js2-indent-operator-re - (concat "[-+*/%<>&^|?:.]\\([^-+*/]\\|$\\)\\|!?=\\|" - (regexp-opt '("in" "instanceof") 'words)) + (concat "[-+*/%<>&^|?:.]\\([^-+*/.]\\|$\\)\\|!?=\\|" + (regexp-opt '("in" "instanceof") 'symbols)) "Regular expression matching operators that affect indentation of continued expressions.") (defconst js2-declaration-keyword-re - (regexp-opt '("var" "let" "const") 'words) + (regexp-opt '("var" "let" "const") 'symbols) "Regular expression matching variable declaration keywords.") (defun js2-re-search-forward-inner (regexp &optional bound count) @@ -225,31 +225,30 @@ and comments have been removed." (eq (char-after) ??)))) (not (and (eq (char-after) ?*) - (looking-at (concat "\\* *" js2-mode-identifier-re " *(")) + ;; Generator method (possibly using computed property). + (looking-at (concat "\\* *\\(?:\\[\\|" + js2-mode-identifier-re + " *(\\)")) (save-excursion - (goto-char (1- (match-end 0))) - (let (forward-sexp-function) (forward-sexp)) - (js2-forward-sws) - (eq (char-after) ?{)))))) + (js2-backward-sws) + ;; We might misindent some expressions that would + ;; return NaN anyway. Shouldn't be a problem. + (memq (char-before) '(?, ?} ?{))))))) (defun js2-continued-expression-p () "Return non-nil if the current line continues an expression." (save-excursion (back-to-indentation) - (or (js2-looking-at-operator-p) - (when (catch 'found - (while (and (re-search-backward "\n" nil t) - (let ((state (syntax-ppss))) - (when (nth 4 state) - (goto-char (nth 8 state))) ;; skip comments - (skip-chars-backward " \t") - (if (bolp) - t - (throw 'found t)))))) - (backward-char) - (when (js2-looking-at-operator-p) - (backward-char) - (not (looking-at "\\*\\|\\+\\+\\|--\\|/[/*]"))))))) + (if (js2-looking-at-operator-p) + (or (not (memq (char-after) '(?- ?+))) + (progn + (forward-comment (- (point))) + (not (memq (char-before) '(?, ?\[ ?\())))) + (forward-comment (- (point))) + (or (bobp) (backward-char)) + (when (js2-looking-at-operator-p) + (backward-char) + (not (looking-at "\\*\\|\\+\\+\\|--\\|/[/*]")))))) (defun js2-end-of-do-while-loop-p () "Return non-nil if word after point is `while' of a do-while diff --git a/tests/externs.el b/tests/externs.el index 40957f600..09a60a9d3 100644 --- a/tests/externs.el +++ b/tests/externs.el @@ -42,6 +42,13 @@ (should (equal (js2-get-jslint-globals) '("quux" "tee" "$"))))) +(ert-deftest js2-finds-jslint-globals-with-space () + (with-temp-buffer + (insert "/* global foo, bar:false, baz:true") + (js2-mode) + (should (equal (js2-get-jslint-globals) + '("foo" "bar" "baz"))))) + ;;;TODO ;; ensure that any symbols bound with the import syntax are added to the extern list ;; ensure that any symbols bound with the export syntax exist in the file scope diff --git a/tests/indent.el b/tests/indent.el index bacb61485..2fe60378c 100644 --- a/tests/indent.el +++ b/tests/indent.el @@ -32,6 +32,7 @@ s (replace-regexp-in-string "^ *" "" s))) (js2-jsx-mode) + (js2-reparse) ; solely for js2-jsx-self-closing, for some reason (indent-region (point-min) (point-max)) (should (string= s (buffer-substring-no-properties (point-min) (point))))))) @@ -146,7 +147,15 @@ "class A { | * x() { | return 1 - | * 2; + | * a(2); + | } + |}") + +(js2-deftest-indent indent-generator-computed-method + "class A { + | *[Symbol.iterator]() { + | yield 'Foo'; + | yield 'Bar'; | } |}") @@ -171,6 +180,13 @@ |}" :bind ((js2-indent-switch-body t))) +(js2-deftest-indent continued-expression-vs-unary-minus + "var arr = [ + | -1, 2, + | -3, 4 + + | -5 + |];") + (js2-deftest-indent jsx-one-line "var foo =
;") diff --git a/tests/parser.el b/tests/parser.el index ea3584a5b..9667e5660 100644 --- a/tests/parser.el +++ b/tests/parser.el @@ -35,14 +35,18 @@ ,@body) (fundamental-mode))))) +(defun js2-mode--and-parse () + (js2-mode) + (js2-reparse)) + (defun js2-test-string-to-ast (s) (insert s) - (js2-mode) + (js2-mode--and-parse) (should (null js2-mode-buffer-dirty-p)) js2-mode-ast) (cl-defun js2-test-parse-string (code-string &key syntax-error errors-count - reference) + reference warnings-count) (ert-with-test-buffer (:name 'origin) (let ((ast (js2-test-string-to-ast code-string))) (if syntax-error @@ -57,10 +61,13 @@ (skip-chars-backward " \t\n") (should (string= (or reference code-string) (buffer-substring-no-properties - (point-min) (point))))))))) + (point-min) (point))))) + (when warnings-count + (should (= warnings-count + (length (js2-ast-root-warnings ast))))))))) (cl-defmacro js2-deftest-parse (name code-string &key bind syntax-error errors-count - reference) + reference warnings-count) "Parse CODE-STRING. If SYNTAX-ERROR is nil, print syntax tree with `js2-print-tree' and assert the result to be equal to REFERENCE, if present, or the original string. If SYNTAX-ERROR @@ -73,6 +80,7 @@ the test." (js2-test-parse-string ,code-string :syntax-error ,syntax-error :errors-count ,errors-count + :warnings-count ,warnings-count :reference ,reference)))) ;;; Basics @@ -126,6 +134,9 @@ the test." (js2-deftest-parse let-expression-statement "let (x = 42) x;") +(js2-deftest-parse void + "void 0;") + ;;; Callers of `js2-valid-prop-name-token' (js2-deftest-parse parse-property-access-when-not-keyword @@ -158,6 +169,12 @@ the test." (js2-deftest-parse parse-for-of "for (var a of []) {\n}") +(js2-deftest-parse of-can-be-name + "void of;") + +(js2-deftest-parse of-can-be-object-name + "of.z;") + (js2-deftest-parse of-can-be-var-name "var of = 3;") @@ -167,22 +184,27 @@ the test." ;;; Destructuring binding (js2-deftest-parse destruct-in-declaration - "var {a, b} = {a: 1, b: 2};") + "var {a, b} = {a: 1, b: 2};" + :warnings-count 0) (js2-deftest-parse destruct-in-arguments - "function f({a: aa, b: bb}) {\n}") + "function f({a: aa, b: bb}) {\n}" + :warnings-count 0) (js2-deftest-parse destruct-in-array-comp-loop "[a + b for ([a, b] in [[0, 1], [1, 2]])];") (js2-deftest-parse destruct-in-catch-clause - "try {\n} catch ({a, b}) {\n a + b;\n}") + "try {\n} catch ({a, b}) {\n a + b;\n}" + :warnings-count 0) (js2-deftest-parse destruct-with-initializer-in-object - "var {a, b = 2, c} = {};") + "var {a, b = 2, c} = {};\nb;" + :warnings-count 0) (js2-deftest-parse destruct-with-initializer-in-array - "var [a, b = 2, c] = [];") + "var [a, b = 2, c] = [];\nb;" + :warnings-count 0) (js2-deftest-parse destruct-non-name-target-is-error "var {1=1} = {};" :syntax-error "1" :errors-count 1) @@ -197,12 +219,12 @@ the test." "\"use strict\";\nvar {a=1,a=2} = {};" :syntax-error "a" :errors-count 1) (js2-deftest destruct-name-conflict-is-warning-in-array "\"use strict\";\nvar [a=1,a=2] = [];" - (js2-mode) + (js2-mode--and-parse) (should (equal '("msg.var.redecl" "a") (caar js2-parsed-warnings)))) (js2-deftest initializer-outside-destruct-is-error "({a=1});" - (js2-mode) + (js2-mode--and-parse) (should (equal "msg.init.no.destruct" (car (caar js2-parsed-errors))))) @@ -218,7 +240,7 @@ the test." "var x = {f(y) { return y;\n}};") (js2-deftest object-literal-method-own-name-in-scope "({f(){f();}});" - (js2-mode) + (js2-mode--and-parse) (should (equal '("msg.undeclared.variable" "f") (caar js2-parsed-warnings)))) @@ -246,11 +268,11 @@ the test." ;;; Function definition (js2-deftest function-redeclaring-var "var gen = 3; function gen() {};" - (js2-mode) + (js2-mode--and-parse) (should (= (length (js2-ast-root-warnings js2-mode-ast)) 1))) (js2-deftest function-expression-var-same-name "var gen = function gen() {};" - (js2-mode) + (js2-mode--and-parse) (should (null (js2-ast-root-warnings js2-mode-ast)))) ;;; Function parameters @@ -412,7 +434,7 @@ the test." (js2-deftest no-label-node-inside-expr "x = y:" (let (js2-parse-interruptable-p) - (js2-mode)) + (js2-mode--and-parse)) (let ((assignment (js2-expr-stmt-node-expr (car (js2-scope-kids js2-mode-ast))))) (should (js2-name-node-p (js2-assign-node-right assignment))))) @@ -504,12 +526,24 @@ the test." ;;; 'async' and 'await' are contextual keywords +(js2-deftest-parse async-can-be-name + "void async;") + +(js2-deftest-parse async-can-be-object-name + "async.z;") + (js2-deftest-parse async-can-be-var-name "var async = 3;") (js2-deftest-parse async-can-be-function-name "function async() {\n}") +(js2-deftest-parse await-can-be-name + "void await;") + +(js2-deftest-parse await-can-be-object-name + "await.z;") + (js2-deftest-parse await-can-be-var-name "var await = 3;") @@ -801,15 +835,22 @@ the test." (should (js2-export-node-default export-node)))) (js2-deftest export-function-no-semicolon "export default function foo() {}" - (js2-mode) + (js2-mode--and-parse) (should (equal nil js2-parsed-warnings))) (js2-deftest export-default-function-no-semicolon "export function foo() {}" - (js2-mode) + (js2-mode--and-parse) (should (equal nil js2-parsed-warnings))) (js2-deftest export-anything-else-does-require-a-semicolon "export var obj = {}" - (js2-mode) + (js2-mode--and-parse) (should (not (equal nil js2-parsed-warnings)))) +(js2-deftest export-default-async-function-no-semicolon "export default async function foo() {}" + (js2-mode--and-parse) + (should (equal nil js2-parsed-warnings))) +(js2-deftest export-async-function-no-semicolon "export async function foo() {}" + (js2-mode--and-parse) + (should (equal nil js2-parsed-warnings))) + (js2-deftest-parse parse-export-rexport "export * from 'other/lib';") (js2-deftest-parse parse-export-export-named-list "export {foo, bar as bang};") (js2-deftest-parse parse-re-export-named-list "export {foo, bar as bang} from 'other/lib';") @@ -819,6 +860,18 @@ the test." (js2-deftest-parse parse-export-generator-declaration "export default function* one() {\n}") (js2-deftest-parse parse-export-assignment-expression "export default a = b;") +(js2-deftest-parse parse-export-function-declaration-no-semi + "export function f() {\n}") + +(js2-deftest-parse parse-export-class-declaration-no-semi + "export class C {\n}") + +(js2-deftest-parse parse-export-async-function-allow-await + "export async function f() {\n await f();\n}") + +(js2-deftest-parse parse-export-default-async-function-allow-await + "export default async function f() {\n await f();\n}") + ;;; Strings (js2-deftest-parse string-literal @@ -884,7 +937,7 @@ the test." ;;; Scopes (js2-deftest ast-symbol-table-includes-fn-node "function foo() {}" - (js2-mode) + (js2-mode--and-parse) (let ((entry (js2-scope-get-symbol js2-mode-ast 'foo))) (should (= (js2-symbol-decl-type entry) js2-FUNCTION)) (should (equal (js2-symbol-name entry) "foo")) @@ -894,7 +947,7 @@ the test." function bar() {} var x; }" - (js2-mode) + (js2-mode--and-parse) (let* ((scope (js2-node-at-point (point-min))) (fn-entry (js2-scope-get-symbol scope 'bar)) (var-entry (js2-scope-get-symbol scope 'x))) @@ -912,36 +965,36 @@ the test." (should (funcall predicate (js2-get-defining-scope scope variable))))) (js2-deftest for-node-is-declaration-scope "for (let i = 0; i; ++i) {};" - (js2-mode) + (js2-mode--and-parse) (js2-test-scope-of-nth-variable-satisifies-predicate "i" 0 #'js2-for-node-p)) (js2-deftest const-scope-sloppy-script "{const a;} a;" - (js2-mode) + (js2-mode--and-parse) (js2-test-scope-of-nth-variable-satisifies-predicate "a" 0 #'js2-script-node-p) (js2-test-scope-of-nth-variable-satisifies-predicate "a" 1 #'js2-script-node-p)) (js2-deftest const-scope-strict-script "'use strict'; { const a; } a;" - (js2-mode) + (js2-mode--and-parse) (js2-test-scope-of-nth-variable-satisifies-predicate "a" 0 #'js2-block-node-p) (js2-test-scope-of-nth-variable-satisifies-predicate "a" 1 #'null)) (js2-deftest const-scope-sloppy-function "function f() { { const a; } a; }" - (js2-mode) + (js2-mode--and-parse) (js2-test-scope-of-nth-variable-satisifies-predicate "a" 0 #'js2-function-node-p) (js2-test-scope-of-nth-variable-satisifies-predicate "a" 1 #'js2-function-node-p)) (js2-deftest const-scope-strict-function "function f() { 'use strict'; { const a; } a; }" - (js2-mode) + (js2-mode--and-parse) (js2-test-scope-of-nth-variable-satisifies-predicate "a" 0 #'js2-block-node-p) (js2-test-scope-of-nth-variable-satisifies-predicate "a" 1 #'null)) (js2-deftest array-comp-is-result-scope "[x * 2 for (x in y)];" - (js2-mode) + (js2-mode--and-parse) (js2-test-scope-of-nth-variable-satisifies-predicate "x" 0 #'js2-comp-loop-node-p)) (js2-deftest array-comp-has-parent-scope "var a,b=[for (i of [[1,2]]) for (j of i) j * a];" - (js2-mode) + (js2-mode--and-parse) (search-forward "for") (forward-char -3) (let ((node (js2-node-at-point))) @@ -1011,23 +1064,23 @@ the test." ;;; Error handling (js2-deftest for-node-with-error-len "for " - (js2-mode) + (js2-mode--and-parse) (let ((node (js2-node-at-point (point-min)))) (should (= (js2-node-len (js2-node-parent node)) 4)))) (js2-deftest function-without-parens-error "function b {}" ;; Should finish the parse. - (js2-mode)) + (js2-mode--and-parse)) ;;; Comments (js2-deftest comment-node-length "//" - (js2-mode) + (js2-mode--and-parse) (let ((node (js2-node-at-point (point-min)))) (should (= (js2-node-len node) 2)))) (js2-deftest comment-node-length-newline "//\n" - (js2-mode) + (js2-mode--and-parse) (let ((node (js2-node-at-point (point-min)))) (should (= (js2-node-len node) 3)))) @@ -1066,7 +1119,7 @@ the test." (insert ,buffer-contents)) (unwind-protect (progn - (js2-mode) + (js2-mode--and-parse) (should (equal ,summary (js2--variables-summary (js2--classify-variables))))) (fundamental-mode)))))