]> code.delx.au - gnu-emacs-elpa/commitdiff
Merge pull request #289 from dgreensp/fix-array-rest
authorDmitry Gutov <dgutov@yandex.ru>
Sun, 27 Mar 2016 00:58:16 +0000 (03:58 +0300)
committerDmitry Gutov <dgutov@yandex.ru>
Sun, 27 Mar 2016 00:58:16 +0000 (03:58 +0300)
Fix array destructuring including triple-dot

1  2 
js2-mode.el
tests/parser.el

diff --combined js2-mode.el
index 8420c4e35ed6c63cf5e34b816e082889ff892700,4ad9c6a77dd15437b79b4b3a84ddbddc9d3a7646..7c30a1c59b3720803c81910018e18c4b3cbbf387
@@@ -2551,9 -2551,7 +2551,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
@@@ -3479,7 -3477,6 +3479,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 "||")
@@@ -3581,8 -3578,7 +3581,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
@@@ -3845,32 -3841,9 +3845,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)
@@@ -3882,7 -3855,7 +3882,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)
@@@ -6906,18 -6902,15 +6906,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"
@@@ -7511,7 -7502,7 +7511,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))
@@@ -7755,46 -7746,24 +7755,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)))
@@@ -7846,6 -7815,9 +7846,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)))
@@@ -8107,7 -8079,11 +8107,11 @@@ declared; probably to check them for er
       ((js2-array-node-p node)
        (dolist (elem (js2-array-node-elems node))
          (when elem
-           (if (js2-infix-node-p elem) (setq elem (js2-infix-node-left elem)))
+           (setq elem (cond ((js2-infix-node-p elem) ;; default (=)
+                             (js2-infix-node-left elem))
+                            ((js2-unary-node-p elem) ;; rest (...)
+                             (js2-unary-node-operand elem))
+                            (t elem)))
            (push (js2-define-destruct-symbols
                   elem decl-type face ignore-not-in-block)
                  name-nodes)))
@@@ -8873,11 -8849,6 +8877,11 @@@ invalid export statements.
       ((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
@@@ -8950,7 -8915,7 +8954,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)
@@@ -9735,9 -9700,6 +9739,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)))
@@@ -9929,6 -9891,18 +9933,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
@@@ -10478,19 -10453,13 +10482,13 @@@ array-literals, array comprehensions an
  
  (defun js2-parse-array-literal (pos)
    (let ((after-lb-or-comma t)
-         after-comma tt elems pn
+         after-comma tt elems pn was-rest
          (continue t))
      (unless js2-is-in-destructuring
        (js2-push-scope (make-js2-scope))) ; for the legacy array comp
      (while continue
        (setq tt (js2-get-token))
        (cond
-        ;; comma
-        ((= tt js2-COMMA)
-         (setq after-comma (js2-current-token-end))
-         (if (not after-lb-or-comma)
-             (setq after-lb-or-comma t)
-           (push nil elems)))
         ;; end of array
         ((or (= tt js2-RB)
              (= tt js2-EOF))  ; prevent infinite loop
                                        :len (- js2-ts-cursor pos)
                                        :elems (nreverse elems)))
          (apply #'js2-node-add-children pn (js2-array-node-elems pn)))
-        ;; destructuring binding
-        (js2-is-in-destructuring
-         (push (cond
-                ((and (= tt js2-NAME)
-                      (= js2-ASSIGN (js2-peek-token)))
-                 ;; a=defaultValue
-                 (js2-parse-initialized-binding (js2-parse-name js2-NAME)))
-                ((or (= tt js2-LC)
-                     (= tt js2-LB)
-                     (= tt js2-NAME))
-                 ;; [a, b, c] | {a, b, c} | {a:x, b:y, c:z} | a
-                 (js2-parse-destruct-primary-expr))
-                ;; invalid pattern
-                (t
-                 (js2-report-error "msg.bad.var")
-                 (make-js2-error-node)))
-               elems)
-         (setq after-lb-or-comma nil
-               after-comma nil))
+        ;; anything after rest element (...foo)
+        (was-rest
+         (js2-report-error "msg.param.after.rest"))
+        ;; comma
+        ((= tt js2-COMMA)
+         (setq after-comma (js2-current-token-end))
+         (if (not after-lb-or-comma)
+             (setq after-lb-or-comma t)
+           (push nil elems)))
         ;; array comp
         ((and (>= js2-language-version 170)
+              (not js2-is-in-destructuring)
               (= tt js2-FOR)          ; check for array comprehension
               (not after-lb-or-comma) ; "for" can't follow a comma
               elems                   ; must have at least 1 element
            (js2-report-error "msg.no.bracket.arg"))
          (if (and (= tt js2-TRIPLEDOT)
                   (>= js2-language-version 200))
-             ;; spread operator
-             (push (js2-make-unary tt 'js2-parse-assign-expr)
-                   elems)
+             ;; rest/spread operator
+             (progn
+               (push (js2-make-unary tt 'js2-parse-assign-expr)
+                     elems)
+               (if js2-is-in-destructuring
+                   (setq was-rest t)))
            (js2-unget-token)
            (push (js2-parse-assign-expr) elems))
          (setq after-lb-or-comma nil
@@@ -10690,7 -10653,6 +10682,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)
@@@ -10785,7 -10747,7 +10777,7 @@@ expression).
         ;; Found a 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 -10947,6 +10979,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)
@@@ -11925,7 -11884,10 +11917,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.
diff --combined tests/parser.el
index d32f0426a5d0c1757d1bf9e59640801d9a901089,5e19c5b2c0fcebc2563e8250e2ef7616d2a99f4c..90bbadd72000c8e5be653796c639644843b4d7d5
@@@ -126,9 -126,6 +126,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
  (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;")
  
  (js2-deftest-parse spread-in-function-call
    "f(3, ...[t(2), t(3)], 42, ...[t(4)]);")
  
+ (js2-deftest-parse rest-in-array-destructure
+   "let [x, y, z, ...w] = [1, ...a, ...b, c];")
+ (js2-deftest-parse comma-after-rest-in-array
+   "let [...x,] = [1, 2, 3];"
+   :syntax-error "," :errors-count 1)
+ (js2-deftest-parse elem-after-rest-in-array
+   "let [...x, y] = [1, 2, 3];"
+   :syntax-error "," :errors-count 2)
+ (js2-deftest-parse array-destructure-expr-default
+   "let [[x] = [3]] = y;")
  ;;; Arrow functions
  
  (js2-deftest-parse arrow-function-with-empty-args-and-no-curlies
  
  ;;; '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;")
  
    (js2-mode)
    (should (not (equal nil js2-parsed-warnings))))
  
 +(js2-deftest export-default-async-function-no-semicolon "export default async function foo() {}"
 +  (js2-mode)
 +  (should (equal nil js2-parsed-warnings)))
 +(js2-deftest export-async-function-no-semicolon "export async function foo() {}"
 +  (js2-mode)
 +  (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