]> code.delx.au - gnu-emacs-elpa/commitdiff
Merge branch 'dgreensp-object-rest-spread'
authorDmitry Gutov <dgutov@yandex.ru>
Sun, 29 May 2016 00:06:57 +0000 (03:06 +0300)
committerDmitry Gutov <dgutov@yandex.ru>
Sun, 29 May 2016 00:06:57 +0000 (03:06 +0300)
1  2 
js2-mode.el
tests/parser.el

diff --combined js2-mode.el
index 238440c8f7a7f3582e44019f0ff5f8d180523e9b,22442b96f976d3b7608e16e2c2e4d91251598481..4d9bbf8da02e85ea35cb873dcb0bb879874c4951
@@@ -1076,7 -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."
@@@ -1456,7 -1455,7 +1456,7 @@@ the correct number of ARGS must be prov
           "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.")
@@@ -2552,9 -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
@@@ -3480,7 -3477,6 +3480,7 @@@ The type field inherited from `js2-node
                 (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 "||")
@@@ -3582,8 -3578,7 +3582,8 @@@ property is added if the operator follo
        (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
@@@ -3846,32 -3841,9 +3846,32 @@@ You can tell the quote type by looking 
        (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)
@@@ -3883,7 -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)
          (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)
@@@ -6907,18 -6902,15 +6907,18 @@@ of a simple name.  Called before EXPR h
             '("alias"
               "augments"
               "borrows"
 +             "callback"
               "bug"
               "base"
               "config"
               "default"
               "define"
               "exception"
 +             "func"
               "function"
               "member"
               "memberOf"
 +             "method"
               "name"
               "namespace"
               "since"
               "export"
               "fileoverview"
               "final"
 +             "func"
               "function"
               "hidden"
               "ignore"
               "inner"
               "interface"
               "license"
 +             "method"
               "noalias"
               "noshadow"
               "notypecheck"
@@@ -7259,12 -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))))
@@@ -7456,22 -7445,23 +7456,23 @@@ We do a depth-first traversal of NODE
  we append the property name to QNAME, then call `js2-record-imenu-entry'."
    (let (right)
      (dolist (e (js2-object-node-elems node))  ; e is a `js2-object-prop-node'
-       (let ((left (js2-infix-node-left e))
-             ;; Element positions are relative to the parent position.
-             (pos (+ pos (js2-node-pos e))))
-         (cond
-          ;; foo: function() {...}
-          ((js2-function-node-p (setq right (js2-infix-node-right e)))
-           (when (js2-prop-node-name left)
-             ;; As a policy decision, we record the position of the property,
-             ;; not the position of the `function' keyword, since the property
-             ;; is effectively the name of the function.
-             (js2-record-imenu-entry right (append qname (list left)) pos)))
-          ;; foo: {object-literal} -- add foo to qname, offset position, and recurse
-          ((js2-object-node-p right)
-           (js2-record-object-literal right
-                                      (append qname (list (js2-infix-node-left e)))
-                                      (+ pos (js2-node-pos right)))))))))
+       (when (js2-infix-node-p e)
+         (let ((left (js2-infix-node-left e))
+               ;; Element positions are relative to the parent position.
+               (pos (+ pos (js2-node-pos e))))
+           (cond
+            ;; foo: function() {...}
+            ((js2-function-node-p (setq right (js2-infix-node-right e)))
+             (when (js2-prop-node-name left)
+               ;; As a policy decision, we record the position of the property,
+               ;; not the position of the `function' keyword, since the property
+               ;; is effectively the name of the function.
+               (js2-record-imenu-entry right (append qname (list left)) pos)))
+            ;; foo: {object-literal} -- add foo to qname, offset position, and recurse
+            ((js2-object-node-p right)
+             (js2-record-object-literal right
+                                        (append qname (list (js2-infix-node-left e)))
+                                        (+ pos (js2-node-pos right))))))))))
  
  (defun js2-node-top-level-decl-p (node)
    "Return t if NODE's name is defined in the top-level scope.
@@@ -7513,7 -7503,7 +7514,7 @@@ For instance, processing a nested scop
    (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))
@@@ -7757,46 -7747,24 +7758,46 @@@ string is NAME.  Returns nil and keeps 
      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)))
@@@ -7848,6 -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)))
@@@ -8098,21 -8069,20 +8099,25 @@@ declared; probably to check them for er
          (list node)))
       ((js2-object-node-p node)
        (dolist (elem (js2-object-node-elems node))
-         ;; js2-infix-node-p catches both object prop node and initialized
-         ;; binding element (which is directly an infix node).
-         (cond
-          ((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))
-          ;; Destructuring with default argument.
-          ((js2-infix-node-p elem)
-           (push (js2-define-destruct-symbols
-                  (js2-infix-node-left elem)
-                  decl-type face ignore-not-in-block)
-                 name-nodes))))
+         (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
+                    subexpr 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))
@@@ -8607,7 -8577,7 +8612,7 @@@ imports or a namespace import that foll
                (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)
    "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
  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)))
                                     "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
@@@ -8709,49 -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 ()
@@@ -8879,17 -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)))))
        (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
@@@ -8962,7 -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)
@@@ -9747,9 -9708,6 +9752,9 @@@ If NODE is non-nil, it is the AST node 
         ((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)))
@@@ -9941,6 -9899,18 +9946,6 @@@ to parse the operand (for prefix operat
      (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.")
       ((= 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
@@@ -10690,7 -10661,6 +10695,7 @@@ If ONLY-OF-P is non-nil, only the 'for 
      (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)
  `js2-method-node') as a string, or nil if it can't be
  represented as a string (e.g., the key is computed by an
  expression)."
-   (let ((key (js2-infix-node-left property-node)))
-     (when (js2-computed-prop-name-node-p key)
-       (setq key (js2-computed-prop-name-node-expr key)))
-     (cond
-      ((js2-name-node-p key)
-       (js2-name-node-name key))
-      ((js2-string-node-p key)
-       (js2-string-node-value key))
-      ((js2-number-node-p key)
-       (js2-number-node-value key)))))
+   (cond
+    ((js2-unary-node-p property-node) nil) ;; {...foo}
+    (t
+     (let ((key (js2-infix-node-left property-node)))
+       (when (js2-computed-prop-name-node-p key)
+         (setq key (js2-computed-prop-name-node-expr key)))
+       (cond
+        ((js2-name-node-p key)
+         (js2-name-node-name key))
+        ((js2-string-node-p key)
+         (js2-string-node-value key))
+        ((js2-number-node-p key)
+         (js2-number-node-value key)))))))
  
  (defun js2-parse-object-literal-elems (&optional class-p)
    (let ((pos (js2-current-token-beg))
            (setq previous-token (js2-current-token)
                  tt (js2-get-prop-name-token))))
        (cond
-        ;; Found a property (of any sort)
+        ;; Rest/spread (...expr)
+        ((and (>= js2-language-version 200)
+              (not class-p) (not static) (not previous-token)
+              (= js2-TRIPLEDOT tt))
+         (setq after-comma nil
+               elem (js2-make-unary js2-TRIPLEDOT 'js2-parse-assign-expr)))
+        ;; 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)))
      (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))
          (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
@@@ -10987,7 -10964,6 +11001,7 @@@ TYPE-STRING is a string `get', `set', `
      (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)
@@@ -11032,7 -11008,6 +11046,7 @@@ And, if CHECK-ACTIVATION-P is non-nil, 
    "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)
@@@ -11575,9 -11550,7 +11589,9 @@@ Selecting an error will jump it to the 
  
    (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.
@@@ -11928,7 -11901,10 +11942,7 @@@ PARSE-STATUS is as documented in `parse
              (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.
@@@ -12575,10 -12551,7 +12589,10 @@@ it marks the next defun after the ones 
  (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 --combined tests/parser.el
index b546c1cff0a4462e2dcf8d516a8200a65485458c,ea3584a5b10ebb8b003fb2a9f05325f5bd31d134..9667e566067986b9484abd1dda5480ec919f5f71
               ,@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
            (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
@@@ -80,7 -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
  (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
  (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;")
  
  ;;; 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)
    "\"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)))))
  
    "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))))
  
  ;;; 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
  (js2-deftest-parse array-destructure-expr-default
    "let [[x] = [3]] = y;")
  
+ (js2-deftest-parse spread-in-object-literal
+   "f({x, y, ...z});")
+ (js2-deftest-parse rest-in-object-literal
+   "const {x, y, ...z} = f();")
  ;;; Arrow functions
  
  (js2-deftest-parse arrow-function-with-empty-args-and-no-curlies
  
  (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)))))
  
  
  ;;; '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;")
  
      (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';")
  (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
  ;;; 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"))
    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)))
      (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)))
  ;;; 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))))
  
           (insert ,buffer-contents))
         (unwind-protect
             (progn
 -             (js2-mode)
 +             (js2-mode--and-parse)
               (should (equal ,summary (js2--variables-summary
                                        (js2--classify-variables)))))
           (fundamental-mode)))))